1406 words
7 minutes
日拱两卒(一)
2026-07-04

Leetcode的考察点和ACM不太一样,之前面试也有几次尴尬写不出的经历。

从2026.07.04开始把Hot100过一遍,因为前面几题其实很久以前提交过,所以这次倒着刷。

这轮试着用Python而不是C++!

每天两题写完差不多正好是9月日常实习。某一天的题太简单的话就多做几道!

100#

n + 1个整数,值域是[1, n],找出唯一的重复的数。

假设这个重复的数是x。定义cnt[i]为小于等于i的数的个数。

cnt[i] <= ii <= x - 1时。

cnt[i] > ii >= x 时。

所以二分出最小的满足cnt[i] > ii就可以了。

class Solution:
def findDuplicate(self, nums: List[int]) -> int:
l = 1
r = len(nums) - 1
while l < r:
mid = (l + r) >> 1;
cnt = 0
for num in nums:
cnt += num <= mid
if cnt > mid:
r = mid
else:
l = mid + 1
return l

99#

按字典序找出下一个排列。

比如[1, 2, 3]的下一个是[1, 3, 2],再下一个是[2, 1, 3]

[1, 3, 5, 4, 2] -> [1, 4, 5, 3, 2] -> [1, 4, 2, 3, 5]。这是一个一般化过程。

从后往前看,如果有一个数破坏了递增,说明这个数移到后面去是可以提升字典序的(因为后面有比它大的数)。

那么把哪个数和它调换?因为要求下一个排列,肯定是比它大的最小的数。

换完以后容易发现现在递减的部分可以倒过来,进一步缩小字典序。

class Solution:
def nextPermutation(self, nums: List[int]) -> None:
n = len(nums)
i = n - 2
while i >= 0 and nums[i] >= nums[i + 1]:
i -= 1
if i >= 0:
j = n - 1
while nums[j] <= nums[i]:
j -= 1
nums[i], nums[j] = nums[j], nums[i]
nums[i+1:] = nums[i+1:][::-1]

98#

不用sort排序一个只含有0, 1, 2的数组。

这个问题叫荷兰国旗问题(名字加深印象)!

p0维护下一个0的位置,它的左边都是0p2维护下一个2的位置,它的右边都是2i是当前检查的位置。

循环的不变量:[0, p0)都是 0(p2, n - 1]都是 2,中间[p0, i)都是 1。

2交换到后面去的时候要再检查一次当前位置,因为被换到前面来的数可能是02

在这个算法里,一个数最多会被搬两次。

class Solution:
def sortColors(self, nums: List[int]) -> None:
p0, i, p2 = 0, 0, len(nums) - 1
while i <= p2:
if nums[i] == 0:
nums[i], nums[p0] = nums[p0], nums[i]
p0 += 1
i += 1
elif nums[i] == 2:
nums[i], nums[p2] = nums[p2], nums[i]
p2 -= 1
else:
i += 1

97#

求一个数组里的绝对众数。

擂台法。同一个数字想成同一个门派,同门派就加一战斗力,不同门派就减一战斗力。绝对众数的门派肯定能站到最后。

class Solution:
def majorityElement(self, nums: List[int]) -> int:
ans, cnt = 0, 0
for num in nums:
if cnt == 0:
ans = num
cnt += 1
else:
cnt += 1 if num == ans else -1
return ans

96#

找出只出现一次的数字,保证其他数字都是两次。

老生常谈的异或。

class Solution:
def singleNumber(self, nums: List[int]) -> int:
ans = 0
for num in nums:
ans = ans ^ num
return ans

95#

编辑距离。

dp[n][m]代表只考虑word1n个字母,word2m个字母的答案。

n个和第m个字母相等时,只需要考虑dp[n - 1][m - 1]这个子问题。

不相等时,插入、删除、替换对应三个子问题。

@cache起到记忆化搜索的作用,Python把这个功能内置了。

class Solution:
def minDistance(self, word1: str, word2: str) -> int:
n = len(word1)
m = len(word2)
@cache
def dfs(x, y):
if x < 0:
return y + 1
if y < 0:
return x + 1
if word1[x] == word2[y]:
return dfs(x - 1, y - 1)
return min(dfs(x - 1, y), dfs(x, y - 1), dfs(x - 1, y - 1)) + 1
return dfs(n - 1, m - 1)

94#

最长公共子序列。

熟悉Python的多维列表初始化和enumerate

class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
n, m = len(text1), len(text2)
dp = [[0] * (m + 1) for _ in range(n + 1)]
for i, x in enumerate(text1):
for j, y in enumerate(text2):
dp[i + 1][j + 1] = dp[i][j] + 1 if x == y else max(dp[i][j + 1], dp[i + 1][j])
return dp[n][m]

93#

最长回文子串。

经典的区间DP。

class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
dp = [[False] * n for _ in range(n)]
for i in range(n):
dp[i][i] = True
for i in range(n - 1):
dp[i][i + 1] = True if s[i] == s[i + 1] else False
for length in range(3, n + 1):
for l in range(n):
r = l + length - 1
if r > n - 1:
break
if s[l] == s[r]:
dp[l][r] = dp[l + 1][r - 1]
ans = 0
st = 0
for i in range(n):
for j in range(i, n):
if dp[i][j] and j - i + 1 > ans:
ans = j - i + 1
st = i
return s[st:st + ans]

92#

从左上角走到右下角的最小权重和,只能向下和向右。

经典的简单DP。

class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
dp = [[0] * m for _ in range(n)]
dp[0][0] = grid[0][0]
for j in range(1, m):
dp[0][j] = dp[0][j - 1] + grid[0][j]
for i in range(1, n):
dp[i][0] = dp[i - 1][0] + grid[i][0]
for i in range(1, n):
for j in range(1, m):
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]
return dp[n - 1][m - 1]

91#

从左上角走到右下角的不同路径数,只能向下和向右。

可以是简单DP。

也可以是组合数学,mn列,因为只能向下和向右,肯定是走m - 1 + n - 1步,这些步数里有m - 1步是向下走。

数据量小的时候Python有现成的comb函数,数据量大的时候还是得上预处理阶乘和逆元的写法。

class Solution:
def uniquePaths(self, m: int, n: int) -> int:
return comb(m + n - 2, m - 1)