classSolution: deflongestConsecutive(self, nums: List[int]) -> int: res = 0 st = set(nums) # 使用哈希集合,从而使查找为O(1) # 这里不能遍历nums,否则会多次处理相等的元素 for num in st: if num - 1in st: # 从最小的开始 continue last = num + 1 while last in st: last = last + 1 res = max(res, last - num) return res
双指针
盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明:你不能倾斜容器。
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: defmaxArea(self, height: List[int]) -> int: left, right = 0, len(height) - 1 res = 0 while left != right: h = min(height[left], height[right]) w = right - left res = max(res, h * w) if height[left] < height[right]: left += 1 else: right -= 1 return res
三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。
classSolution: defthreeSum(self, nums: List[int]) -> List[List[int]]: res = [] nums.sort() for i, num inenumerate(nums): if num > 0: # 剪枝 break if i > 0and nums[i] == nums[i - 1]: # 去重 continue # 双指针收缩 j = i + 1 k = len(nums) - 1 while j < k: sum = nums[i] + nums[j] + nums[k] ifsum > 0: k -= 1 elifsum < 0: j += 1 else: res.append([nums[i], nums[j], nums[k]]) # 去重 while j < k and nums[j] == nums[j + 1]: j += 1 while j < k and nums[k] == nums[k - 1]: k -= 1 j += 1 k -= 1 return res
classSolution: deftrap(self, height: List[int]) -> int: n = len(height) if n <= 2: return0 # 使用单调栈记录索引,从栈顶到栈底单调递增,从而找到凹槽 stack = [] stack.append(0) res = 0 for i inrange(1, n): top = stack[-1] while stack and height[top] < height[i]: mid = stack.pop() if stack: left = stack[-1] h = min(height[left], height[i]) - height[mid] w = i - left - 1 res += h * w top = stack[-1] stack.append(i) return res
滑动窗口
无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution: deflengthOfLongestSubstring(self, s: str) -> int: slow = fast = 0 st = set() res = 0 while fast != len(s): if s[fast] in st: # 出现重复元素,将重复元素及其之前的元素全部清除 while s[slow] != s[fast]: st.remove(s[slow]) slow += 1 slow += 1# 移动到重复元素的下一位 fast += 1 else: st.add(s[fast]) fast += 1 res = max(res, fast - slow) return res
找到字符串中所有字母异位词
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: deffindAnagrams(self, s: str, p: str) -> List[int]: counter = Counter(p) # 统计p中元素出现的频率 res = [] left = 0 for right, c inenumerate(s): counter[c] -= 1# s中出现就减一,减到零说明找到 while counter[c] < 0: # c出现过多 counter[s[left]] += 1 left += 1 if right - left + 1 == len(p): # 没有超出的了,说明s子串与p构成异位词 res.append(left) return res
子串
和为 K 的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。
1 2 3 4 5 6 7 8 9 10 11
classSolution: defsubarraySum(self, nums: List[int], k: int) -> int: # 哈希表键为前缀和,值为该前缀和的个数,遍历查找s[j] - s[i] = k,即统计有多少以j结尾的子数组 res = sum = 0 cnt = defaultdict(int) cnt[0] = 1 for num in nums: sum += num # 统计当前前缀和,即s[j] res += cnt[sum - k] # 寻找所有的s[i] cnt[sum] += 1 return res
滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。
classSolution: defminWindow(self, s: str, t: str) -> str: cnt = defaultdict(int) for c in t: cnt[c] += 1 less = len(cnt) # 记录子串中有多少字母出现次数达不到t中的出现次数
resLeft, resRight = -1, len(s) left = 0 for right, c inenumerate(s): cnt[c] -= 1# 右端点进入子串 if cnt[c] == 0: less -= 1# 达到了 while less == 0: # 已全部达到,进一步缩小 if right - left < resRight - resLeft: resLeft, resRight = left, right x = s[left] if cnt[x] == 0: less += 1 cnt[x] += 1# 左端点移出子串 left += 1 return""if resLeft < 0else s[resLeft : resRight + 1]
classSolution: defproductExceptSelf(self, nums: List[int]) -> List[int]: res = [1] * len(nums) # 先计算后面数字的乘积 for i inrange(len(nums) - 2, -1, -1): res[i] = res[i + 1] * nums[i + 1] # 再乘上前面数字的乘积 pre = 1 for i, x inenumerate(nums): res[i] *= pre pre *= x return res
classSolution: deforangesRotting(self, grid: List[List[int]]) -> int: # bfs,使用队列记录新的腐烂的橘子 fresh = 0# 记录是否将所有橘子腐烂 res = 0 m, n = len(grid), len(grid[0]) q = [] for i inrange(m): for j inrange(n): if grid[i][j] == 1: fresh += 1 elif grid[i][j] == 2: q.append((i, j)) while q and fresh: res += 1 tmp = q q = [] for i, j in tmp: for x, y in (i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1): if0 <= x < m and0 <= y < n and grid[x][y] == 1: grid[x][y] = 2 fresh -= 1 q.append((x, y)) return -1if fresh != 0else res
classSolution: defcanFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: # 三色标记法,0表示未访问,1表示正在访问,2表示已经访问 graph = [[] for _ inrange(numCourses)] for a, b in prerequisites: graph[b].append(a) # 使用链表表示该图
deffind(self, s: str) -> int: # 0表示未找到,1表示只找到前缀,2表示找到完整的字符串 cur = self.root for c in s: ifnot c in cur.son: return0 cur = cur.son[c] return2if cur.end else1
# Your Trie object will be instantiated and called as such: # obj = Trie() # obj.insert(word) # param_2 = obj.search(word) # param_3 = obj.startsWith(prefix)
# 使用balance记录左括号数与右括号数之差,index则为上一个左括号的索引 defbacktracking(index: int, balance: int) -> None: iflen(path) == n: ans = [')'] * 2 * n for i in path: ans[i] = '(' res.append(''.join(ans)) return # 每次加入b个右括号,再加入一个左括号 for b inrange(balance + 1): path.append(index + b + 1) backtracking(index + b + 1, balance - b + 1) path.pop()
backtracking(-1, 0) return res
单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
defcheck(prefix: str) -> bool: for i inrange(len(prefix) // 2): if prefix[i] != prefix[len(prefix) - i - 1]: returnFalse returnTrue
defbacktracking(index: int) -> None: if index == len(s): res.append(path[:]) return
prefix = [] for i inrange(index, len(s)): # 构造以index开始,i结束的回文 prefix.append(s[i]) if check(prefix): path.append(''.join(prefix)) backtracking(i + 1) path.pop()
backtracking(0) return res
N皇后
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
classSolution: defsolveNQueens(self, n: int) -> List[List[str]]: res = [] board = [-1] * n # 记录皇后在每一行的第几列 # 都是从上到下的顺序 usedCol = [False] * n used45 = [False] * (2 * n - 1) used135 = [False] * (2 * n - 1)
defbacktracking(row: int) -> None: if row == n: path = [] for i inrange(n): s = ['.'] * n s[board[i]] = 'Q' path.append(''.join(s)) res.append(path[:])
for i inrange(n): if usedCol[i] or used45[i + row] or used135[n - 1 + row - i]: continue board[row] = i usedCol[i] = True used45[i + row] = True used135[n - 1 + row - i] = True backtracking(row + 1) used135[n - 1 + row - i] = False used45[i + row] = False usedCol[i] = False
# Your MinStack object will be instantiated and called as such: # obj = MinStack() # obj.push(val) # obj.pop() # param_3 = obj.top() # param_4 = obj.getMin()
字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 测试用例保证输出的长度不会超过 105。
classSolution: defdecodeString(self, s: str) -> str: # 认为每出现一组[]陷入一次递归,使用栈来模拟 res = "" k = 0# 记录[]前的那个数字 stack = []
for c in s: if c.isalpha(): res += c elif c.isdigit(): k = k * 10 + int(c) elif c == '[': # 之前的结果入栈,防止丢失 stack.append((res, k)) res = "" k = 0# 因此在else处不用k = 0,因为最深的[]没有数字出现 else: # 递归结束,出栈进行拼接、 pre_res, pre_k = stack.pop() res = pre_res + res * pre_k
return res
柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classSolution: deflargestRectangleArea(self, heights: List[int]) -> int: # 该单调栈从栈顶到栈底单调递减 heights.append(-1) # 从而保证到最后时,栈中元素都出来进行计算 stack = [-1] # 保证栈空时入栈 res = 0 for right inrange(len(heights)): # left和right之间的所有柱子中,mid是最矮的那个,故高计算正确 # 因为mid左边的柱子中,第一个比它矮的依然在栈中,即left # 而mid右边如果有比它矮的柱子,则mid会提前出栈 # 而left和right都比mid矮,因此宽是最大的 while heights[stack[-1]] > heights[right]: mid = stack.pop() left = stack[-1] h = heights[mid] w = right - left - 1 res = max(res, h * w) stack.append(right) return res
堆
数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
classSolution: deffindKthLargest(self, nums: List[int], k: int) -> int: defquick_select(nums, k) -> int: pivot = random.choice(nums) # 随机选择基准数 big, equal, small = [], [], [] for num in nums: if num > pivot: big.append(num) elif num < pivot: small.append(num) else: equal.append(num) if k <= len(big): return quick_select(big, k) eliflen(nums) - len(small) < k: return quick_select(small, k - len(nums) + len(small)) else: return pivot return quick_select(nums, k)
前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
给定一个长度为 n 的 0 索引整数数组 nums。初始位置在下标 0。 每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在索引 i 处,你可以跳转到任意 (i + j) 处: 0 <= j <= nums[i] 且 i + j < n 返回到达 n - 1 的最小跳跃次数。测试用例保证可以到达 n - 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution: defjump(self, nums: List[int]) -> int: # 当触及到当前跳跃极限时进行跳跃,并更新覆盖范围 # 这次跳跃本质上是跳到了能触及下次跳跃极限的位置 # 这样是正确的,因为迭代器局限在当前覆盖范围之内 # nextJump表示的是在[0, curJump]这个区间里,所能达到的最远距离 # 而curJump反映的是上一次所能达到的最远距离 # 当遍历到curJump时,说明必须跳一次才能到达更远的距离了,这次跳的距离不确定 curJump, nextJump, res, i = 0, 0, 0, 0 while i <= curJump and curJump < len(nums) - 1: nextJump = max(nextJump, i + nums[i]) if i == curJump: curJump = nextJump res += 1 i += 1 return res
划分字母区间
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 “ababcc” 能够被分为 [“abab”, “cc”],但类似 [“aba”, “bcc”] 或 [“ab”, “ab”, “cc”] 的划分是非法的。 注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。 返回一个表示每个字符串片段的长度的列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classSolution: defpartitionLabels(self, s: str) -> List[int]: # 类似于跳跃游戏,遍历字母,找到它们最后出现在哪里,当触及到边缘时,分割出一个区间 further = [0] * 26 # 记录最后一次出现在哪里 for i, c inenumerate(s): further[ord(c) - 97] = i most,last = 0, -1 res = [] for i, c inenumerate(s): most = max(most, further[ord(c) - 97]) if i == most: res.append(most - last) last = most return res
动态规划
完全平方数
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
1 2 3 4 5 6 7 8 9 10 11 12
classSolution: defnumSquares(self, n: int) -> int: dp = [inf] * (n + 1) dp[0] = 0 i = 1 while i * i <= n: j = i * i while j <= n: dp[j] = min(dp[j], dp[j - i * i] + 1) j += 1 i += 1 return dp[n]
单词拆分
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
1 2 3 4 5 6 7 8 9
classSolution: defwordBreak(self, s: str, wordDict: List[str]) -> bool: dp = [False] * (len(s) + 1) dp[0] = True for i inrange(len(s) + 1): for word in wordDict: if s[i - len(word): i] == word and dp[i - len(word)]: dp[i] = True return dp[len(s)]
classSolution: deflengthOfLIS(self, nums: List[int]) -> int: # 这里的tails用于记录长度为i + 1的递增子序列的最后一个元素 tails, res = [0] * len(nums), 0 for num in nums: # 二分查找到可以插入新的元素的递增子序列 i, j = 0, res while i < j: m = (i + j) // 2 if tails[m] < num: i = m + 1 else: j = m tails[i] = num if j == res: res += 1 return res
for i inrange(2 * n - 1): # 包揽奇数回文串和偶数回文串 l, r = i // 2, (i + 1) // 2 while l >= 0and r < n and s[l] == s[r]: l -= 1 r += 1 # 结束时,s[l + 1]到s[r - 1]为回文串 if r - l - 1 > res_right - res_left: res_left, res_right = l + 1, r # 左闭右开 return s[res_left: res_right]
# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next classSolution: defreverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]: n = 0# 获取长度 cur = head while cur: cur = cur.next n += 1 prev, cur = None, head dum = ListNode(-1, head) start = dum while n >= k: n -= k # k个一组进行链表反转 for _ inrange(k): cur.next, cur, prev = prev, cur.next, cur # start为反转链表的前一节点,cur此时落在下一个反转链表的开头,prev为本次反转链表的开头 start.next.next, start.next, start = cur, prev, start.next return dum.next
随机链表的复制
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。 例如,如果原链表中有 X 和 Y 两个节点,其中 X.random –> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random –> y 。 返回复制链表的头节点。 用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示: val:一个表示 Node.val 的整数。 random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。 你的代码 只 接受原链表的头节点 head 作为传入参数。
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right classSolution: deflevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: res = [] ifnot root: return res q = deque() q.append(root) # 使用队列辅助 while q: path = [] for _ inrange(len(q)): # 当前层的节点数 node = q.popleft() path.append(node.val) if node.left: q.append(node.left) if node.right: q.append(node.right) res.append(path) return res
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right classSolution: head = None
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right classSolution: defbuildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: # 使用哈希表预处理中序遍历,便于快速查找位置,节约时间 index = {x: i for i, x inenumerate(inorder)}
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None
classSolution: deflowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': # 后序遍历自底向上 # 只返回p, q, None, 或者它们共同的祖先 ifnot root or root == p or root == q: return root left, right = self.lowestCommonAncestor(root.left, p, q), self.lowestCommonAncestor(root.right, p, q) ifnot left andnot right: # 都为空,说明p,q都没出现 returnNone if left andnot right: # 其中一个不为空,说明已经找到或者只找到一个 return left ifnot left and right: return right if left and right: # 左右各一个,说明当前节点为公共祖先 return root
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right classSolution: defmaxPathSum(self, root: Optional[TreeNode]) -> int: # 同样是只算链的和 res = -inf
classSolution: defmajorityElement(self, nums: List[int]) -> int: # 使用擂台比较,考虑到严格众数比其他数加起来都多,它一定会留到最后 res = hp = 0 for x in nums: # 每次hp = 0将该数字作为擂主,直到出现次数抵消,开启新擂主 if hp == 0: res, hp = x, 1 else: hp += 1if x == res else -1 return res
classSolution: defnextPermutation(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ n = len(nums) # step1: 从右向左找到第一个小于右侧相邻数字的数x,此时x右边单调递减 i = n - 2 while i >= 0and nums[i] >= nums[i + 1]: i -= 1
# step2: 找到x右边最小的大于x的数字y,进行交换,此时单调递减不变 # 注意如果step1没找到,说明已经是最大排列 if i >= 0: j = n - 1 while nums[j] <= nums[i]: j -= 1 nums[i], nums[j] = nums[j], nums[i]
# step3: 反转y右边的数,变成最小排列 left, right = i + 1, n - 1 while left < right: nums[left], nums[right] = nums[right], nums[left] left += 1 right -= 1
classSolution: defsearch(self, nums: List[int], target: int) -> int: # 通过比较x与nums[-1]来判断x在第一段还是第二段 # check函数用以判断target是否在mid的左边(或者相等) defcheck(i: int) -> bool: x = nums[i] if x > nums[-1]: # 在第一段,此时target也必须在第一段 return target > nums[-1] and x >= target else: return target > nums[-1] or x >= target
left, right = 0, len(nums) - 1 while left < right: mid = (left + right) // 2 if check(mid): right = mid else: left = mid + 1 return right if nums[right] == target else -1
寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (m+n)) 。