1. 遇到的题
69. x 的平方根
【快手日常一面,腾讯暑期二面】

class Solution:
def mySqrt(self, x: int) -> int:
l = 0
r = x
ans = -1
while l <= r:
mid = l + r >> 1
if mid * mid <= x:
ans = mid
l = mid + 1
else:
r = mid - 1
return ans
152.乘积最大子数组
【美团暑期一面】

class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
f_min = [0] * n # f_min[i], 从nums 0~i 能取到的最小乘积
f_max = [0] * n # f_max[i], 从nums 0~i 能取到的最大乘积
f_min[0] = f_max[0] = nums[0]
for i in range(1, n):
x = nums[i]
f_max[i] = max(x, f_max[i-1] * x, f_min[i-1] * x)
f_min[i] = min(x, f_max[i-1] * x, f_min[i-1] * x)
return max(f_max)
5. 最长回文子串
【阿里国际暑期一面】

class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
left, right = 0, 0
# 奇数回文串
for i in range(n):
l, r = i, i
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
if l + 1 <= r - 1: # 循环终止前的状态
if r - l - 2 > right - left: # 长度大于历史长度
left, right = l + 1, r - 1
# 偶数回文串
for i in range(n-1):
l, r = i, i + 1
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
if l + 1 <= r -1:
if r - l - 2 > right - left:
left, right = l + 1, r - 1
return s[left:right+1]
6.Z字变换
【字节暑期一面】

- 顺序遍历字符串,然后把字符串存到对应层中
- 第一次,按
0 -> numRows - 1的顺序 - 遇到 0 或 numRows - 1 需要翻转,
numRows - 1 -> 1 - 最终存储的顺序为:0,1,2, 3, 2, 1, 0, 1, 2, 3
- 保证
i的变换顺序是这样子
- 保证
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows < 2:
return s
res = ["" for i in range(numRows)]
i = 0
flag = -1
for c in s:
res[i] += c
if i == 0 or i == numRows - 1:
flag = -flag
i += flag
return "".join(res)
17.09. 第 k 个数
【字节暑期一面】


维护一个堆,每次取出最小的元素来乘 3,5,7 获取新元素加到堆中
import heapq
class Solution:
def getKthMagicNumber(self, k: int) -> int:
primes = [3, 5, 7]
arr = [1]
seen = {1}
for _ in range(k):
# 取出堆顶(最小堆)
cur = heapq.heappop(arr)
for p in primes:
num = p * cur
if num not in seen:
seen.add(num)
heapq.heappush(arr, num)
return cur
20. 有效的括号
【阿里云暑期一面】

d = {')':'(', ']':'[', '}':'{'}
stack = []
for i in s:
# 遇到左括号,入栈
if i in '([{':
stack.append(i)
# 遇到右括号,出栈左括号,看是否有匹配的左括号
else:
if not stack: # 栈空的时候遇到右括号,则一定不能匹配
return False
if d[i] == stack[-1]: # 右括号与前面的左括号匹配
stack.pop()
else:
return False
if not stack: # 栈为空, 说明全部都匹配成功
return True
return False
91. 解码方法
【快手x-star 一面】
a,b,c,d……z,对应 1,2,3,4……26。求:输入一串数字能有几种组合数?
f[i]表示0~i个字符解码的组合数- 对于
s[i],我们只关心:(1)能否独立解码(2)能否和s[i-1]一起解码 - 于是就存在有三种情况
s[i]只能单独解码为 a ∈ \in ∈ [1, 9],f[i] = f[i-1]s[i]只能和 i-1 组合起来解码为 b ∈ \in ∈ [10, 26],f[i] = f[i-2]s[i]可以单独解码也可以组合解码,f[i] = f[i-1] + f[i-2]

- 最后初始条件:
f = [1] + [0] * n
class Solution:
def numDecodings(self, s: str) -> int:
n = len(s)
f = [1] + [0] * n
for i in range(n):
# 1.可以单独解码
if 1 <= int(s[i]) <= 9:
f[i+1] += f[i]
# 2.如果大于两个数 且 可以和 i-1 组合解码
if i >= 1 and 10 <= int(s[i-1:i+1]) <= 26:
f[i+1] += f[i-1]
return f[n]
2. 高频题
215. 数组中的第K个最大元素
要求 O(n),使用快排
设 N 为数组长度。根据快速排序原理,如果某次哨兵划分后,基准数的索引正好是 N−k ,则意味着它就是第 k 大的数字

# 已经确定的 topn 的元素数少于k个, 说明第 k 大的元素在 small 区间中
if len(big) + len(equal) < k:
# 前 len(big) + len(equal) 大的元素已经确定了
# 在 small 区间只需要确定 k - (len(big) + len(equal))
return quick_sort(small, k - (len(big) + len(equal)))
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
def quick_sort(arr, k):
# 随机选择基准
privot = random.choice(arr)
big, equal, small = [], [], [] # >, =, < 基准的元素
for x in arr:
if x > privot:
big.append(x)
elif x < privot:
small.append(x)
else:
equal.append(x)
# 说明第 k 大的元素在 big 区间中
if k <= len(big):
return quick_sort(big, k)
# 已经确定的最大的元素数少于k个, 说明第 k 大的元素在 small 区间中
if len(big) + len(equal) < k:
# 前 len(big) + len(equal) 大的元素已经确定了
# 在 small 区间只需要确定 k - (len(big) + len(equal))
return quick_sort(small, k - (len(big) + len(equal)))
# 如果不在 big 也不在 small, 那就在 equal
return privot
return quick_sort(nums, k)
求最小的k个数
import random
from typing import List
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
def quick_select(arr, k):
if k == 0 or not arr:
return []
pivot = random.choice(arr)
less, equal, greater = [], [], []
for num in arr:
if num < pivot:
less.append(num)
elif num > pivot:
greater.append(num)
else:
equal.append(num)
if k <= len(less):
return quick_select(less, k)
elif k <= len(less) + len(equal):
return less + equal[:k - len(less)]
else:
return less + equal + quick_select(greater, k - len(less) - len(equal))
result = quick_select(arr, k)
return result
15. 三数之和
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 给定 i, 然后[j, k]在剩余区间求 两数之和
# 且不包含重复三元组
nums = sorted(nums)
ans = []
n = len(nums)
for i in range(n - 2):
if i > 0 and nums[i] == nums[i-1]:
continue
target = -nums[i]
j = i + 1
k = n - 1
while j < k:
if nums[j] + nums[k] < target:
j += 1
elif nums[j] + nums[k] > target:
k -= 1
else:
ans.append([nums[i], nums[j], nums[k]])
j += 1
k -= 1
while j < k and nums[j] == nums[j-1]:
j += 1
while j < k and nums[k] == nums[k+1]:
k -= 1
return ans
2.1 二叉树
236. 二叉树的最近公共祖先
- none, p, q 的时候都 return 自身
- 只在单边, 就只返回单边的结果
- 在两边都有, 则返回当前结点
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root or root is p or root is q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root
if left:
return left
if right:
return right
124. 二叉树中的最大路径和
前置题目:543. 二叉树的直径
- 二叉树的直径:任意两个节点之间最长路径的长度
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
# 在求最大深度的时候更新答案
global ans
ans = 0
def dfs(node):
if not node:
# 保证叶子节点的直径是 0 (左右空节点都是 -1, 再+2 = 0)
return -1
left = dfs(node.left) # 左侧的最大深度
right = dfs(node.right) # 右侧的最大深度
global ans
ans = max(ans, left + right + 2) # +2 是左右节点到当前节点的距离
return max(left, right) + 1
dfs(root)
return ans
- 左边最大路径和 + 右边最大路径和 + 当前拐点的值
- 路径和可能是负数,如果是负数则取 0,表示不走这段
- 相当于是把求最大直径改为求最大和,并在求最大和的过程中更新答案
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
global ans
ans = -inf
def dfs(node):
if not node:
return 0
left = max(0, dfs(node.left))
right = max(0, dfs(node.right))
global ans
ans = max(ans, left + right + node.val)
return max(left, right) + node.val
dfs(root)
return ans
102. 二叉树的层序遍历
要判断特殊情况,root 为 None 时
from collections import deque
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
ans = []
q = deque()
q.append(root)
while q:
cur = len(q) # 本层的节点数
val = []
for _ in range(cur):
node = q.popleft()
val.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
ans.append(val)
return ans
2.2 贪心
121. 买卖股票的最佳时机
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 记录之前的最低股价
# 使用第 i 天之前的最低股价来计算第 i 天的最大利润
min_price = 1e5
ans = 0
for x in prices:
ans = max(ans, x - min_price)
min_price = min(min_price, x)
return ans
122. 买卖股票的最佳时机 II
- 对于连续上涨的股价,设为
p1, p2, p3……pn。它的最大收益为pn - p1,等价于每天都买卖,pn - p1 = (pn - pn-1) + …… (p3 - p2) + (p2 - p1) - 对于连续下降的股价,不交易是最赚的
- 所以只需要计算
昨天买, 今天卖是不是赚的tmp = prices[i] - prices[i-1]- 是赚的,就加入收益
- 否则就不操作
class Solution:
def maxProfit(self, prices: List[int]) -> int:
ans = 0
for i in range(1, len(prices)):
tmp = prices[i] - prices[i-1] # 昨天买, 今天卖
if tmp > 0:
ans += tmp
return ans
2.3 DP
动态规划,用空间换时间
所以一般状态数组 f 都是反应一个区间的结果
f[i]:A[0:i]要求的结果f[i][j]:A[0:i]和B[0:j]要求的结果
198. 打家劫舍
- 不能连续偷
- 第i间房的最大利润为:
f[i] = max(f[i-2]+num[i], f[i-1]) - f 同时平移+2,
f[i+2] = max(f[i]+x, f[i+1]) nums不用加,因为这个平移只作用在 f 数组
class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
f = [0] * (n+2)
for i, x in enumerate(nums):
f[i+2] = max(f[i]+x, f[i+1])
return f[-1]
213. 打家劫舍 II
- 判断边界
nums[0]是否要选 - 如果选,
nums[0], 则1和-1不能选 - 如果不选,则相当于是在剩余的
nums[1:]中选
class Solution:
def rob1(self, nums):
n = len(nums)
f = [0] * (n + 2)
for i, x in enumerate(nums):
f[i+2] = max(f[i]+x, f[i+1])
return f[-1]
def rob(self, nums: List[int]) -> int:
rob0 = nums[0] + self.rob1(nums[2:-1])
no_rob0 = self.rob1(nums[1:])
return max(rob0, no_rob0)
1143. 最长公共子序列
子序列(不连续)
f[i][j],表示text1 的前 i 个字符和text2 的前 j 个字符的最长公共子序列的长度x == y时,f[i][j] = f[i-1][j-1] + 1x != y时,f[i][j] = max(f[i-1][j], f[i][j-1])- 不相等的时候只能取上一个状态的结果, 而上一个状态只可能是
f[i-1][j], f[i][j-1]
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
l1 = len(text1)
l2 = len(text2)
f = [ [0]*(l2+1) for _ in range(l1+1) ]
for i, x in enumerate(text1):
for j, y in enumerate(text2):
if x == y:
f[i+1][j+1] = f[i][j] + 1
else:
f[i+1][j+1] = max(f[i][j+1], f[i+1][j])
return f[l1][l2]
72. 编辑距离

f[i][j],表示把word1 的前 i 个字符(word[0:i]) 替换为word2 的前 j 个字符的最少操作数x == y时,f[i][j] = f[i-1][j-1],无需操作x != y时,需要编辑(操作数+1),从插入,删除,替换中选一个操作最少的- 插入,肯定是要插入与
word2 的第 j 个字符相等的符号- 即元素
i+1 == j - 此时的状态就相当于在删除
j的状态下再操作——f[i][j-1]
- 即元素
- 删除,相当于去掉
word1[i],所以状态回到了 ——f[i-1][j] + 1 - 替换,
f[i-1][j-1] + 1
- 插入,肯定是要插入与
- 边界条件
word2为空时,word1必须删除i个元素才可相等word1为空时,word1必须插入i个元素才可相等
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# 删除 f[i-1][j], 插入f[i][j-1], 替换f[i-1][j-1]
# x == y: f[i][j] = f[i-1][j-1]
# x != y: f[i][j] = min(f[i][j-1], f[i-1][j], f[i-1][j-1]) + 1
# f 矩阵同时平移 1
l1 = len(word1)
l2 = len(word2)
f = [ [0] * (l2 + 1) for _ in range(l1 + 1) ]
# 边界条件 f[i][0] 和 f[0][j]
for i in range(1, l1 + 1):
# 即 word1 有i个元素, word2 为空时, 必须删除 i 次, 才可以 word1==word2
f[i][0] = i
for j in range(1, l2 + 1):
# 即 word1 为空时, word2 有i个元素, 必须插入 i 次, 才可以 word1==word2
f[0][j] = j
for i, x in enumerate(word1):
for j, y in enumerate(word2):
if x == y:
f[i + 1][j + 1] = f[i][j]
else:
f[i + 1][j + 1] = min(f[i+1][j], f[i][j+1], f[i][j]) + 1
return f[l1][l2]
5. 最长回文子串
子串(连续)
- 方法一:双指针,从最外侧开始遍历(两头),只有两头相等时才判断是不是回文串
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
ans = ""
max_lens = 0
for right in range(n-1, -1, -1):
for left in range(right + 1):
if s[left] == s[right]:
tmp = s[left:right+1]
lens = right - left + 1
if lens > max_lens:
if tmp == tmp[::-1]:
ans = tmp
max_lens = lens
break
return ans
- 方法二:中心扩散
- 判断
abcba,只需要从c开始, - 判断左右两边是否相等,如果相等则继续扩散
if l + 1 <= r - 1:是循环终止前的状态
- 判断
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
left, right = 0, 0
# 奇回文串, 以每个 i 为中心, 计算每个的最长回文串
for i in range(n):
l = r = i # 奇数中心只有一个
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
if l + 1 <= r - 1: # s[l+1:r] 是回文串
if r - l - 2 > right - left:
left, right = l + 1, r - 1
# 偶数回文串, 以 (i,i+1) 为中心
for i in range(n - 1):
l, r = i, i + 1
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
if l + 1 <= r - 1:
if r - l - 2 > right - left:
left, right = l + 1, r - 1
return s[ left : right + 1 ] # 要取到 right 需要加 1
2.4 双指针
209. 长度最小的子数组

- 由于全是正整数
- 计算每个右端点,对应的最短子数组(左端点不断移动,
l += 1) - 当
[l, r]内的和小于target时,r += 1再计算当前右端点的最短子数组 - 时间复杂度
O(n),l 移动 n 次,r 移动 n 次
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
ans = inf
n = len(nums)
s = 0 # 当前的和
left = 0
for right, x in enumerate(nums):
s += x
# 找当前 right 的最短的满足条件的子数组
while s >= target:
ans = min(ans, right-left+1)
s -= nums[left]
left += 1
return ans if ans <= n else 0
76. 最小覆盖子串
和 209.长度最小的子数组 一样
- 求每个 right 的最小覆盖
from collections import Counter
class Solution:
def minWindow(self, s: str, t: str) -> str:
target = Counter(t)
cnt = Counter()
n = len(s)
ans_left, ans_right = 0, n
left = 0
for right, x in enumerate(s):
cnt[x] += 1
while cnt >= target:
if right - left < ans_right - ans_left:
ans_left, ans_right = left, right
cnt[s[left]] -= 1
left += 1
return s[ans_left:ans_right+1] if ans_right < n else ""
2.5 链表
25. K 个一组翻转链表
p0:总是指向,待翻转链表的前一个节点tmp = p0.next,保存 p0 的下一个位置p0.next.next = cur,与后半段相接p0.next = pre,与前半段相接p0=tmp,开启下一次

class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
n = 0
cur = head
while cur:
n += 1
cur = cur.next
p0 = dummy = ListNode(next=head)
while n >= k:
n -= k
pre = None
cur = p0.next
for _ in range(k):
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
tmp = p0.next # 下一段待翻转链表的前一个位置
p0.next.next = cur # 与后半段相接
p0.next = pre # 与前半段相接
p0 = tmp # 指向下一段待翻转链表的前一个位置
return dummy.next


1152

被折叠的 条评论
为什么被折叠?



