deftwoSum(List:nums,int:target): dict = {} for i,num inenumerate(nums): if target-num indict: return [d[target-num],i] d[num] = i return [] # 没找到的情况下
2.字母异位词分组
核心思路:异位词指排序后相同;因此可以以排序后的为key,字符串本身为value
1 2 3 4 5 6 7 8 9 10 11 12 13
defgroupAnagrams(self, strs: List[str]) -> List[List[str]]: dict = {} for s in strs: key = ''.join(sorted(s)) # 不确定key直接sort()能不能行,应该是不行的 if key notindict: d[key] = [s] else: d[key].append(s) ans = [] for _,v indict.items(): ans += v return ans
defquicksort(arr,start,end): # 快排,这里指数字;字符串可以通过ord或者其他方式进行排序 if arr = 0or start >= end: return target = arr[start] left = start right = end while left<right: # 从右往左,找到比target小的 while left<right and arr[right] >= target: right -= 1 #交换 arr[left] = arr[right] #从左往右,找到比target大的 while left<right and arr[left] < target: left += 1 arr[right] = arr[left] #基准定位 arr[left] = target quicksort(arr,start,l-1) quicksort(arr,l+1,end)
这里可以定义quicksort到字符串,一样的
3.最长连续子序列
循环查看nums[i]的前一位是否在nums里,如果存在说明本身不是开头,直接跳过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
deflongestConsecutive(self, nums: List[int]) -> int: dict = set(nums) ans = 0 if nums == Noneorlen(nums) == 0: return0 for num in nums: if num notindict: # 是开头,长度设置为1,枚举+1在不在 lenth = 1 while num+1in d: lenth += 1 num +=1 ans = max(ans,lenth) return ans
双指针
4.移动0
不复制数组,将所有的0移动至数组末尾:一次遍历,记录非0个数,并放过去;剩下的补0
1 2 3 4 5 6 7 8 9 10 11
defmoveZeroes(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ j = 0# 记录非0个数 for i inrange(len(nums)): if nums[i]: nums[j] = nums[i] j +=1 for i inrange(j,len(nums)): nums[i] = 0
另外一种双指针
1 2 3 4 5 6 7 8 9
defmoveZeroes(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ slow = 0# 指向下一个非零元素的位置 for fast inrange(len(nums)): if nums[fast] != 0: nums[slow], nums[fast] = nums[fast], nums[slow] slow += 1
5.盛水最多的容器
双指针+逼近,两个指针从两侧开始遍历,记录最大盛水值;留下高度高的,移动高度低的
1 2 3 4 5 6 7 8 9 10 11 12
defmaxArea(self, height: List[int]) -> int: n = len(height) l,r = 0,len(height)-1 ans = 0 while l<r: contain = min(height[l],height[r])*(r-l+1) if height[l] < height[r]: l += 1 else: r -= 1 ans = max(ans,contain) return ans
defthreeSum(self, nums: List[int]) -> List[List[int]]: nums.sort() n = len(nums) res = [] iflen(nums) < 3or nums == None: return [] for i inrange(1,n): if (nums[i])>0: #特判 return res if i > 0and nums[i] == nums[i-1]: contine#去重 else: l = i +1 r = n - 1 if nums[i] + nums[l] + nums[r] = 0: res.append([nums[i],nums[l],nums[r]]) while l<r and nums[l] == nums[l+1]: l+=1 while l<r and nums[r] == nums[r-1]: r-=1 l += 1 r -= 1 elif nums[i]+nums[l]+nums[r] > 0: r -= 1 else: l += 1 return res
deftrap(self, height: List[int]) -> int: left_ = [] right_ = [] max_l = 0 max_r = 0 for i inrange(0,len(height)): max_l = max(height[i],max_l) left_.append(max_l) print(left_) for j inrange(len(height)-1,-1,-1): max_r = max(height[j],max_r) right_.append(max_r) print(right_[::-1]) water = 0 for k inrange(len(height)): water += min(left_[k],right_[::-1][k]) - height[k] return (water)
此外,优化算法应该是使用栈:按元素进栈,循环判断当前的height和stack栈顶的元素;
入栈:栈为空;或者没有比目前更高的height,或者栈顶出栈之后栈空
出栈:找到更高的高度,接住从栈内要出栈到目前位置中间的雨水:
计算:min(左,右)-bot * i-left-1 ;中间长度;面积
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
deftrap(self, height: List[int]) -> int: ans = [] n = len(height) stack = [] for i inrange(n): while stack and height[i] > height[stack[-1]]: #说明栈不空且当前值大于栈顶,更新bot bot = height[stack.pop()] ifnot stack: break rain = (i-stack[-1]-1)*min(height[stack[-1]],height[i])-bot ans += rain #while else stack.append(i) return res
滑动窗口
滑动窗口应该有答题模板的
8.无重复的最长子串
思路:滑动窗口,考虑是子串而不是子序列,所以在内部进行循环;使用哈希表(dict)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
deflengthOfLongestSubstring(self, s: str) -> int: ans = 0 n = len(s) d = set() r = -1#外层窗口 # r起始放在-1是为了i=1时候r+1 = 0 for i inrange(n): if i!=0: #开始滑动了,因为一开始的遍历以i=1开始 d.remove(s[i-1]) while i<n and s[r+1] notin d: d.add(s[r+1]) r += 1 ans = max(ans,r-i+1) # 逻辑:给r递增,当不在d说明不重复,此时d可以更新,r可以累增,i到r之间是不重复的子串 return ans
9.找到字符串中所有字母的异位词
思路有点不太正经:排序p,对于s的所有长度等于p的子串依次判断
1 2 3 4 5 6 7 8
sup = sorted(p) ans = [] for i inrange(len(s)-len(p)+1): # print(i) ifsorted(s[i:i+len(p)]) == sup: # 这里用sorted不用set是因为异位的词有可能包含同样的字母 但是个数不一样 ans.append(i) return ans
子串
10.和为K的子数组:
哈希表+前缀和
注意,前缀和的解法只提供了某个位置到当前为止的前缀和等于k,不能准确知道序列
思路:dict:{前缀和:出现的次数};默认preSum = 0
遍历数组,更新前缀和,如果出现在preSum - k 出现在d中,那就增加次数ans += d[preSum - k];最后更新当前前缀和
1 2 3 4 5 6 7 8 9 10 11 12 13
defsubarraySum(self, nums: List[int], k: int) -> int: hash1 = collection.defaultdict(int) preSum = 0 ans = 0 hash[0] = 1 for num in nums: preSum += num if preSum - k in hash1: ans += 1 hash1[preSum] += 1 return ans
11.滑动窗口最大值
思路:双端队列,栈顶留最大值;ans每次增加的时候增加deq[0]的元素
左出栈:栈不为空且窗口越界
右出栈:新加入的nums[i]大于栈内的栈顶元素,出栈直到只剩下比nums[i]大的值
入栈:出完栈肯定要入的
添加元素:当前下标大于k-1(即形成窗口的时候)开始向ans内添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defmaxSlidingWindow(self, nums: List[int], k: int) -> List[int]: n = len(nums) deque = collections.deque() ans = [] if nums == Noneorlen(nums)==0: return for i inrange(n): if deque and deque[-1] > i - k +1: deque.popleft() while deque and nums[deque[-1]] < nums[i]: deque.pop() deque.append(i) if i > i -k +1: ans.append(nums[deque[0]) return ans
12.最小覆盖子串
普通数组
dp偏多
13.最大子数组和
思路:dp,dp[i]表示前i的最大和
dp[i] = dp[i-1] + nums[i]
如果dp[i-1] <0说明nums[i]最大(毕竟加了一个负数)
如果dp[i-1]>0说明最大值应该加上nums[i]本身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defmaxSubArray(self, nums: List[int]) -> int: n = len(nums) dp = [0] * n if n == 0: return0 if n == 1: return nums[0] dp[0] = nums[0] for i inrange(1,n): if dp[i-1] > 0: dp[i] = dp[i-1] + nums[i] else: dp[i] = nums[i] returnmax(dp)
defmerge(self, intervals: List[List[int]]) -> List[List[int]]: n = len(intervals) ans = [] intervals.sort(key = lambda x:x[0]) if n== 1: return intervals for i inrange(n): ifnot ans: ans.append(intervals[i]) else: start_bef,end_bef = ans[-1][0],ans[-1][1] if intervals[i][0] < end_bef: ans[-1][0] = min(intervals[i][0],ans[-1][0]) ans[-1][1] = max(intervals[-1][1],ans[-1][1]) else: ans.append(intervals[i]) return ans
15. 轮转数组
思路:右旋转k,相当于后k个拼接到前(n-k)个
1 2 3 4 5 6 7
n = len(nums) step = k % len(nums) # 右旋转k,相当于后k个拼接到前(n-k)个 t = nums[0-step:]+nums[:n-step] print(nums,t,k) for i inrange(len(nums)): nums[i] = t[i]
16. 除自身以外的乘积
思路:开辟两个数组:前缀积和后缀积,遍历i时直接将i位置的前缀积和后缀积相乘
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defproductExceptSelf(self, nums: List[int]) -> List[int]: n = len(nums) ans = [] former_ = [1] * n latter_ = [1] * n for i inrange(1,n): former_[i] = former_[i-1]*nums[i-1] for i inrange(1,n): latter_[i] = latter_[i+1]*nums[i+1] for j inrange(n): ans.append(former_[j]*latter_[j]) return ans
deffirstMissingPositive(self, nums: List[int]) -> int: n = len(nums) for i inrange(n): if nums[i] < 0: nums[i] = n+1 for i inrange(n): num = abs(nums[i]) if num < n: nums[num-1] = -abs(nums[num-1]) #数组本身做个哈希,处理重复情况;使用abs为了防止负数 for i inrange(n): if nums[i]>0: return i+1 return n+1
矩阵
18. 矩阵置0
思路:直接模拟,用两个栈保存x和y为0的坐标,然后循环遍历两个栈的元素使对应的行/列变为0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defsetZeroes(self, matrix: List[List[int]]) -> None: m,n = len(matrix),len(matrix[0]) x_sup = [] y_sup = [] for i inrange(m): for j inrange(n): if matrix[i][j] == 0: x_sup.append(i) y_sup.append(j) while x_sup and y_sup: x = x_sup.pop() y = y_sup.pop() for i inrange(n): matrix[x][i] = 0 for j inrange(m): matrix[j][y] = 0
defspiralOrder(self, matrix: List[List[int]]) -> List[int]: top,bot,left,right = 0,len(matrix),0,len(matrix[0])-1# bot是行数,right是列数 res = [] ifnot matrix: return whileTrue: for i inrange(l,r+1): res.append(matrix[top][i]) top += 1 if top > bot: break for i inrange(top,bot+1): res.append(matrix[i][right]) right -= 1 if right < left: break for i inrange(right,left-1,-1): res.append(matrix[bot][i]) bot -= 1 if bot < top: break for i inrange(bot,top-1,-1): res.append(matrix[i][left]) left +=1 if left > right: break return res
20. 旋转图像
思路:旋转图像等于转置之后行转置
1 2 3 4 5 6 7 8 9 10 11
defrotate(self, matrix: List[List[int]]) -> None: """ Do not return anything, modify matrix in-place instead. """ # 旋转90 = 对称+行翻转 m,n = len(matrix),len(matrix[0]) for i inrange(m): for j inrange(i+1,n): matrix[i][j],matrix[j][i] = matrix[j][i],matrix[i][j] for line in matrix: line = line.reverse()
defsearchMatrix(self, matrix: List[List[int]], target: int) -> bool: i,j = 0,len(matrix[0])-1 m,n = len(matrix),len(matrix[0]) while i<m and j >=0: if matrix[i][j] < target: i += 1 elif matrix[i][j] > target: j -= 1 else: returnTrue returnFalse
链表
链表题:
可以设置一些变量
dummy节点
快慢指针
22. 相交链表
思路:互相走对方的,如果相交应该最后会相遇,反推一定在相交节点相遇
代码:定义两个链表,随后进入循环->a=b时候跳出->当某一个为空时候走另一个链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution: defgetIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: a = headA b = headB while a!=b: if a == None: a = headB else: a = a.next if b == None: b = headA else: b = b.next # 跳出循环,此时a=b return a
23. 翻转链表
经典翻转了,挂链
1 2 3 4 5 6 7 8 9
defreverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: pre = None cur = head while cur: temp = cur.next cur.next = pre #拉断 pre = cur #重组 cur = temp return pre
defisPalindrome(self, head: Optional[ListNode]) -> bool: fast = head slow = head while fast and fast.next: slow = slow.next fast = fast.next.next #此时slow是中间 m = slow pre = None while m: temp = m.next m.next = pre pre = m m = temp while pre: if pre.val != head.val: returnFalse returnTrue
25. 环形链表
思路:快慢指针判相遇
1 2 3 4 5 6 7 8 9 10
defhasCycle(self, head: ListNode) -> bool: slow = head fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if fast is slow: #判定指针 不能用等于 returnTrue returnFalse
26. 环形链表II
思路:这个链表要求返回入口节点 先判相遇,再相遇节点之后从头开始,再次相遇就是相遇节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
if head == None: returnNone slow = head fast = head while slow.nextand fast and fast.next: slow = slow.next fast = fast.next.next #快慢指针的plus,如果存在环,那么slow到相遇点=起始点到相遇点(两者在相遇点相遇) if slow is fast: n = head while n isnot slow: n = n.next slow = slow.next return n returnNone
defremoveNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: dummy = ListNode(0) dummy.next = head fast = dummy slow = dummy for _ inrange(n): if fast.next: fast = fast.next else: return head while fast and fast.next: slow = slow.fast fast = fast.next slow = slow.next.next return dummy.next
defput(self, key: int, value: int) -> None: if key inself: self.move_to_end(key) self[key] = value iflen(self) > self.capacity: self.popitem(last=False)
二叉树
二叉树以递归、bfs(层序)、栈为主,注意实现;
其中递归主要理解,栈模拟作为思路可以在每一个总结
36. 二叉树的中序遍历
正常中序遍历,这里可能需要说的是不带self的写法(函数卸载里面)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
definorderTraversal(self, root: Optional[TreeNode]) -> List[int]: self.root = root # 不能少 res=[] defmid_order(root): ifnot root: return mid_order(root.left) res.append(root.val) mid_order(root.left) return res ans = mid_order(root) return ans #递归的修改位置即为对应的
栈模拟(前中后序通用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
definorderTraversal(self, root: Optional[TreeNode]) -> List[int]: res = [] stack = [] cur = root # 思路:cur用来定位叶子节点; # 中序是左 根 右 因此定位到左叶子直接弹出 cur指向right放在ans取值之后能保证根节点在右子树之前放入 while stack or cur: while cur: stack.append(cur) cur = cur.left cur = stack.pop() res.append(cur.val) cur = cur.right return res
这个模板的前序和后序直接给出:
1 2 3 4 5 6 7 8 9 10
# 前序,先放入val值 中左右 while stack or cur: while cur: res.append(cur.val) stack.append(cur) cur = cur.left cur = stack.pop() cur = cur.right return res
1 2 3 4 5 6 7 8 9
# 后序,先放入val值 左右中 -> 中右左 while stack or cur: while cur: res.append(cur.val) stack.append(cur) cur = cur.right cur = stack.pop() cur = cur.left return res[::-1]
# 栈模拟,这里的模板是bfs的思想 defmaxDepth(self, root: Optional[TreeNode]) -> int: ifnot root: return0 stack = [root] res = 0 while stack: temp = [] for node in stack: if node.left: temp.append(node.left) if node.right: temp.append(node.right) stack = temp # 换栈 res += 1 # res 添加的条件stack每进入循环一次(说明栈内始终有值) return res
defsortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]: defdfs(left,right): if left == right: returnNone#退出条件,l=r表示区间长度为0 m = (left + right) // 2 return TreeNode(nums[m],dfs(left,m),dfs(m+1,right))# val,left,right return dfs(0,len(nums))
43. 验证二叉搜索树
思路:二叉搜索树表示左边小于中间小于右边,递归调用,当满足dfs终止条件直接返回False
1 2 3 4 5
defisValidBST(self, root: Optional[TreeNode],left = -inf , right = inf) -> bool: if root == None: returnTrue#空树是BST mid = root.val return left < mid < right and isValidBST(root.left,left,mid) and isValidBST(root.right,mid,right)
44. 二叉搜索树的第K小元素
思路:
中序遍历,然后返回k-1位置的元素
dfs,往左走然后判断,走一次k-=1, 判断k 然后往右走
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
defkthSmallest(self, root: Optional[TreeNode], k: int) -> int: defmid_order(root): stack = [] ans = [] cur = root ifnot root: return while stack or cur:#用or while cur: stack.append(cur) cur = cur.next cur = stack.pop() ans.append(cur.val) cur = cur.next return ans return ans[k-1]
或者使用dfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defkthSmallest(self, root: Optional[TreeNode], k: int) -> int: defdfs(root): ifnot root: return if root.left: dfs(root.left) if k == 0: return k -= 1 if k == 0: self.ans = root.val dfs(root.right) self.k = k dfs(root) returnself.ans
45. 二叉树的右视图
很明显了,层序遍历的加强版,即只保留每一层的最右侧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defrightSideView(self, root: Optional[TreeNode]) -> List[int]: ifnot root: return q = deque() q.append(root) res = [] while q: #temp = [] 屏蔽掉是因为此题每一层只输出一个节点 for i inrange(len(q)): node = q.popleft() if i == len(q) - 1: res.append(node.val) if node.left: q.append(node.left) if node.right: q.append(node.right) return res
defhasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool: ifnot root: return path = [] res = [] defdfs(root,targetSum): ifnot root: return val = root.val path.append(val) targetSum -= val if targetSum == 0andnot root.left andnot root.right: res.append(path[:]) dfs(root.left,targetSum) dfs(root.right,targetSum) path.pop()#不能忘记出栈 dfs(root,targetSum) if res == []: returnFalse returnTrue
路径总和2:找出所有路径,从根到叶子节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
defpathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]: res = [] path = [] ifnot root: return defdfs(root,tar): ifnot root: return val = root.val path.append(val) tar -= val ifnot root.left andnot root.right and tar == 0: res.append(path[:]) dfs(root.left,tar) dfs(root.right,tar) dfs(root,targetSum) return res
路径总和3:(本题)不一定是到叶子,也不一定从根节点开始,存在即可
思路:因为没有叶子节点的限制,并且需要遍历所有节点:
考虑辅助函数,维护变量res(表示以root为根节点的的满足左右子树和为target的数量)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defpathSum(self, root: Optional[TreeNode], targetSum: int) -> int: defdfs(root,target): ifnot root: return res = 0 if root.val == target: res += 1 # 判断左右子树,因为当前节点肯定有本身,所以target要减去val res += dfs(root.left,target-root.val) res += dfs(root.right,target-root.val) return ret ifnot root: return0 res = dfs(root,targetSum) res += self.dfs(root.left,targetSum) res += self.dfs(root.right,targetSum) return res
49. 二叉树的最近公共祖先
分类讨论:首先递归判断pq属于左侧还是右侧,根据l和r的情况进行判断
1.如果l和r都没有pq(即l,r都是空),说明左右边都没有pq,返回空
2.如果l和r都不为空,说明两侧都有,此时返回根节点
如果出现在一侧,那么说明该侧根节点是祖先
1 2 3 4 5 6 7 8 9 10 11 12
deflowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': ifnot root or p == root or q== root: return root left = self.lowestCommonAncestor(root.left,p,q) right = self.lowestCommonAncestor(root.right,p,q) ifnot left: return right ifnot right: return left ifnot left andnot right: return root
defnumIslands(self, grid: List[List[str]]) -> int: m = len(grid) n = len(grid[0]) defdfs(x,y): # 一定要先写return 是dfs的base case(最小递归) if x < 0or x > m or y < 0or y > n or grid[x][y] == '0': return grid[x][y] = '0' dfs(x-1,y) dfs(x+1,y) dfs(x,y-1) dfs(x,y+1) count = 0 for i inrange(m): for j inrange(n): if grid[i][j] == '1': dfs(i,j) count+=1 return count
defcanFinish(numCourses, prerequisites): graph = {} for course,pre in prerequisites: if courese notin graph: graph[course] = [] graph[course] .append(pre) visted = set() defdfs(course): if course in visited:#课程前置有重复了 returnFalse#有环 visited.add(course) if course in graph: for i pre in graph[course]: ifnot dfs(pre): returnFalse visited.remove(course)#全部都能修完 returnTrue for course inrange(numCourses): ifnot dfs(course): returnFalse returnTrue
54. 前缀树
先不看了
回溯
55. 全排列
思路是取出每一个位置的元素,然后递归找其他位置的全排列之后拼接
1 2 3 4 5 6 7 8 9 10 11 12
defpermute(self, nums: List[int]) -> List[List[int]]: iflen(nums) == 1: return [nums] ans = [] for i inrange(len(nums)): cur = nums[i] rest = nums[:i]+nums[i+1:] all_rest = self.permute(rest) for j in all_rest: ans.append([cur] + j) return ans
56. 子集
返回所有可能的子集
这里有一种有趣的做法,按照二进制来 注意右移和与的使用
1 2 3 4 5 6 7 8 9 10 11
defsubsets(self, nums: List[int]) -> List[List[int]]: n = len(nums) ans = [] temp = [] for i inrange(2**n): for j inrange(n): if (i>>j)&1: #依次判断j位是不是1 temp.append(nums[j]) ans.append(temp) return ans
57. 电话号码的数字组合
使用队列(题目要求返回数字对应字母的全部组合)
思路:
1.将输入的数字所代表的每一个都入列;
2.将出队的和第二个数字代表的每一个元素组合后入队
……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# 2 3 # a b c # ad ae af bd be bf cd ce cf defletterCombinations(self, digits: str) -> List[str]: d ={"2":"abc","3":"def","4":"ghi","5":"jkl","6":"mno","7":"pqrs","8":"tuv","9":"wxyz"} ifnot digits: return [] q = [''] for digit in digits: for _ inrange(len(q)): temp = q.pop(0) for letter in d[int(digit)]: q.append(temp+letter) return q #
defgenerateParenthesis(self, n: int) -> List[str]: res = [] # bt,start序列,剩余的括号l数量,剩余的括号r数量 defbt(s,l,r): if l == 0: res.append(s+')'*r) return if l == r: # 剩余的左等于右,只能先左括号 gene(s+"(",l-1,r)#左括号少一个 if l!=r: gene(s+"(",l-1,r)#左括号少一个 gene(s+")",l,r-1)#右括号少一个 bt("",n,n) return res
defexist(self, board: List[List[str]], word: str) -> bool: m,n = len(board),len(board[0]) defdfs(i,j,k): #basecase ifnot0<= i < m ornot0 <= j <= n or board[i][j] != word[k]: returnFalse else: if k == len(word) -1: returnTrue #开始dfs board[i][j] = ''#已访问的标记 res = dfs(i-1,j,k+1) or dfs(i,j-1,k+1) or dfs(i+1,j,k+1) or dfs(i,j+1,k+1) board[i][j] = word[k]#恢复原来的 return res
for i inrange(m): for j inrange(n): if dfs(i,j,0): returnTrue returnTrue
61. 分割回文串
分割和回文子串的区别是,分割要求加能够恢复为原来的字符串;而回文子串则仅需要时子串就可以
这题和N皇后有点像的,借助judge判断回文,通过judge的结果选择是否添加结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defpartition(self, s: str) -> List[List[str]]: # 辅助函数 defjudge(s): # 判断s是不是回文 return s==s[::-1] ans = [] defbt(start,path): if start == len(s): ans.append(path[:]) return for end inrange(start,len(s)): if judge(s[start:end+1]): path.append(s[start:end+1) bt(end+1,path) path.pop()#撤销 bt(0,[]) return res
defsolveNQueens(self, n: int) -> List[List[str]]: t = [['.'for _ inrange(n)] for _ inrange(n)] # python不能修改字符串,在拼接时候可以''.join defisvaild(row,col): for i inrange(row): if chess[i] == col orabs(chess[i]-col) == row-1: returnFalse returnTrue #bt defbt(row): #base case if row==n: tmp = [] for i inrange(n): tmp.append(''.join(t[i])) res.append(tmp) return for col inrange(n): #放皇后 if isvaild(row,col): chess[i] = col t[row][col] = 'Q'#操作 bt(row+1) t[row][col] = '.'#撤销 result = [] chess = [-1] * n bt(0) return result
defsearchInsert(self, nums: List[int], target: int) -> int: n = len(nums) l,r = -1,n-1 while l+1 < r: m = (r+l) // 2 if nums[m] < target: l = m else: r = m return r
defsearchMatrix(self, matrix: List[List[int]], target: int) -> bool: m = len(matrix) n = len(matrix[0]) print(m,n) for line inrange(m): if matrix[line][n-1] == target: returnTrue elif matrix[line][n-1] < target: # 说明在不在本行,可能在下一行 continue else: # 左闭右开区间 l,r = 0,n while l < r: mid = (l+r) // 2 if matrix[line][mid] < target: l = mid + 1 else: r = mid if l < n and matrix[line][l] == target: # 判断是否找到目标值 returnTrue returnFalse
defsearchRange(self, nums: List[int], target: int) -> List[int]: l = searchLeft(nums) r = searchLeft(nums) iflen(nums) == 0or nums[0] > target or nums[-1] < target: return [-1,-1] defsearchLeft(nums): l,r = 0,len(nums)-1 while l <= r: m = (r + l)//2 if nums[m] == target: r = m-1 elif nums[m] > target: r = m -1 else: l = m+1 if nums[l] == target: return l return -1 defsearchRight(nums): l,r = 0,len(nums)-1 while l <= r: m = (r + l)//2 if nums[m] == target: l = m+1 elif nums[m] > target: r = m -1 else: l = m+1 if nums[r] == targetr return r return -1
defsearch(self, nums: List[int], target: int) -> int: #1. 旋转数组的最小值 deffindMin(self,nums:List[int]) -> int: n = len(nums) l,r = 0,n while l < r: m = (l + r) // 2 if nums[m] < nums[-1]: # 中间值小于最后值,最小值应该在前面 r = m else: # 中间值大于最后,一定在后面 l = m + 1 return l defpartseek(l,r,target): n = len(nums) l,r = 0,n while l < r: m = (l + r )//2 if nums[m] < target: l = m +1 else: r = m if nums[l] == target: return l else: return -1
# 2. 两个部分的二分 # [4,5,1,2,3] tar =5 minIndex = findMin(nums) if target > nums[-1]: return partseek(0,minIndex) else: return partseek(minIndex,n)
67. 寻找旋转排序数组的最小值
1 2 3 4 5 6 7 8 9 10 11 12 13
# 66中已经实现了 deffindMin(self,nums:List[int]) -> int: n = len(nums) l,r = 0,n while l < r: m = (l + r) // 2 if nums[m] < nums[-1]: # 中间值小于最后值,最小值应该在前面 r = m else: # 中间值大于最后,一定在后面 l = m + 1 return nums[l]
defisValid(self, s: str) -> bool: d = { '}':'{', ')':'(', ']':'[' } stack = [] for i in s: if i in'([{': stack.append(i) if i in'}])': # 如果是右括号,并且对应 if d[i] == stack[-1] ans stack: stack.pop() else: returnFalse ifnot stack: returnTrue returnFalse
defdecodeString(self, s: str) -> str: current_string = '' num = 0 ans = '' stack = [] for i in s: if i in'0123456789': num += num*10 + int(i) if i.isalpha(): current_string += i if i == '[': # 开始记录,先把之前的存进栈 stack.append((num,current_string)) char_set = '' num = 0 if i == ']': prev_num,prev_string = stack.pop() current_string = prev_num*prev_string + current_string return ans
72. 每日温度
给出下一天比这当天高的温度
思路:用栈保存最高温度出现的下标,返回下标减去当天的天数
反向遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defdailyTemperatures(self, temperatures: List[int]) -> List[int]: n = len(temperatures) stack = [] ans = [0] * n # 这不是dp for i inrange(n-1,-1,-1): t = temperatures[i] while stack and stack[-1] <= t: stack.pop() if stack: ans[i] = st[-1] - i stack.append(i)
return ans
如果是正向遍历呢:
每天入栈,当找到一个比栈顶元素大的就出栈,并求出当天i-出栈的那天j作为值返回给ans[j]
1 2 3 4 5 6 7 8 9 10 11 12
ans = [0] * n stack = [] for i inrange(n): t = temperatures[i] while stack and t > stack[-1]: j = stack.pop() ans[j] = i - j # i是当天,j是刚比他小的天 stack.append(i) return ans
73. 柱状图中最大的矩形
堆
74. 数组中的第K大元素
返回数组的第K大(而不是第K个不同)的元素
[3,2,3,1,2,4,5,5,6] -> k = 4 输出为4(而不是3,输出3是第K个不同的元素)
1 2 3 4 5 6 7 8 9 10
# 思路1:使用heapq建堆;python默认建堆为最小堆,所以取反 # 因为要弹元素,最小堆弹的是小值,所以取反之后弹最小实际上就是弹最大 # 弹出前K-1个 deffindKthLargest(self, nums: List[int], k: int) -> int: heap = [-x for x in nums] # 建堆语法 heapq.heapify(heap)#堆化,目前堆的首部是最小值 for i inrange(k-1): heapq.heappop(heap) return (-heap[0])
# 考虑到题目实际指的是排序后倒数第K个下标的元素,可以使用改进的快速排序 defquickselect(arr,start,end,k): if start==end: return arr[start] target = arr[start] left = start right = end while left < right: # 从后往前找比target小的 while left < right and nums[right] >= target: right -= 1 nums[left] = nums[right] while left < right and nums[left] < target: left += 1 nums[right] = nums[left] nums[left] = target rank = left - start + 1# rank表示当前划分的数是第几大的数 if rank == k: # 如果正好是第K大,返回它 return arr[left] elif rank > k: # 如果第K大元素在左边部分 return quickselect(arr, start, left - 1, k) else: # 如果第K大元素在右边部分 return quickselect(arr, left + 1, end, k - rank) #if left == n-k: # return nums[left] #quicksort(arr,start,left) #quicksort(arr,left+1,end) # 上述是正常的快排,每次target能正常找到位置;后续两次划分的quicksort递归两个区域; # 改进之后只需要递归一片区域
75. 前K个高频元素
Counter/Dict统计,改进快排
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
deftopKFrequent(self, nums: List[int], k: int) -> List[int]: # 标准字典统计解法 d = {} for i in nums: if i notin d: d[i] = 1 else: d[i] += 1 # m = max(d.values()) n = [v for _,v in d.items()]
s = sorted(n,reverse = True) print(s) res = [] for i,v in d.items(): if v in s[:k]: res.append(i) return res # counter:
# 方法1 defmaxProfit(self, prices: List[int]) -> int: temp = prices[0] ans = 0 for i in prices[1:]: if i < temp: temp = i ans = max(ans,i-temp) return ans
defpartitionLabels(self, s: str) -> List[int]: last_char = {c:i for i,c inenumerate(s)} start = end = 0 ans = [] for i,c inenumerate(s): end = max(end,last_char[c]) if end == i: ans.append(end-start+1) start = i+1 return ans
defcoinChange(self, coins: List[int], amount: int) -> int: dp = [0] + [amount+1] * len(coins) for coin in coins: for j inrange(coin,amount+1): dp[j] = min(dp[j],dp[j-coin]+1) return dp[-1]
86. 单词拆分
思路:遍历字符,如果遍历到某个位置在字典中存在,那么说明到这个位置之前的字符是能够被表示的
细节思路:两层循环(约等于双指针),外层i和内层j,判断i:j是否在字典中
1 2 3 4 5 6 7 8 9 10
defwordBreak(self, s: str, wordDict: List[str]) -> bool: n = len(s) dp = [False] * (n+1) dp[0] = True for i inrange(n): for j inrange(i+1,n+1): if dp[i] and s[i:j] in wordDict: dp[j] = True return dp[-1]
其实还有一个思路是,把所有在字典里的字符都替换成空,看最后是不是空字符串
1 2 3 4 5
for i in wordDict: if i in s: s = s.replace(i,"")
returnTrueif (s=="") elseFalse
87. 最长递增子序列
给定整数数组nums,找到最长严格递增子序列的长度
字面意思:如果相邻的两个数后者大于前者,即为严格递增
dp[i] 表示到达i位置时,(以num[i]) 结尾的最长严格递增序列长度
通过对i位置的二重循环进行判断:
外层为当前元素i
内层 j 为小于i的所有位置
当内层 j 一直发现前方的小于 i 时,i 位置的 dp 值设置为长度(通过dp控制)
其中dp i 应该为 dp[i] 和以j结尾的dp值+1的较大者
1 2 3 4 5 6 7 8
deflengthOfLIS(self, nums: List[int]) -> int: n = len(nums) dp = [1] * (n) for i inrange(n): for j inrange(i): if nums[j] < nums[i]: dp[i] = max(dp[i],dp[j]+1) returnmax(dp)
defcanPartition(self, nums: List[int]) -> bool: target = sum(nums)//2 ifsum(nums)%2 == 1orlen(nums)<2: returnFalse dp = [True] + [False] * target for i inrange(len(nums)): for j inrange(target,nums[i]-1,-1): dp[j] = dp[j] or dp[j-nums[i]]#dp j加上nums[i]正好是target return dp[target]
90. 最长有效括号
思路:栈匹配括号+匹配成功的标记,然后计算最长1的长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
deflongestValidParentheses(self, s: str) -> int: stack = [] temp = ['0'] * len(s) for i,c inenumerate(s): if c == '(': stack.append(i) else: if stack: j = stack.pop() temp[i],temp[j] = '1','1' print(temp) t = ''.join(temp) split1 = t.split("0") len_max = 0 for i in split1: iflen(i) > len_max: len_max = len(i) return (len_max)
多维DP
这类题有特点 1. 有状态转移 2. 或者是匹配两个不同/相同长度的某个位置
比较经典的是LCS
91. 不同路径
机器人只能往下或者往右;
返回到右下角一共有多少条不同的路径
比较经典的二维dp,因为边界处只能有一个方向,所以j = 0 和 i = 0的地方要设置为1
其他区域的取值应该来源于来自上方和来自左边的值
1 2 3 4 5 6 7 8 9 10
defuniquePaths(self, m: int, n: int) -> int: res = [[0] * n for _ inrange(m)] for i inrange(m): res[i][0] = 1 for j inrange(n): res[0][j] = 1 for i inrange(1,m): for j inrange(1,n): res[i][j] = res[i-1][j] + res[i][j-1] return (res[-1][-1])
92. 最小路径和
给定一个网格区域,找出从左上到右下的路径使得路径上的和最小
(只能向下或者向右)
明确的是,这题应该是动态规划(不同路径)然后加一个路径和的限制
所以基本上还是dp的老套路
dp [i] [j]表示到达 grid[i] [j] 的最小路径和
dp [i] [j] 来源于上方或者左边的值
边界只能来源于一侧
路径和的变化和不同路径的变化主要在于dp[i] [j] 存的内容不一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defminPathSum(self, grid: List[List[int]]) -> int: m,n = len(grid),len(grid[0]) dp = [[0]*n for i inrange(m)] dp[0][0] = grid[0][0] for i inrange(1,n): #对于每一列,x=0时候只能来源于上侧 dp[0][i] = dp[0][i-1] + grid[0][i] for j inrange(1,m): #对于每一行,j=0时候只能来源于上册 dp[j][0] = dp[j-1][0] + grid[j][0] for i inrange(m): for j inrange(n): grid[i][j] = min(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j]) return dp[-1][-1]
93. 最长回文子串
沿用判断子串的方法,找到所有回文子串返回最长的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
deflongestPalindrome(self, s: str) -> str: defjudge(l,r,strs): while l>=0and r<len(strs) and strs[l] == strs[r]:#大于等于! r += 1 l -= 1 return strs[l+1:r] ans = [] iflen(s)==1: return s for i inrange(len(s)): single = judge(i,i,s) multi = judge(i,i+1,s) ans.append(single) ans.append(multi) a = sorted(ans,key=lambda i :len(i)) return (a[-1])
94. 最长公共子序列
思路:动态规划(二维DP)
设置dp[i] [j] 为 text1 匹配到 text2的最长长度,这里分为两种情况
对于遍历到的text1[i]和text2[j]:
相等,此时将前一个位置的长度+1(即i-1,j-1)
不相等,此时应该选择1或者2中前一个位置和当前位置的最大值
1 2 3 4 5 6 7 8 9 10 11 12
deflongestCommonSubsequence(self, text1: str, text2: str) -> int: # 注意边界text1和text2的0不会匹配任何公共子序列,所以dp[0][:]和dp[:][0]均等于0 m,n = len(text1),len(text2) dp = [[0] * (n+1) for _ inrange(m+1)] # 初值可以不用额外在定义了,这个赋值已经包括了 for i inrange(m): for j inrange(n): if text1[i] == text2[j]: dp[i][j] == dp[i-1][j-1]+1 else: dp[i][j] = max(dp[i-1][j],dp[i][j-1]) return dp[-1][-1]
defminDistance(self, word1: str, word2: str) -> int: ifnot word1 ornot word2: returnmax(len(word1), len(word2)) m ,n = len(word1),len(word2) dp = [[0]*(n+1) for _ inrange(m+1)] for i inrange(m): dp[i][0] = i for j inrange(n): dp[0][j] = j for i inrange(m): for j inrange(n): if word1[i] == word[j]: dp[i][j] = dp[i-1][j-1] else: delete1 = dp[i][j-1] +1 add1 = dp[i-1][j] +1 change1 = dp[i-1][j-1] +1 dp[i][j] = min(delete1,add1,change1) return dp[-1][-1]
技巧
96. 只出现一次的数字
除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素
按位异或,相同的异或为0,0异或等于数等于数本身(1异或数等于数取反)
1 2 3 4 5
defsingleNumber(self, nums: List[int]) -> int: ans = nums[0] for i inrange(1,len(nums)): ans ^= nums[i] return (ans)
97. 多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于⌊ n/2 ⌋ 的元素
思路:1. 快排返回中间值 2. 函数调用排序返回中间值 3. 摩尔投票
1 2 3 4 5 6 7 8 9 10 11 12
# 主要写摩尔投票 defmajorityElement(self, nums: List[int]) -> int: # 思路就是,vote = 0时候就让当前数为众数;如果和众数相同就vote+1;否则-1;选举新的众数 vote = 0 for i in nums: if vote == 0: mass = i if i == mass: vote +=1 else: vote -=1 return mass
defnextPermutation(self, nums: List[int]) -> None: n = len(nums) index = n -2# 最后一个是n-1,因为保证相邻两个数的向右(i,i+1)不越界 while i >=0and nums[i] >= nums[i+1]: #找小 i -= 1 if i >= 0: j = n-1 while j < n and nums[j] <= nums[i]:#找大 j -= 1 nums[j],nums[i] = nums[i],nums[j] #nums = nums[i] + nums[i+1:][::-1] l,r = i+1,n-1 while l<r: nums[l],nums[r] = nums[r],nums[l] l+=1 r-=1
# 如果不限制,那么可以排序寻找连续相同值;可以一次遍历输出计数为2的值 # 限制nums不修改,并且额外空间等于O(1) # 改进二分查找,鸽巢原理:l到m中如果有多余半数的元素,说明多余的元素在该区间 deffindDuplicate(self, nums: List[int]) -> int: l,r = 1,len(nums) while l<r: m = (l+r)//2: count = sum( left <= num <= m for num in nums) # nums内落在l,m的个数 if count>m-l+1: r = m # 这里包含m,因为重复的元素可能是m else: l = m+1 return l