LeetCode Hot 100 题解(2026-08-09)
本文档整理了最新 LeetCode Hot 100 所有题目,按分类组织,每题包含题目描述、便于面试记忆的解题方法、Java 和 Python 代码实现。
一、数组
1. 两数之和(简单)
题目描述: 给定整数数组 nums 和目标值 target,找出数组中和为目标值的两个数,返回其下标。
解题思路: 哈希表存储已遍历元素的值→下标映射。遍历时计算 target - nums[i],若在哈希表中则找到答案,否则将当前元素存入哈希表。O(n) 时间,O(n) 空间。
java
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
map.put(nums[i], i);
}
return new int[]{-1, -1};
}
}
python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
seen = {} # key=值, value=下标
for i, num in enumerate(nums):
complement = target - num # 要找的配对值
if complement in seen:
return [seen[complement], i]
seen[num] = i # 遍历完再放进去,避免自己和自己配对
return []
2. 字母异位词分组(中等)
题目描述: 给定字符串数组,将字母异位词(字母相同但排列不同)分组。
解题思路: 对每个字符串排序作为 key,用哈希表分组。或者统计每个字符出现次数(如 "a1b2c3")作为 key。O(n·klogk) 或 O(n·k)。
java
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
// 排序后作为key
char[] chars = s.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
return new ArrayList<>(map.values());
}
}
python
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
groups = {}
for s in strs:
key = ''.join(sorted(s)) # 排序后异位词key相同
groups.setdefault(key, []).append(s)
return list(groups.values())
3. 最长连续序列(中等)
题目描述: 给定未排序的整数数组,找出最长连续数字序列的长度。要求 O(n) 时间。
解题思路: 将所有数字存入 HashSet。遍历每个数,只有当它是连续序列的起点(num-1 不在集合中)时,才向后延伸计算长度。每个数最多访问两次,O(n)。
java
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int num : nums) set.add(num);
int maxLen = 0;
for (int num : set) {
// 只从序列起点开始
if (!set.contains(num - 1)) {
int cur = num, len = 1;
while (set.contains(cur + 1)) {
cur++;
len++;
}
maxLen = Math.max(maxLen, len);
}
}
return maxLen;
}
}
python
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
num_set = set(nums)
max_len = 0
for num in num_set:
if num - 1 not in num_set: # 序列起点
cur, length = num, 1
while cur + 1 in num_set:
cur += 1
length += 1
max_len = max(max_len, length)
return max_len
二、双指针
4. 移动零(简单)
题目描述: 将数组中所有0移到末尾,保持非零元素相对顺序,必须原地操作。
解题思路: 快慢指针,i 遍历数组,j 指向下一个非零元素应放的位置。遇到非零数就交换 nums[i] 和 nums[j],j++。本质是把非零数往前搬。
java
class Solution {
public void moveZeroes(int[] nums) {
int j = 0; // 下一个非零元素应放的位置
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
j++;
}
}
}
}
python
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
j = 0 # 下一个非零元素应放的位置
for i in range(len(nums)):
if nums[i] != 0:
nums[i], nums[j] = nums[j], nums[i]
j += 1
5. 盛最多水的容器(中等)
题目描述: 给定数组 height 表示垂直线高度,找出两条线与x轴构成的最大容器面积。
解题思路: 左右双指针向中间收缩。每次计算面积后,移动较短的那边(因为面积由短板决定,移动长板不可能增大面积)。O(n)。
java
class Solution {
public int maxArea(int[] height) {
int l = 0, r = height.length - 1;
int maxArea = 0;
while (l < r) {
int area = (r - l) * Math.min(height[l], height[r]);
maxArea = Math.max(maxArea, area);
if (height[l] < height[r]) l++;
else r--;
}
return maxArea;
}
}
python
class Solution:
def maxArea(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
max_area = 0
while l < r:
area = (r - l) * min(height[l], height[r])
max_area = max(max_area, area)
if height[l] < height[r]:
l += 1
else:
r -= 1
return max_area
6. 三数之和(中等)
题目描述: 找出数组中所有和为 0 且不重复的三元组。
解题思路: 排序后固定一个数,对剩余部分用双指针找两数之和等于 -numsi。关键:跳过重复元素避免重复结果。O(n^2)。
java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
if (nums[i] > 0) break; // 剪枝:最小数>0不可能和为0
if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重
int l = i + 1, r = nums.length - 1;
while (l < r) {
int sum = nums[i] + nums[l] + nums[r];
if (sum == 0) {
res.add(Arrays.asList(nums[i], nums[l], nums[r]));
while (l < r && nums[l] == nums[l + 1]) l++; // 去重
while (l < r && nums[r] == nums[r - 1]) r--;
l++; r--;
} else if (sum < 0) l++;
else r--;
}
}
return res;
}
}
python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
for i in range(len(nums) - 2):
if nums[i] > 0: break
if i > 0 and nums[i] == nums[i - 1]: continue # 去重
l, r = i + 1, len(nums) - 1
while l < r:
total = nums[i] + nums[l] + nums[r]
if total == 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 total < 0:
l += 1
else:
r -= 1
return res
7. 接雨水(困难)
题目描述: 给定高度数组表示柱子,计算下雨后能接多少雨水。
解题思路(双指针法,易于记忆): 左右双指针,维护 leftMax 和 rightMax 记录左右两侧最高柱子。哪边低就处理哪边:当前高度低于该侧最大值时,差值就是当前列的雨水。O(n)。
java
class Solution {
public int trap(int[] height) {
int l = 0, r = height.length - 1;
int leftMax = 0, rightMax = 0, water = 0;
while (l < r) {
leftMax = Math.max(leftMax, height[l]);
rightMax = Math.max(rightMax, height[r]);
if (leftMax < rightMax) {
water += leftMax - height[l];
l++;
} else {
water += rightMax - height[r];
r--;
}
}
return water;
}
}
python
class Solution:
def trap(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
left_max = right_max = water = 0
while l < r:
left_max = max(left_max, height[l])
right_max = max(right_max, height[r])
if left_max < right_max:
water += left_max - height[l]
l += 1
else:
water += right_max - height[r]
r -= 1
return water
三、滑动窗口
8. 无重复字符的最长子串(中等)
题目描述: 找出字符串中不含重复字符的最长子串长度。
解题思路: 滑动窗口 + HashSet/字典。右指针扩展窗口,遇到重复字符时左指针跳到 重复字符上次位置+1。用字典记录字符最后一次出现位置。O(n)。
java
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int l = 0, maxLen = 0;
for (int r = 0; r < s.length(); r++) {
char c = s.charAt(r);
if (map.containsKey(c)) {
// max防止l回退:重复字符可能在上一个l之前
l = Math.max(l, map.get(c) + 1);
}
map.put(c, r);
maxLen = Math.max(maxLen, r - l + 1);
}
return maxLen;
}
}
python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
pos = {}
l = max_len = 0
for r, ch in enumerate(s):
if ch in pos:
l = max(l, pos[ch] + 1) # max防止l回退到已经跳过的旧位置
pos[ch] = r
max_len = max(max_len, r - l + 1)
return max_len
9. 找到字符串中所有字母异位词(中等)
题目描述: 找出字符串 s 中所有 p 的字母异位词的起始下标。
解题思路: 固定大小滑动窗口。维护两个长度为 26 的计数数组分别统计 p 和当前窗口的字符频率,当两个数组相等时记录起始位置。O(n)。
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res = new ArrayList<>();
if (s.length() < p.length()) return res;
int[] pCnt = new int[26], winCnt = new int[26];
for (char c : p.toCharArray()) pCnt[c - 'a']++;
for (int i = 0; i < s.length(); i++) {
winCnt[s.charAt(i) - 'a']++;
if (i >= p.length()) winCnt[s.charAt(i - p.length()) - 'a']--;
if (Arrays.equals(pCnt, winCnt)) res.add(i - p.length() + 1);
}
return res;
}
}
python
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
res = []
if len(s) < len(p): return res
p_cnt = [0] * 26
win_cnt = [0] * 26
for ch in p:
p_cnt[ord(ch) - 97] += 1
for i, ch in enumerate(s):
win_cnt[ord(ch) - 97] += 1
if i >= len(p):
win_cnt[ord(s[i - len(p)]) - 97] -= 1
if p_cnt == win_cnt:
res.append(i - len(p) + 1)
return res
四、子串
10. 和为 K 的子数组(中等)
题目描述: 统计数组中连续子数组的和等于 k 的个数。
解题思路: 前缀和 + 哈希表。preSum[i] 表示 nums[0..i-1] 的和。对于当前位置 r,需要找有多少个 l < r 使得 preSum[r] - preSum[l] = k,即 preSum[l] = preSum[r] - k。用哈希表记录每个前缀和出现次数。O(n)。
java
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1); // 前缀和为0出现1次
int preSum = 0, count = 0;
for (int num : nums) {
preSum += num;
count += map.getOrDefault(preSum - k, 0);
map.put(preSum, map.getOrDefault(preSum, 0) + 1);
}
return count;
}
}
python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
pre_sum_count = {0: 1} # 前缀和0出现1次,处理从开头累加=k的情况
pre_sum = count = 0
for num in nums:
pre_sum += num
# 找之前有多少个前缀和等于 pre_sum - k
count += pre_sum_count.get(pre_sum - k, 0)
pre_sum_count[pre_sum] = pre_sum_count.get(pre_sum, 0) + 1
return count
11. 滑动窗口最大值(困难)
题目描述: 返回滑动窗口每次移动时窗口内的最大值。
解题思路: 单调递减队列(存下标)。队列头部始终是当前窗口最大值。新元素入队时从尾部弹出所有小于它的元素(维护单调递减)。当队头下标超出窗口范围时出队。O(n)。
java
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] res = new int[n - k + 1];
Deque<Integer> dq = new LinkedList<>(); // 存下标
for (int i = 0; i < n; i++) {
// 移除队尾所有小于当前值的元素
while (!dq.isEmpty() && nums[dq.peekLast()] < nums[i]) {
dq.pollLast();
}
dq.offerLast(i);
// 队头超出窗口范围则移除
if (dq.peekFirst() <= i - k) dq.pollFirst();
// 窗口形成后记录结果
if (i >= k - 1) res[i - k + 1] = nums[dq.peekFirst()];
}
return res;
}
}
python
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
res = []
dq = collections.deque() # 存下标,保持对应值单调递减
for i, num in enumerate(nums):
# 维护单调递减:弹出队尾所有小于当前值的元素
while dq and nums[dq[-1]] < num:
dq.pop()
dq.append(i)
# 队头下标已滑出窗口范围,移除
if dq[0] <= i - k:
dq.popleft()
if i >= k - 1:
res.append(nums[dq[0]])
return res
12. 最小覆盖子串(困难)
题目描述: 找出 s 中包含 t 所有字符的最小子串。
解题思路: 滑动窗口 + 计数。need 记录 t 中需要的字符数,have 记录窗口内已满足的字符种类数。右指针扩展直到窗口合法(已包含所有 t 字符),然后左指针收缩寻找更小子串。O(n)。
java
class Solution {
public String minWindow(String s, String t) {
int[] need = new int[128], window = new int[128];
for (char c : t.toCharArray()) need[c]++;
int needCnt = 0;
for (int cnt : need) if (cnt > 0) needCnt++;
int l = 0, haveCnt = 0, minLen = Integer.MAX_VALUE, start = 0;
for (int r = 0; r < s.length(); r++) {
char c = s.charAt(r);
window[c]++;
if (window[c] == need[c]) haveCnt++;
while (haveCnt == needCnt) {
if (r - l + 1 < minLen) {
minLen = r - l + 1;
start = l;
}
char leftChar = s.charAt(l);
if (window[leftChar] == need[leftChar]) haveCnt--;
window[leftChar]--;
l++;
}
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}
}
python
class Solution:
def minWindow(self, s: str, t: str) -> str:
need = {}
for ch in t:
need[ch] = need.get(ch, 0) + 1
need_cnt = len(need) # 有多少种不同的字符需要满足
l = have_cnt = min_start = 0
min_len = float('inf')
window = {}
for r, ch in enumerate(s):
window[ch] = window.get(ch, 0) + 1
# 当前字符数量刚好达标时,已满足种类+1
if ch in need and window[ch] == need[ch]:
have_cnt += 1
# 窗口已合法,尝试收缩左边界找最小窗口
while have_cnt == need_cnt:
if r - l + 1 < min_len:
min_len = r - l + 1
min_start = l
left_ch = s[l]
# 移除左边界字符会导致不再满足要求
if left_ch in need and window[left_ch] == need[left_ch]:
have_cnt -= 1
window[left_ch] -= 1
l += 1
return "" if min_len == float('inf') else s[min_start:min_start + min_len]
五、普通数组
13. 最大子数组和(中等)
题目描述: 找出连续子数组的最大和(Kadane 算法)。
解题思路: dp[i] = max(dp[i-1] + nums[i], nums[i]),即要么接上前面的子数组,要么从当前位置重新开始。可优化为 O(1) 空间:用 cur 代替 dp[i-1]。O(n)。
java
class Solution {
public int maxSubArray(int[] nums) {
int cur = nums[0], maxSum = nums[0];
for (int i = 1; i < nums.length; i++) {
cur = Math.max(cur + nums[i], nums[i]);
maxSum = Math.max(maxSum, cur);
}
return maxSum;
}
}
python
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
cur = max_sum = nums[0]
for num in nums[1:]:
cur = max(cur + num, num)
max_sum = max(max_sum, cur)
return max_sum
14. 合并区间(中等)
题目描述: 合并所有重叠的区间。
解题思路: 按区间起点排序,遍历时如果当前区间起点 <= 上一个区间终点,则合并(终点取 max);否则加入新区间。O(nlogn)。
java
class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
List<int[]> res = new ArrayList<>();
res.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
int[] last = res.get(res.size() - 1);
if (intervals[i][0] <= last[1]) {
last[1] = Math.max(last[1], intervals[i][1]);
} else {
res.add(intervals[i]);
}
}
return res.toArray(new int[res.size()][]);
}
}
python
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key=lambda x: x[0])
res = [intervals[0]]
for start, end in intervals[1:]:
if start <= res[-1][1]:
res[-1][1] = max(res[-1][1], end)
else:
res.append([start, end])
return res
15. 轮转数组(中等)
题目描述: 将数组向右轮转 k 步,原地操作。
解题思路: 三次反转:①反转整个数组;②反转前 k 个;③反转剩余部分。注意 k %= n。O(n) 时间,O(1) 空间。
java
class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
private void reverse(int[] nums, int l, int r) {
while (l < r) {
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
l++; r--;
}
}
}
python
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
k %= len(nums)
nums.reverse()
nums[:k] = reversed(nums[:k])
nums[k:] = reversed(nums[k:])
16. 除自身以外数组的乘积(中等)
题目描述: 返回数组 answer,其中 answeri 等于 nums 中除 numsi 外其余元素的乘积。不能使用除法,O(n)。
解题思路: 两次遍历:第一次从左到右计算每个位置左边所有元素的乘积;第二次从右到左乘上右边所有元素的乘积。O(n) 时间,O(1) 额外空间(不算输出数组)。
java
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] res = new int[n];
res[0] = 1;
// 左乘积
for (int i = 1; i < n; i++) {
res[i] = res[i - 1] * nums[i - 1];
}
// 右乘积
int right = 1;
for (int i = n - 1; i >= 0; i--) {
res[i] *= right;
right *= nums[i];
}
return res;
}
}
python
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
res = [1] * n
for i in range(1, n):
res[i] = res[i - 1] * nums[i - 1]
right = 1
for i in range(n - 1, -1, -1):
res[i] *= right
right *= nums[i]
return res
17. 缺失的第一个正数(困难)
题目描述: 找出未排序整数数组中缺失的最小正整数,O(n) 时间,O(1) 空间。
解题思路: 原地哈希。将每个 num 放到下标 num-1 的位置(如果 num 在 1, n 范围内)。最后第一个 nums[i] != i+1 的位置就是答案。O(n)。
java
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
// 原地哈希:把值x放到下标x-1的位置。while而非if:交换后新换来的数可能也需要归位
// nums[nums[i]-1] != nums[i] 是关键:目标位置已有正确值时不再交换,避免死循环
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
int tmp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = tmp;
}
}
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) return i + 1;
}
return n + 1;
}
}
python
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
for i in range(n):
# 原地哈希:把值x放到下标x-1的位置。while而非if:交换后新换来的数可能也需要归位
# nums[nums[i]-1] != nums[i] 是关键:目标位置已有正确值时不再交换,避免死循环
while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
for i in range(n):
if nums[i] != i + 1:
return i + 1
return n + 1
六、矩阵
18. 矩阵置零(中等)
题目描述: 若矩阵中某个元素为 0,将其所在行和列全部置 0,原地操作。
解题思路: 用矩阵的第一行和第一列作为标记位。先记录第一行/列本身是否有 0,再遍历矩阵用首行首列标记,最后根据标记置零并处理首行首列。
java
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean firstRowZero = false, firstColZero = false;
for (int j = 0; j < n; j++) if (matrix[0][j] == 0) firstRowZero = true;
for (int i = 0; i < m; i++) if (matrix[i][0] == 0) firstColZero = true;
for (int i = 1; i < m; i++)
for (int j = 1; j < n; j++)
if (matrix[i][j] == 0) {
matrix[i][0] = 0;
matrix[0][j] = 0;
}
for (int i = 1; i < m; i++)
for (int j = 1; j < n; j++)
if (matrix[i][0] == 0 || matrix[0][j] == 0)
matrix[i][j] = 0;
if (firstRowZero) for (int j = 0; j < n; j++) matrix[0][j] = 0;
if (firstColZero) for (int i = 0; i < m; i++) matrix[i][0] = 0;
}
}
python
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
m, n = len(matrix), len(matrix[0])
first_row_zero = any(matrix[0][j] == 0 for j in range(n))
first_col_zero = any(matrix[i][0] == 0 for i in range(m))
for i in range(1, m):
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = matrix[0][j] = 0
for i in range(1, m):
for j in range(1, n):
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
if first_row_zero:
for j in range(n): matrix[0][j] = 0
if first_col_zero:
for i in range(m): matrix[i][0] = 0
19. 螺旋矩阵(中等)
题目描述: 按顺时针螺旋顺序返回矩阵所有元素。
解题思路: 定义四条边界 top/bottom/left/right,按 右→下→左→上 的顺序遍历,每遍历完一条边缩紧边界,直到边界越界。
java
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
int top = 0, bottom = matrix.length - 1;
int left = 0, right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
for (int j = left; j <= right; j++) res.add(matrix[top][j]);
top++;
for (int i = top; i <= bottom; i++) res.add(matrix[i][right]);
right--;
if (top <= bottom) {
for (int j = right; j >= left; j--) res.add(matrix[bottom][j]);
bottom--;
}
if (left <= right) {
for (int i = bottom; i >= top; i--) res.add(matrix[i][left]);
left++;
}
}
return res;
}
}
python
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
res = []
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
for j in range(left, right + 1): res.append(matrix[top][j])
top += 1
for i in range(top, bottom + 1): res.append(matrix[i][right])
right -= 1
if top <= bottom:
for j in range(right, left - 1, -1): res.append(matrix[bottom][j])
bottom -= 1
if left <= right:
for i in range(bottom, top - 1, -1): res.append(matrix[i][left])
left += 1
return res
20. 旋转图像(中等)
题目描述: 将 n×n 矩阵顺时针旋转 90 度,原地操作。
解题思路: 先沿对角线(左上↔右下)翻转,再沿竖直中轴线左右翻转。即 matrix[i][j] ↔ matrix[j][i],然后每行反转。
java
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 对角线翻转
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++) {
int tmp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = tmp;
}
// 每行反转
for (int i = 0; i < n; i++)
for (int j = 0; j < n / 2; j++) {
int tmp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = tmp;
}
}
}
python
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix)
for i in range(n):
for j in range(i + 1, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
for i in range(n):
matrix[i].reverse()
21. 搜索二维矩阵 II(中等)
题目描述: 在每行每列都升序排列的矩阵中搜索目标值。
解题思路: 从右上角开始:matrix[i][j] > target 则左移,< target 则下移。O(m+n)。
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int i = 0, j = matrix[0].length - 1;
while (i < matrix.length && j >= 0) {
if (matrix[i][j] == target) return true;
else if (matrix[i][j] > target) j--;
else i++;
}
return false;
}
}
python
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
i, j = 0, len(matrix[0]) - 1
while i < len(matrix) and j >= 0:
if matrix[i][j] == target:
return True
elif matrix[i][j] > target:
j -= 1
else:
i += 1
return False
七、链表
22. 相交链表(简单)
题目描述: 找出两个单链表相交的起始节点。
解题思路: 双指针各自遍历两条链表,走到尾部后切换到对方链表头部继续遍历。两指针相遇处即为交点(或都为 null 表示不相交)。
java
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a = headA, b = headB;
while (a != b) {
a = a == null ? headB : a.next;
b = b == null ? headA : b.next;
}
return a;
}
}
python
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
a, b = headA, headB
while a != b:
a = a.next if a else headB
b = b.next if b else headA
return a
23. 反转链表(简单)
题目描述: 反转单链表。
解题思路: 迭代法:pre、cur、next 三指针。next = cur.next,cur.next = pre,pre = cur,cur = next。O(n),O(1)。
java
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null, cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
python
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre, cur = None, head
while cur:
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
return pre
24. 回文链表(简单)
题目描述: 判断链表是否为回文。
解题思路: 快慢指针找到中点,反转后半部分链表,然后比较前后两半。O(n) 时间,O(1) 空间。
java
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 反转后半部分
ListNode pre = null;
while (slow != null) {
ListNode next = slow.next;
slow.next = pre;
pre = slow;
slow = next;
}
// 比较
ListNode l = head, r = pre;
while (r != null) {
if (l.val != r.val) return false;
l = l.next;
r = r.next;
}
return true;
}
}
python
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 反转后半部分
pre = None
while slow:
nxt = slow.next
slow.next = pre
pre = slow
slow = nxt
# 比较
l, r = head, pre
while r:
if l.val != r.val:
return False
l = l.next
r = r.next
return True
25. 环形链表(简单)
题目描述: 判断链表是否有环。
解题思路: 快慢指针(Floyd 判圈)。快指针每次 2 步,慢指针每次 1 步。相遇则有环,fast 走到 null 则无环。
java
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
}
python
class Solution:
def hasCycle(self, head: ListNode) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
26. 环形链表 II(中等)
题目描述: 找出链表环的入口节点。
解题思路: 先用快慢指针找到相遇点,然后一个指针从头、一个从相遇点,每次各一步,相遇处即为环入口。
java
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
ListNode p = head;
while (p != slow) {
p = p.next;
slow = slow.next;
}
return p;
}
}
return null;
}
}
python
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
p = head
while p != slow:
p = p.next
slow = slow.next
return p
return None
27. 合并两个有序链表(简单)
题目描述: 合并两个升序链表为一个升序链表。
解题思路: 递归或迭代。迭代法用 dummy 节点,每次取两个头部较小者接在结果链表后。O(m+n)。
java
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1), cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dummy.next;
}
}
python
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = cur = ListNode(-1)
while l1 and l2:
if l1.val <= l2.val:
cur.next = l1
l1 = l1.next
else:
cur.next = l2
l2 = l2.next
cur = cur.next
cur.next = l1 or l2
return dummy.next
28. 两数相加(中等)
题目描述: 两个非空链表表示逆序存储的数字,相加返回新链表。
解题思路: 模拟加法竖式,维护进位 carry。每位和 = l1.val + l2.val + carry,新节点存 %10,carry = /10。处理完所有节点和进位后结束。
java
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1), cur = dummy;
int carry = 0;
while (l1 != null || l2 != null || carry != 0) {
int sum = carry;
if (l1 != null) { sum += l1.val; l1 = l1.next; }
if (l2 != null) { sum += l2.val; l2 = l2.next; }
cur.next = new ListNode(sum % 10);
carry = sum / 10;
cur = cur.next;
}
return dummy.next;
}
}
python
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = cur = ListNode(-1)
carry = 0
while l1 or l2 or carry:
total = carry
if l1:
total += l1.val
l1 = l1.next
if l2:
total += l2.val
l2 = l2.next
cur.next = ListNode(total % 10)
carry = total // 10
cur = cur.next
return dummy.next
29. 删除链表的倒数第 N 个结点(中等)
题目描述: 删除链表倒数第 n 个结点。
解题思路: 快慢指针。fast 先走 n+1 步(或 n 步配合 dummy),然后 slow 和 fast 同速移动。fast 到尾部时 slow 指向倒数第 n 个的前驱,删除即可。
java
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1, head);
ListNode fast = dummy, slow = dummy;
// fast 先走 n+1 步
for (int i = 0; i <= n; i++) fast = fast.next;
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
python
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(-1, head)
fast = slow = dummy
for _ in range(n + 1):
fast = fast.next
while fast:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next
30. 两两交换链表中的节点(中等)
题目描述: 两两交换链表中相邻节点,不修改节点值。
解题思路: 递归或迭代。迭代法:用 dummy 节点,每次处理两个节点。注意修改指针顺序。
java
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1, head), pre = dummy;
while (pre.next != null && pre.next.next != null) {
ListNode a = pre.next, b = a.next;
a.next = b.next;
b.next = a;
pre.next = b;
pre = a;
}
return dummy.next;
}
}
python
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy = pre = ListNode(-1, head)
while pre.next and pre.next.next:
a, b = pre.next, pre.next.next
a.next = b.next
b.next = a
pre.next = b
pre = a
return dummy.next
31. K 个一组翻转链表(困难)
题目描述: 每 k 个节点一组翻转链表,不足 k 个保持不变。
解题思路: 先检查剩余节点是否够 k 个。够的话反转这段 k 个节点(类似反转链表),然后将反转后的段落与前后部分对接,递归处理剩余部分。
java
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
// 检查是否有k个节点
ListNode cur = head;
for (int i = 0; i < k; i++) {
if (cur == null) return head;
cur = cur.next;
}
// 翻转前k个(标准链表反转):pre最终指向反转后的新头
ListNode pre = null;
cur = head;
for (int i = 0; i < k; i++) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
// head原为头节点,反转后变成当前段的尾节点,需要连接到下一段的头
// cur现在指向第k+1个节点(下一段的起点)
head.next = reverseKGroup(cur, k);
return pre; // pre是反转后的新头
}
}
python
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
# 检查是否有k个节点
cur = head
for _ in range(k):
if not cur: return head
cur = cur.next
# 翻转前k个(标准链表反转):pre最终指向反转后的新头
pre, cur = None, head
for _ in range(k):
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
# head原为头节点,反转后变成当前段的尾节点,需要连接到下一段的头
# cur现在指向第k+1个节点(下一段的起点)
head.next = self.reverseKGroup(cur, k)
return pre # pre是反转后的新头
32. 随机链表的复制(中等)
题目描述: 复制含有 next 和 random 指针的链表。
解题思路: 哈希表法。第一遍遍历创建所有新节点,建立 原节点→新节点 的映射。第二遍遍历根据映射设置新节点的 next 和 random。O(n) 时间,O(n) 空间。
java
class Solution {
public Node copyRandomList(Node head) {
if (head == null) return null;
Map<Node, Node> map = new HashMap<>();
// 第一遍:创建所有新节点,建立 原节点→新节点 的映射(key为旧节点引用)
for (Node cur = head; cur != null; cur = cur.next) {
map.put(cur, new Node(cur.val));
}
// 第二遍:利用映射为每个新节点设置next和random指针
// map.get(cur.next) 通过旧next找到对应的新next,random同理
for (Node cur = head; cur != null; cur = cur.next) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
}
return map.get(head);
}
}
python
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head: return None
mapping = {}
cur = head
while cur:
mapping[cur] = Node(cur.val) # 建立 原节点→新节点 的映射
cur = cur.next
cur = head
while cur:
# 通过旧节点的next/random找到对应的新节点
mapping[cur].next = mapping.get(cur.next)
mapping[cur].random = mapping.get(cur.random)
cur = cur.next
return mapping[head]
33. 排序链表(中等)
题目描述: 对链表进行排序,要求 O(nlogn) 时间,O(1) 空间。
解题思路: 自底向上归并排序。先计算链表长度,按 step=1,2,4,... 分组合并。每次合并两个有序子链表。O(nlogn) 时间,O(1) 空间。
java
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
// 快慢指针找中点
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode mid = slow.next;
slow.next = null;
// 递归排序左右
ListNode left = sortList(head);
ListNode right = sortList(mid);
// 合并
return merge(left, right);
}
private ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1), cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) { cur.next = l1; l1 = l1.next; }
else { cur.next = l2; l2 = l2.next; }
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dummy.next;
}
}
python
class Solution:
def sortList(self, head: ListNode) -> ListNode:
if not head or not head.next: return head
# 快慢指针找中点
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
mid = slow.next
slow.next = None
# 递归排序并合并
left = self.sortList(head)
right = self.sortList(mid)
return self.merge(left, right)
def merge(self, l1, l2):
dummy = cur = ListNode(-1)
while l1 and l2:
if l1.val <= l2.val:
cur.next = l1; l1 = l1.next
else:
cur.next = l2; l2 = l2.next
cur = cur.next
cur.next = l1 or l2
return dummy.next
34. 合并 K 个升序链表(困难)
题目描述: 合并 K 个升序链表。
解题思路: 优先队列(最小堆)。将每个链表的头节点放入堆中,每次弹出最小值接到结果链表后,并将该节点的 next 入堆。O(Nlogk),N 为总节点数。
java
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
for (ListNode node : lists) {
if (node != null) pq.offer(node);
}
ListNode dummy = new ListNode(-1), cur = dummy;
while (!pq.isEmpty()) {
ListNode node = pq.poll();
cur.next = node;
cur = cur.next;
if (node.next != null) pq.offer(node.next);
}
return dummy.next;
}
}
python
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
import heapq
heap = []
for i, node in enumerate(lists):
if node:
heapq.heappush(heap, (node.val, i, node))
dummy = cur = ListNode(-1)
while heap:
_, i, node = heapq.heappop(heap)
cur.next = node
cur = cur.next
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
35. LRU 缓存(中等)
题目描述: 实现 LRU(最近最少使用)缓存,支持 get 和 put 操作,O(1) 时间。
解题思路: 哈希表 + 双向链表。哈希表实现 O(1) 查找,双向链表维护访问顺序。访问/插入时将节点移到链表头部,容量满时删除链表尾部节点。
java
class LRUCache {
class Node {
int key, val;
Node prev, next;
Node(int k, int v) { key = k; val = v; }
}
Map<Integer, Node> map = new HashMap<>();
// head/tail 是哨兵节点(dummy),避免处理首尾空指针,简化边界操作
Node head = new Node(-1, -1), tail = new Node(-1, -1);
int cap;
public LRUCache(int capacity) {
cap = capacity;
head.next = tail; // 初始化为空链表:head <-> tail
tail.prev = head;
}
public int get(int key) {
if (!map.containsKey(key)) return -1;
Node node = map.get(key);
moveToHead(node); // 访问后移到头部,表示最近使用
return node.val;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
map.get(key).val = value;
moveToHead(map.get(key));
} else {
if (map.size() == cap) {
Node last = tail.prev; // tail.prev 才是真正的尾节点(tail是哨兵)
removeNode(last);
map.remove(last.key); // 注意要删map中的key,node中存key就是为了这一步
}
Node node = new Node(key, value);
map.put(key, node);
addToHead(node);
}
}
void moveToHead(Node node) { removeNode(node); addToHead(node); }
// 从双向链表中摘除node,前后节点直接相连
void removeNode(Node node) { node.prev.next = node.next; node.next.prev = node.prev; }
// 将node插入到head之后:head <-> node <-> 原head.next
void addToHead(Node node) {
node.next = head.next; // node.next指向原第一个真实节点
node.prev = head;
head.next.prev = node; // 原第一个真实节点的prev回指node
head.next = node; // head.next指向新节点
}
}
python
class LRUCache:
class Node:
def __init__(self, k, v):
self.key = k
self.val = v
self.prev = self.next = None
def __init__(self, capacity: int):
self.cap = capacity
self.cache = {}
# head/tail 是哨兵节点,避免处理首尾空指针,简化边界操作
self.head = self.Node(-1, -1)
self.tail = self.Node(-1, -1)
self.head.next = self.tail # 初始化为空链表:head <-> tail
self.tail.prev = self.head
def _remove(self, node):
"""从双向链表中摘除node,前后节点直接相连"""
node.prev.next = node.next
node.next.prev = node.prev
def _add_to_head(self, node):
"""将node插入到head之后:head <-> node <-> 原head.next"""
node.next = self.head.next # node.next指向原第一个真实节点
node.prev = self.head
self.head.next.prev = node # 原第一个真实节点的prev回指node
self.head.next = node # head.next指向新节点
def get(self, key: int) -> int:
if key not in self.cache:
return -1
node = self.cache[key]
self._remove(node)
self._add_to_head(node) # 访问后移到头部,表示最近使用
return node.val
def put(self, key: int, value: int) -> None:
if key in self.cache:
self._remove(self.cache[key])
self.cache[key].val = value
self._add_to_head(self.cache[key])
else:
if len(self.cache) == self.cap:
last = self.tail.prev # tail.prev才是真正的尾节点(tail是哨兵)
self._remove(last)
del self.cache[last.key] # node中存key就是为了这里能删map中的条目
node = self.Node(key, value)
self.cache[key] = node
self._add_to_head(node)
八、二叉树
36. 二叉树的中序遍历(简单)
题目描述: 返回二叉树的中序遍历结果。
解题思路: 栈模拟递归。不断将左子节点入栈,然后弹出访问,再转向右子节点。O(n)。
java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
res.add(cur.val);
cur = cur.right;
}
return res;
}
}
python
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res, stack = [], []
cur = root
while cur or stack:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
37. 二叉树的最大深度(简单)
题目描述: 求二叉树的最大深度。
解题思路: 递归:maxDepth(root) = 1 + max(maxDepth(left), maxDepth(right))。空节点深度为 0。
java
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
}
python
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root: return 0
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
38. 翻转二叉树(简单)
题目描述: 翻转二叉树(左右子树互换)。
解题思路: 递归:交换当前节点的左右子树,然后分别翻转左右子树。
java
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
python
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root: return None
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
39. 对称二叉树(简单)
题目描述: 判断二叉树是否镜像对称。
解题思路: 递归比较左右子树:isMirror(l, r) 检查 l.val == r.val 且 l.left 与 r.right 对称、l.right 与 r.left 对称。
java
class Solution {
public boolean isSymmetric(TreeNode root) {
return isMirror(root.left, root.right);
}
boolean isMirror(TreeNode l, TreeNode r) {
if (l == null && r == null) return true;
if (l == null || r == null) return false;
return l.val == r.val && isMirror(l.left, r.right) && isMirror(l.right, r.left);
}
}
python
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def isMirror(l, r):
if not l and not r: return True
if not l or not r: return False
return l.val == r.val and isMirror(l.left, r.right) and isMirror(l.right, r.left)
return isMirror(root.left, root.right)
40. 二叉树的直径(简单)
题目描述: 求二叉树直径(任意两节点间的最长路径,可能不经过根节点)。
解题思路: DFS 递归。对于每个节点,计算左右子树深度,直径 = max(直径, 左深度 + 右深度)。返回较深一侧深度 + 1。O(n)。
java
class Solution {
int diameter = 0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return diameter;
}
int depth(TreeNode node) {
if (node == null) return 0;
int l = depth(node.left), r = depth(node.right);
diameter = Math.max(diameter, l + r);
return 1 + Math.max(l, r);
}
}
python
class Solution:
def diameterOfBinaryTree(self, root: TreeNode) -> int:
self.diameter = 0
def depth(node):
if not node: return 0
l, r = depth(node.left), depth(node.right)
self.diameter = max(self.diameter, l + r)
return 1 + max(l, r)
depth(root)
return self.diameter
41. 二叉树的层序遍历(中等)
题目描述: 返回二叉树逐层遍历的结果。
解题思路: BFS 队列。每次处理当前层所有节点(记录当前队列长度 levelSize),将值加入当前层列表。
java
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
int size = q.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
level.add(node.val);
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
res.add(level);
}
return res;
}
}
python
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root: return []
res, q = [], collections.deque([root])
while q:
level = []
for _ in range(len(q)):
node = q.popleft()
level.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
res.append(level)
return res
42. 将有序数组转换为二叉搜索树(简单)
题目描述: 将升序数组转换为平衡二叉搜索树。
解题思路: 二分递归。取中间元素为根,左半边递归构建左子树,右半边递归构建右子树。
java
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return build(nums, 0, nums.length - 1);
}
TreeNode build(int[] nums, int l, int r) {
if (l > r) return null;
int mid = (l + r) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = build(nums, l, mid - 1);
root.right = build(nums, mid + 1, r);
return root;
}
}
python
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
def build(l, r):
if l > r: return None
mid = (l + r) // 2
root = TreeNode(nums[mid])
root.left = build(l, mid - 1)
root.right = build(mid + 1, r)
return root
return build(0, len(nums) - 1)
43. 验证二叉搜索树(中等)
题目描述: 判断二叉树是否是有效的 BST。
解题思路: 递归,携带当前节点的值范围 (min, max)。左子树值 < root.val,右子树值 > root.val。使用 long 类型边界处理 Integer 极值。
java
class Solution {
public boolean isValidBST(TreeNode root) {
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
boolean validate(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;
return validate(node.left, min, node.val) && validate(node.right, node.val, max);
}
}
python
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def validate(node, lo, hi):
if not node: return True
if not (lo < node.val < hi): return False
return validate(node.left, lo, node.val) and validate(node.right, node.val, hi)
return validate(root, float('-inf'), float('inf'))
44. 二叉搜索树中第 K 小的元素(中等)
题目描述: 找出 BST 中第 k 小的元素。
解题思路: 中序遍历有序。中序遍历到第 k 个节点时记录结果。使用计数器。
java
class Solution {
int count = 0, res = 0;
public int kthSmallest(TreeNode root, int k) {
inorder(root, k);
return res;
}
void inorder(TreeNode node, int k) {
if (node == null) return;
inorder(node.left, k);
count++;
if (count == k) { res = node.val; return; }
inorder(node.right, k);
}
}
python
class Solution:
def kthSmallest(self, root: TreeNode, k: int) -> int:
self.count, self.res = 0, 0
def inorder(node):
if not node: return
inorder(node.left)
self.count += 1
if self.count == k:
self.res = node.val
return
inorder(node.right)
inorder(root)
return self.res
45. 二叉树的右视图(中等)
题目描述: 返回从右侧看到的节点值(每层最右边的节点)。
解题思路: 层序遍历(BFS),每层取最后一个节点值。或 DFS 先右后左,记录每层第一个访问到的节点。
java
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
int size = q.size();
for (int i = 0; i < size; i++) {
TreeNode node = q.poll();
if (i == size - 1) res.add(node.val);
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
}
return res;
}
}
python
class Solution:
def rightSideView(self, root: TreeNode) -> List[int]:
if not root: return []
res, q = [], collections.deque([root])
while q:
size = len(q)
for i in range(size):
node = q.popleft()
if i == size - 1: res.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
return res
46. 二叉树展开为链表(中等)
题目描述: 将二叉树按先序遍历展开为单链表(右指针为 next)。
解题思路: 对于每个节点,将左子树整体移到右子树位置,原右子树接到左子树的最右节点后面。
java
class Solution {
public void flatten(TreeNode root) {
TreeNode cur = root;
while (cur != null) {
if (cur.left != null) {
TreeNode rightmost = cur.left;
while (rightmost.right != null) rightmost = rightmost.right;
rightmost.right = cur.right;
cur.right = cur.left;
cur.left = null;
}
cur = cur.right;
}
}
}
python
class Solution:
def flatten(self, root: TreeNode) -> None:
cur = root
while cur:
if cur.left:
rightmost = cur.left
while rightmost.right:
rightmost = rightmost.right
rightmost.right = cur.right
cur.right = cur.left
cur.left = None
cur = cur.right
47. 从前序与中序遍历序列构造二叉树(中等)
题目描述: 根据前序和中序遍历构造二叉树。
解题思路: 前序第一个是根节点,在中序中找到根的位置,左边是左子树,右边是右子树。递归构建。
java
class Solution {
Map<Integer, Integer> inMap = new HashMap<>();
// preIdx 必须是类成员而非局部变量:前序遍历顺序就是构建顺序,
// 每创建一个节点 preIdx++,且递归先左后右的顺序必须严格递增,
// 传参做不到跨递归调用同步递增
int preIdx = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) inMap.put(inorder[i], i);
return build(preorder, 0, inorder.length - 1);
}
TreeNode build(int[] preorder, int l, int r) {
// l > r 是关键边界:当区间为空(如左子树不存在时,inMap.get(val)-1 < l)
// 此时直接返回 null,递归终止
if (l > r) return null;
int val = preorder[preIdx++];
TreeNode root = new TreeNode(val);
root.left = build(preorder, l, inMap.get(val) - 1);
root.right = build(preorder, inMap.get(val) + 1, r);
return root;
}
}
python
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
self.in_map = {v: i for i, v in enumerate(inorder)}
self.pre_idx = 0
def build(l, r):
if l > r: return None
val = preorder[self.pre_idx]
self.pre_idx += 1
root = TreeNode(val)
root.left = build(l, self.in_map[val] - 1)
root.right = build(self.in_map[val] + 1, r)
return root
return build(0, len(inorder) - 1)
48. 路径总和 III(中等)
题目描述: 找出二叉树中路径和等于 targetSum 的路径数量(路径方向必须向下)。
解题思路: 前缀和 + 哈希表(类似"和为 K 的子数组")。DFS 维护根到当前节点的路径和,查找是否有历史前缀和等于 curSum - targetSum。注意回溯时移除当前前缀和。
java
class Solution {
public int pathSum(TreeNode root, int targetSum) {
Map<Long, Integer> preSum = new HashMap<>();
preSum.put(0L, 1); // 哨兵:空路径前缀和为0,处理从根节点开始的路径
return dfs(root, 0L, targetSum, preSum);
}
int dfs(TreeNode node, long cur, int target, Map<Long, Integer> preSum) {
if (node == null) return 0;
cur += node.val;
// cur - target:若存在历史前缀和等于 cur-target,则说明从那个节点到当前节点的路径和为 target
int count = preSum.getOrDefault(cur - target, 0);
preSum.put(cur, preSum.getOrDefault(cur, 0) + 1);
count += dfs(node.left, cur, target, preSum);
count += dfs(node.right, cur, target, preSum);
// 回溯时必须移除当前前缀和:子树遍历完后返回父节点时,当前路径已不包含本节点,
// 若不删除,会污染兄弟子树的前缀和统计(兄弟路径不可能经过本节点)
preSum.put(cur, preSum.get(cur) - 1);
return count;
}
}
python
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> int:
self.pre_sum = {0: 1}
def dfs(node, cur):
if not node: return 0
cur += node.val
cnt = self.pre_sum.get(cur - targetSum, 0)
self.pre_sum[cur] = self.pre_sum.get(cur, 0) + 1
cnt += dfs(node.left, cur) + dfs(node.right, cur)
self.pre_sum[cur] -= 1 # 回溯
return cnt
return dfs(root, 0)
49. 二叉树的最近公共祖先(中等)
题目描述: 找出二叉树中两个指定节点的最近公共祖先。
解题思路: 递归。如果当前节点为 null 或等于 p 或 q,直接返回当前节点。递归左右子树,若左右都不为空则当前节点为 LCA,否则返回非空那边。
java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) return root;
return left != null ? left : right;
}
}
python
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return left or right
50. 二叉树中的最大路径和(困难)
题目描述: 找出二叉树中节点值之和最大的路径(至少包含一个节点)。
解题思路: 递归。对每个节点计算"以该节点为起点向下延伸的最大单边路径和"。更新全局最大值时考虑"左单边 + 当前值 + 右单边"。如果单边和为负,返回 0(不选这条路径)。
java
class Solution {
int maxSum = Integer.MIN_VALUE; // 初始化为最小负值,因为路径和可能为负
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
// maxGain 返回以 node 为起点、向下延伸的最大单边路径和
// 注意:返回值只能选一条边(左或右),因为路径不能分叉
int maxGain(TreeNode node) {
if (node == null) return 0;
// Math.max(0, ...) 是关键:如果子树贡献为负,直接不选该子树(贡献0)
// 这相当于"剪枝"------负路径只会拖累总和,最优解一定不会包含负贡献的子树
int left = Math.max(0, maxGain(node.left));
int right = Math.max(0, maxGain(node.right));
// 全局最大值考虑以当前节点为"拐点"的路径:左单边 + 当前值 + 右单边
maxSum = Math.max(maxSum, left + node.val + right);
// 返回单边最大值:当前值 + 左右中较大的那条边
return node.val + Math.max(left, right);
}
}
python
class Solution:
def maxPathSum(self, root: TreeNode) -> int:
self.max_sum = float('-inf')
def max_gain(node):
if not node: return 0
left = max(0, max_gain(node.left))
right = max(0, max_gain(node.right))
self.max_sum = max(self.max_sum, left + node.val + right)
return node.val + max(left, right)
max_gain(root)
return self.max_sum
九、图论
51. 岛屿数量(中等)
题目描述: 计算网格中岛屿数量(1 为陆地,0 为水)。
解题思路: DFS/BFS 沉岛法。遍历网格,遇到 '1' 时计数 +1,然后 DFS/BFS 将该岛屿所有相连的 '1' 置为 '0'(沉没)。O(mn)。
java
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
for (int i = 0; i < grid.length; i++)
for (int j = 0; j < grid[0].length; j++)
if (grid[i][j] == '1') {
count++;
dfs(grid, i, j);
}
return count;
}
void dfs(char[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') return;
grid[i][j] = '0';
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
}
python
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(i, j):
if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] == '0':
return
grid[i][j] = '0'
dfs(i + 1, j); dfs(i - 1, j)
dfs(i, j + 1); dfs(i, j - 1)
count = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
count += 1
dfs(i, j)
return count
52. 腐烂的橘子(中等)
题目描述: 网格中 0 表示空,1 表示新鲜橘子,2 表示腐烂橘子。每分钟腐烂橘子会感染上下左右的新鲜橘子。求所有橘子腐烂所需分钟数。
解题思路: BFS 多源感染。将所有腐烂橘子(值为 2)入队,同时统计新鲜橘子数量。每轮 BFS 处理队列中同一层的所有腐烂橘子,感染邻居,时间 +1。最后若新鲜橘子为 0 则返回时间。
java
class Solution {
public int orangesRotting(int[][] grid) {
int m = grid.length, n = grid[0].length;
Queue<int[]> q = new LinkedList<>();
int fresh = 0;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++) {
if (grid[i][j] == 2) q.offer(new int[]{i, j});
else if (grid[i][j] == 1) fresh++;
}
if (fresh == 0) return 0;
int time = 0;
int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
while (!q.isEmpty()) {
time++;
for (int i = q.size(); i > 0; i--) {
int[] cur = q.poll();
for (int[] d : dirs) {
int x = cur[0] + d[0], y = cur[1] + d[1];
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 1) {
grid[x][y] = 2;
fresh--;
q.offer(new int[]{x, y});
}
}
}
}
return fresh == 0 ? time - 1 : -1;
}
}
python
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
q = collections.deque()
fresh = 0
for i in range(m):
for j in range(n):
if grid[i][j] == 2:
q.append((i, j))
elif grid[i][j] == 1:
fresh += 1
if fresh == 0: return 0
time = 0
dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
while q:
time += 1
for _ in range(len(q)):
x, y = q.popleft()
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == 1:
grid[nx][ny] = 2
fresh -= 1
q.append((nx, ny))
return time - 1 if fresh == 0 else -1
53. 课程表(中等)
题目描述: 有 n 门课程和先修条件 prerequisitesi = ai, bi(必须先修 bi 再修 ai),判断是否能完成所有课程。
解题思路: 拓扑排序(BFS)。构建邻接表和入度数组,将所有入度为 0 的节点入队。每处理一个节点,将其后继入度 -1,入度为 0 时入队。最后若计数 == n 则能修完。
java
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> adj = new ArrayList<>();
int[] indegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) adj.add(new ArrayList<>());
for (int[] pre : prerequisites) {
adj.get(pre[1]).add(pre[0]);
indegree[pre[0]]++;
}
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < numCourses; i++)
if (indegree[i] == 0) q.offer(i);
int count = 0;
while (!q.isEmpty()) {
int cur = q.poll();
count++;
for (int next : adj.get(cur))
if (--indegree[next] == 0) q.offer(next);
}
return count == numCourses;
}
}
python
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
adj = [[] for _ in range(numCourses)]
indegree = [0] * numCourses
for a, b in prerequisites:
adj[b].append(a)
indegree[a] += 1
q = collections.deque([i for i, d in enumerate(indegree) if d == 0])
count = 0
while q:
cur = q.popleft()
count += 1
for nxt in adj[cur]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
q.append(nxt)
return count == numCourses
54. 实现 Trie(前缀树)(中等)
题目描述: 实现前缀树(Trie),支持 insert、search、startsWith。
解题思路: 每个节点包含一个长度为 26 的子节点数组和一个 isEnd 标志。插入时逐字符创建路径,查找时逐字符匹配。
java
class Trie {
// children[i] 表示字符 'a'+i 对应的子节点,null 表示该字符不存在
// 每个 Trie 节点本身就是一棵子树,同时也是一个 Trie 对象(递归结构)
Trie[] children;
boolean isEnd; // isEnd 标记当前节点是否是一个完整单词的结尾
public Trie() { children = new Trie[26]; }
public void insert(String word) {
Trie node = this; // 从根节点开始(根节点不代表任何字符)
for (char c : word.toCharArray()) {
int i = c - 'a';
if (node.children[i] == null) node.children[i] = new Trie();
node = node.children[i];
}
node.isEnd = true; // 在最后一个字符节点标记单词结束
}
public boolean search(String word) {
Trie node = find(word);
// 关键:不仅要路径存在,还要 isEnd == true,否则只是前缀匹配
return node != null && node.isEnd;
}
public boolean startsWith(String prefix) {
return find(prefix) != null; // 前缀匹配不关心 isEnd
}
Trie find(String s) {
Trie node = this;
for (char c : s.toCharArray()) {
int i = c - 'a';
if (node.children[i] == null) return null;
node = node.children[i];
}
return node;
}
}
python
class Trie:
def __init__(self):
self.children = {}
self.is_end = False
def insert(self, word: str) -> None:
node = self
for ch in word:
if ch not in node.children:
node.children[ch] = Trie()
node = node.children[ch]
node.is_end = True
def search(self, word: str) -> bool:
node = self._find(word)
return node is not None and node.is_end
def startsWith(self, prefix: str) -> bool:
return self._find(prefix) is not None
def _find(self, s):
node = self
for ch in s:
if ch not in node.children:
return None
node = node.children[ch]
return node
十、回溯
55. 全排列(中等)
题目描述: 返回无重复数字数组的所有排列。
解题思路: 回溯,通过交换元素位置来生成排列,不需要 used 数组。backtrack(index) 表示固定前 index 个位置,之后每个位置和当前 index 交换。
java
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
for (int num : nums) path.add(num);
backtrack(0, path, res);
return res;
}
// swap 法不需要 used 数组:将 path[start..end] 视为"未选择池",
// path[0..start-1] 是已固定的前缀。每次从"未选择池"中选一个元素
// 交换到 start 位置,自然实现了"选过的在前面,没选的留在后面"
void backtrack(int start, List<Integer> path, List<List<Integer>> res) {
if (start == path.size()) {
res.add(new ArrayList<>(path));
return;
}
for (int i = start; i < path.size(); i++) {
Collections.swap(path, start, i); // 将 i 位置元素"选入"前缀
backtrack(start + 1, path, res);
Collections.swap(path, start, i); // 恢复,准备选下一个
}
}
}
python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(start):
if start == len(nums):
res.append(nums[:])
return
for i in range(start, len(nums)):
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start]
backtrack(0)
return res
56. 子集(中等)
题目描述: 返回无重复数组的所有子集。
解题思路: 回溯。每个元素选或不选。更简洁:迭代构建,res 初始为空,每次将当前元素加入已有子集。
java
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backtrack(0, nums, new ArrayList<>(), res);
return res;
}
void backtrack(int start, int[] nums, List<Integer> path, List<List<Integer>> res) {
res.add(new ArrayList<>(path));
for (int i = start; i < nums.length; i++) {
path.add(nums[i]);
backtrack(i + 1, nums, path, res);
path.remove(path.size() - 1);
}
}
}
python
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(start, path):
res.append(path[:])
for i in range(start, len(nums)):
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return res
57. 电话号码的字母组合(中等)
题目描述: 给定数字字符串,返回所有可能的字母组合(电话按键映射)。
解题思路: 回溯。使用映射表 digitToLetters,DFS 递归处理每个数字。
java
class Solution {
String[] map = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
if (digits.isEmpty()) return res;
backtrack(digits, 0, new StringBuilder(), res);
return res;
}
void backtrack(String digits, int idx, StringBuilder sb, List<String> res) {
if (idx == digits.length()) {
res.add(sb.toString());
return;
}
String letters = map[digits.charAt(idx) - '0'];
for (char c : letters.toCharArray()) {
sb.append(c);
backtrack(digits, idx + 1, sb, res);
sb.deleteCharAt(sb.length() - 1);
}
}
}
python
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits: return []
mapping = ["", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]
res = []
def backtrack(idx, path):
if idx == len(digits):
res.append(''.join(path))
return
for ch in mapping[int(digits[idx])]:
path.append(ch)
backtrack(idx + 1, path)
path.pop()
backtrack(0, [])
return res
58. 组合总和(中等)
题目描述: 找出所有元素可以无限重复使用、和为 target 的组合。
解题思路: 回溯。每次可以选当前元素或跳过。选了当前元素后可以继续选同一个(因为无限重复)。
java
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
backtrack(candidates, target, 0, new ArrayList<>(), res);
return res;
}
void backtrack(int[] nums, int remain, int start, List<Integer> path, List<List<Integer>> res) {
if (remain < 0) return;
if (remain == 0) { res.add(new ArrayList<>(path)); return; }
for (int i = start; i < nums.length; i++) {
path.add(nums[i]);
backtrack(nums, remain - nums[i], i, path, res); // i 不变,可重复选
path.remove(path.size() - 1);
}
}
}
python
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
def backtrack(start, remain, path):
if remain < 0: return
if remain == 0:
res.append(path[:])
return
for i in range(start, len(candidates)):
path.append(candidates[i])
backtrack(i, remain - candidates[i], path) # i不变,可重复选
path.pop()
backtrack(0, target, [])
return res
59. 括号生成(中等)
题目描述: 生成所有可能的 n 对有效括号组合。
解题思路: 回溯。维护 open(已用左括号数)和 close(已用右括号数)。当 open = close = n 时得到一个解。只要 open < n 就可以加左括号,只要 close < open 就可以加右括号。
java
class Solution {
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
backtrack(n, 0, 0, new StringBuilder(), res);
return res;
}
void backtrack(int n, int open, int close, StringBuilder sb, List<String> res) {
if (sb.length() == 2 * n) { res.add(sb.toString()); return; }
if (open < n) { // 左括号数量未用完就可以加
sb.append('(');
backtrack(n, open + 1, close, sb, res);
sb.deleteCharAt(sb.length() - 1);
}
// 关键约束 close < open:右括号数必须严格小于左括号数才能加右括号
// 这保证了前缀中不会出现右括号多于左括号的情况(即前缀始终有效)
// 错误写法 close < n:那会生成 "(())" 和 "()()" 但也生成 "())(" 等无效组合
if (close < open) {
sb.append(')');
backtrack(n, open, close + 1, sb, res);
sb.deleteCharAt(sb.length() - 1);
}
}
}
python
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
def backtrack(open_cnt, close_cnt, path):
if len(path) == 2 * n:
res.append(''.join(path))
return
if open_cnt < n:
path.append('(')
backtrack(open_cnt + 1, close_cnt, path)
path.pop()
if close_cnt < open_cnt:
path.append(')')
backtrack(open_cnt, close_cnt + 1, path)
path.pop()
backtrack(0, 0, [])
return res
60. 单词搜索(中等)
题目描述: 判断字符网格中是否存在给定单词的路径(相邻单元格连接)。
解题思路: DFS + 回溯 + 标记已访问。遍历每个格子作为起点,DFS 四个方向。用临时修改字符(如置为 '#')代替 visited 数组,回溯时恢复。
java
class Solution {
public boolean exist(char[][] board, String word) {
for (int i = 0; i < board.length; i++)
for (int j = 0; j < board[0].length; j++)
if (dfs(board, word, i, j, 0)) return true;
return false;
}
boolean dfs(char[][] board, String word, int i, int j, int idx) {
if (idx == word.length()) return true;
if (i < 0 || j < 0 || i >= board.length || j >= board[0].length) return false;
if (board[i][j] != word.charAt(idx)) return false;
char tmp = board[i][j];
// 直接在 board 上标记 '#' 而非额外 visited 数组:
// board[i][j] != word[idx] 检查已经过滤掉了正常字符,'#' 永远不会匹配任何字母,
// 所以后续递归会在此位置返回 false,天然避免重复访问。回溯时恢复原字符即可
board[i][j] = '#';
boolean found = dfs(board, word, i + 1, j, idx + 1)
|| dfs(board, word, i - 1, j, idx + 1)
|| dfs(board, word, i, j + 1, idx + 1)
|| dfs(board, word, i, j - 1, idx + 1);
board[i][j] = tmp; // 必须恢复:当前 DFS 路径结束后,其他起点可能需要用到该格
return found;
}
}
python
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
m, n = len(board), len(board[0])
def dfs(i, j, idx):
if idx == len(word): return True
if i < 0 or j < 0 or i >= m or j >= n or board[i][j] != word[idx]:
return False
tmp, board[i][j] = board[i][j], '#'
found = (dfs(i + 1, j, idx + 1) or dfs(i - 1, j, idx + 1) or
dfs(i, j + 1, idx + 1) or dfs(i, j - 1, idx + 1))
board[i][j] = tmp
return found
for i in range(m):
for j in range(n):
if dfs(i, j, 0): return True
return False
61. 分割回文串(中等)
题目描述: 将字符串分割成所有子串都是回文串的方案。
解题思路: 回溯。从当前位置出发,尝试每个可能的回文前缀,将其加入路径并递归处理剩余部分。
java
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>();
backtrack(s, 0, new ArrayList<>(), res);
return res;
}
void backtrack(String s, int start, List<String> path, List<List<String>> res) {
if (start == s.length()) { res.add(new ArrayList<>(path)); return; }
for (int end = start; end < s.length(); end++) {
if (isPalindrome(s, start, end)) {
path.add(s.substring(start, end + 1));
backtrack(s, end + 1, path, res);
path.remove(path.size() - 1);
}
}
}
boolean isPalindrome(String s, int l, int r) {
while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
return true;
}
}
python
class Solution:
def partition(self, s: str) -> List[List[str]]:
res = []
def is_pal(l, r):
while l < r:
if s[l] != s[r]: return False
l += 1; r -= 1
return True
def backtrack(start, path):
if start == len(s):
res.append(path[:])
return
for end in range(start, len(s)):
if is_pal(start, end):
path.append(s[start:end + 1])
backtrack(end + 1, path)
path.pop()
backtrack(0, [])
return res
62. N 皇后(困难)
题目描述: 在 n×n 棋盘上放置 n 个皇后,彼此不攻击,返回所有不同方案。
解题思路: 回溯,逐行放置皇后。用三个集合记录已被占用的列、主对角线(i-j)、副对角线(i+j)。对于每列检查是否与已有皇后冲突。
java
class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList<>();
int[] queens = new int[n]; // queens[i] = j 表示第i行皇后在第j列
Set<Integer> cols = new HashSet<>();
// 对角线公式推导:同一主对角线(左上→右下)上任意两点 (r1,c1) 和 (r2,c2)
// 满足 r1-c1 = r2-c2,所以用 row-col 唯一标识每条主对角线
Set<Integer> diag1 = new HashSet<>(); // row - col
// 同一副对角线(右上→左下)上任意两点满足 r1+c1 = r2+c2,
// 所以用 row+col 唯一标识每条副对角线
Set<Integer> diag2 = new HashSet<>(); // row + col
backtrack(0, n, queens, cols, diag1, diag2, res);
return res;
}
void backtrack(int row, int n, int[] queens, Set<Integer> cols,
Set<Integer> d1, Set<Integer> d2, List<List<String>> res) {
if (row == n) {
res.add(buildBoard(queens, n));
return;
}
for (int col = 0; col < n; col++) {
// 三条件同时检查:同列、同主对角线、同副对角线
if (cols.contains(col) || d1.contains(row - col) || d2.contains(row + col)) continue;
queens[row] = col;
cols.add(col); d1.add(row - col); d2.add(row + col);
backtrack(row + 1, n, queens, cols, d1, d2, res);
cols.remove(col); d1.remove(row - col); d2.remove(row + col);
}
}
List<String> buildBoard(int[] queens, int n) {
List<String> board = new ArrayList<>();
for (int q : queens) {
char[] row = new char[n];
Arrays.fill(row, '.');
row[q] = 'Q';
board.add(new String(row));
}
return board;
}
}
python
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
res = []
cols, diag1, diag2 = set(), set(), set()
queens = [-1] * n
def backtrack(row):
if row == n:
board = []
for q in queens:
board.append(''.join('Q' if i == q else '.' for i in range(n)))
res.append(board)
return
for col in range(n):
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue
queens[row] = col
cols.add(col); diag1.add(row - col); diag2.add(row + col)
backtrack(row + 1)
cols.remove(col); diag1.remove(row - col); diag2.remove(row + col)
backtrack(0)
return res
十一、二分查找
63. 搜索插入位置(简单)
题目描述: 在排序数组中查找目标值的插入位置。
解题思路: 标准二分查找。while (l <= r),找到 mid,相等则返回,否则根据大小缩范围。最终 l 即为插入位置。
java
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) l = mid + 1;
else r = mid - 1;
}
return l;
}
}
python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
l, r = 0, len(nums) - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
l = mid + 1
else:
r = mid - 1
return l
64. 搜索二维矩阵(中等)
题目描述: 在每行递增且下一行首元素大于上一行尾元素的矩阵中搜索目标。
解题思路: 将二维矩阵视为一维有序数组,二分查找。idx = mid,行 = idx / n,列 = idx % n。
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int l = 0, r = m * n - 1; // 视为一维数组,索引范围 [0, m*n-1]
while (l <= r) {
int mid = l + (r - l) / 2;
// 关键:将一维索引映射回二维坐标
// 行号 = mid / n(每行 n 个元素,整除得到行号)
// 列号 = mid % n(余数即列号)
int val = matrix[mid / n][mid % n];
if (val == target) return true;
else if (val < target) l = mid + 1;
else r = mid - 1;
}
return false;
}
}
python
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m, n = len(matrix), len(matrix[0])
l, r = 0, m * n - 1
while l <= r:
mid = (l + r) // 2
val = matrix[mid // n][mid % n]
if val == target:
return True
elif val < target:
l = mid + 1
else:
r = mid - 1
return False
65. 在排序数组中查找元素的第一个和最后一个位置(中等)
题目描述: 找 target 在升序数组中的起始和结束位置。
解题思路: 两次二分:第一次找第一个 >= target 的位置(左边界),第二次找第一个 > target 的位置减 1(右边界)。若左边界位置的值 != target 则没找到。
java
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = findLeft(nums, target);
if (left == nums.length || nums[left] != target) return new int[]{-1, -1};
int right = findLeft(nums, target + 1) - 1;
return new int[]{left, right};
}
int findLeft(int[] nums, int target) {
int l = 0, r = nums.length;
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] >= target) r = mid;
else l = mid + 1;
}
return l;
}
}
python
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def find_left(t):
l, r = 0, len(nums)
while l < r:
mid = (l + r) // 2
if nums[mid] >= t:
r = mid
else:
l = mid + 1
return l
left = find_left(target)
if left == len(nums) or nums[left] != target:
return [-1, -1]
right = find_left(target + 1) - 1
return [left, right]
66. 搜索旋转排序数组(中等)
题目描述: 在旋转过的升序数组中搜索目标值,无重复元素。
解题思路: 二分查找。先判断 nums[mid] 在左侧有序段还是右侧有序段,再根据 target 位置调整指针。
java
class Solution {
public int search(int[] nums, int target) {
int l = 0, r = nums.length - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (nums[mid] == target) return mid;
// 旋转数组的核心:mid 将数组分成两半,必有一半是完全有序的
// 用 nums[l] <= nums[mid] 判断左半是否有序:
// 若左端点 <= 中间点,说明从 l 到 mid 没有"断裂点",左半有序
if (nums[l] <= nums[mid]) { // 左半部分有序
// 若 target 落在左半有序区间内,去左边找;否则去右边
if (nums[l] <= target && target < nums[mid]) r = mid - 1;
else l = mid + 1;
} else { // 否则右半部分有序(旋转点必在左半部分)
if (nums[mid] < target && target <= nums[r]) l = mid + 1;
else r = mid - 1;
}
}
return -1;
}
}
python
class Solution:
def search(self, nums: List[int], target: int) -> int:
l, r = 0, len(nums) - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return mid
if nums[l] <= nums[mid]: # 左半部分有序
if nums[l] <= target < nums[mid]:
r = mid - 1
else:
l = mid + 1
else: # 右半部分有序
if nums[mid] < target <= nums[r]:
l = mid + 1
else:
r = mid - 1
return -1
67. 寻找旋转排序数组中的最小值(中等)
题目描述: 在旋转过的升序数组(无重复)中找最小值。
解题思路: 二分查找。nums[mid] > nums[r] 说明最小值在右半部分,l = mid + 1;否则最小值在左半部分(含 mid),r = mid。
java
class Solution {
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] > nums[r]) l = mid + 1;
else r = mid;
}
return nums[l];
}
}
python
class Solution:
def findMin(self, nums: List[int]) -> int:
l, r = 0, len(nums) - 1
while l < r:
mid = (l + r) // 2
if nums[mid] > nums[r]:
l = mid + 1
else:
r = mid
return nums[l]
68. 寻找两个正序数组的中位数(困难)
题目描述: 找两个正序数组的中位数,O(log(m+n))。
解题思路: 二分第 k 小元素。每次从较短的数组取 k/2 个元素来减少搜索范围。总中位数 = 第 (m+n+1)/2 小与第 (m+n+2)/2 小的平均值。
java
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int total = nums1.length + nums2.length;
// 中位数技巧:无论总长度奇偶,都取第 (total+1)/2 小和第 (total+2)/2 小的平均值
// 奇数时两者相等(如 total=5,(5+1)/2=3,(5+2)/2=3,都取第3小)
// 偶数时取中间两个(如 total=6,(6+1)/2=3,(6+2)/2=4,取第3和第4小的平均)
int left = findKth(nums1, 0, nums2, 0, (total + 1) / 2);
int right = findKth(nums1, 0, nums2, 0, (total + 2) / 2);
return (left + right) / 2.0;
}
// 在两个有序数组中找第 k 小的元素,i 和 j 分别是 a 和 b 的当前起始下标
int findKth(int[] a, int i, int[] b, int j, int k) {
if (i >= a.length) return b[j + k - 1]; // 数组 a 已耗尽
if (j >= b.length) return a[i + k - 1]; // 数组 b 已耗尽
if (k == 1) return Math.min(a[i], b[j]); // 只需找最小的那个
// half = k/2:每次尝试排除 k/2 个不可能是第 k 小的元素
// 比较两个数组的第 half 个元素(相对各自当前起点),较小的那一半可以安全跳过
int half = k / 2;
// Integer.MAX_VALUE 处理边界:当某数组剩余不足 half 个元素时,
// 设其值为无穷大,保证不会被选中,从而从另一数组排除 half 个
int aVal = i + half - 1 < a.length ? a[i + half - 1] : Integer.MAX_VALUE;
int bVal = j + half - 1 < b.length ? b[j + half - 1] : Integer.MAX_VALUE;
if (aVal < bVal)
return findKth(a, i + half, b, j, k - half); // 排除 a 的前 half 个
else
return findKth(a, i, b, j + half, k - half); // 排除 b 的前 half 个
}
}
python
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
def findKth(i, j, k):
if i >= len(nums1): return nums2[j + k - 1]
if j >= len(nums2): return nums1[i + k - 1]
if k == 1: return min(nums1[i], nums2[j])
half = k // 2
v1 = nums1[i + half - 1] if i + half <= len(nums1) else float('inf')
v2 = nums2[j + half - 1] if j + half <= len(nums2) else float('inf')
if v1 < v2:
return findKth(i + half, j, k - half)
else:
return findKth(i, j + half, k - half)
total = len(nums1) + len(nums2)
left = findKth(0, 0, (total + 1) // 2)
right = findKth(0, 0, (total + 2) // 2)
return (left + right) / 2
十二、栈
69. 有效的括号(简单)
题目描述: 判断字符串中的括号是否闭合正确。
解题思路: 栈。遇到左括号压入对应右括号;遇到右括号时检查栈顶是否匹配,不匹配或栈空则无效。最后栈空为有效。
java
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(') stack.push(')');
else if (c == '{') stack.push('}');
else if (c == '[') stack.push(']');
else if (stack.isEmpty() || stack.pop() != c) return false;
}
return stack.isEmpty();
}
}
python
class Solution:
def isValid(self, s: str) -> bool:
stack = []
pairs = {')': '(', '}': '{', ']': '['}
for ch in s:
if ch in '({[':
stack.append(ch)
else:
if not stack or stack[-1] != pairs[ch]:
return False
stack.pop()
return not stack
70. 最小栈(中等)
题目描述: 设计栈,支持 push/pop/top/getMin 操作,所有操作 O(1)。
解题思路: 双栈法。一个普通栈存元素,另一个栈 minStack 存当前位置的最小值。push 时若 x <= minStack.top() 或 minStack 为空,则压入 minStack。
java
class MinStack {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minStack = new Stack<>();
public void push(int val) {
stack.push(val);
if (minStack.isEmpty() || val <= minStack.peek()) minStack.push(val);
}
public void pop() {
if (stack.pop().equals(minStack.peek())) minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
python
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val: int) -> None:
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self) -> None:
if self.stack.pop() == self.min_stack[-1]:
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
71. 字符串解码(中等)
题目描述: 3[a2[c]] → accaccacc,数字表示重复次数。
解题思路: 双栈法(一个存重复次数,一个存之前的字符串)。遇到数字累计;遇到 [ 将当前数和当前字符串入栈并重置;遇到 ] 将栈顶数字弹出,重复当前字符串后与栈顶字符串拼接。
java
class Solution {
public String decodeString(String s) {
Stack<Integer> numStack = new Stack<>();
Stack<StringBuilder> strStack = new Stack<>();
StringBuilder cur = new StringBuilder();
int num = 0;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
num = num * 10 + (c - '0');
} else if (c == '[') {
numStack.push(num);
strStack.push(cur);
num = 0;
cur = new StringBuilder();
} else if (c == ']') {
StringBuilder tmp = strStack.pop();
int repeat = numStack.pop();
for (int i = 0; i < repeat; i++) tmp.append(cur);
cur = tmp;
} else {
cur.append(c);
}
}
return cur.toString();
}
}
python
class Solution:
def decodeString(self, s: str) -> str:
num_stack, str_stack = [], []
cur, num = "", 0
for ch in s:
if ch.isdigit():
num = num * 10 + int(ch)
elif ch == '[':
num_stack.append(num)
str_stack.append(cur)
num, cur = 0, ""
elif ch == ']':
cur = str_stack.pop() + num_stack.pop() * cur
else:
cur += ch
return cur
72. 每日温度(中等)
题目描述: 返回数组 answer,answeri 是等待几天后温度才会更高。
解题思路: 单调递减栈(存下标)。栈中元素对应的温度是递减的。新温度高于栈顶温度时,弹出栈顶并计算差值。O(n)。
java
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] res = new int[n];
Stack<Integer> stack = new Stack<>(); // 存下标
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int prev = stack.pop();
res[prev] = i - prev;
}
stack.push(i);
}
return res;
}
}
python
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
res = [0] * n
stack = []
for i, t in enumerate(temperatures):
while stack and t > temperatures[stack[-1]]:
prev = stack.pop()
res[prev] = i - prev
stack.append(i)
return res
73. 柱状图中最大的矩形(困难)
题目描述: 求柱状图中能勾勒的最大矩形面积。
解题思路: 单调递增栈。遍历每根柱子,当当前高度小于栈顶高度时,以栈顶柱子为高计算面积(宽 = 当前位置到栈顶次元素之间的距离 - 1)。最终在数组末尾追加高度 0 统一处理边界。
java
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] h = new int[n + 2]; // 前后补0
System.arraycopy(heights, 0, h, 1, n);
Stack<Integer> stack = new Stack<>();
int maxArea = 0;
for (int i = 0; i < h.length; i++) {
while (!stack.isEmpty() && h[i] < h[stack.peek()]) {
int height = h[stack.pop()];
int width = i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
}
python
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
# 前后加0作为哨兵:
# - 前面的0保证栈永远不会空(计算width时总有stack[-1]可用)
# - 后面的0保证循环结束时所有柱子都会被弹出处理
h = [0] + heights + [0]
stack, max_area = [], 0
for i, cur_h in enumerate(h):
# 当前高度 < 栈顶高度 → 以栈顶柱子为高的矩形已经确定右边界
while stack and cur_h < h[stack[-1]]:
height = h[stack.pop()]
# width = 右边界(i) - 左边界(新栈顶) - 1
# 注意:左边界是新栈顶(比当前height更矮的那个),中间的都已出栈
width = i - stack[-1] - 1
max_area = max(max_area, height * width)
stack.append(i)
return max_area
十三、堆
74. 数组中的第K个最大元素(中等)
题目描述: 找出数组中第 k 大的元素。
解题思路: 大小为 k 的最小堆。遍历数组元素入堆,堆大小超过 k 时弹出最小元素。最终堆顶即为第 k 大元素。O(nlogk)。
java
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 最小堆
for (int num : nums) {
pq.offer(num);
if (pq.size() > k) pq.poll();
}
return pq.peek();
}
}
python
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
import heapq
heap = []
for num in nums:
heapq.heappush(heap, num)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
75. 前 K 个高频元素(中等)
题目描述: 找出出现频率前 k 高的元素。
解题思路: 先用哈希表统计频率,然后用大小为 k 的最小堆按频率排序,保持堆中为 k 个最高频元素。O(nlogk)。
java
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> freq = new HashMap<>();
for (int num : nums) freq.put(num, freq.getOrDefault(num, 0) + 1);
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> freq.get(a) - freq.get(b));
for (int num : freq.keySet()) {
pq.offer(num);
if (pq.size() > k) pq.poll();
}
int[] res = new int[k];
for (int i = k - 1; i >= 0; i--) res[i] = pq.poll();
return res;
}
}
python
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
import heapq
freq = {}
for num in nums:
freq[num] = freq.get(num, 0) + 1
heap = []
for num, cnt in freq.items():
heapq.heappush(heap, (cnt, num))
if len(heap) > k:
heapq.heappop(heap)
return [num for _, num in heap]
76. 数据流的中位数(困难)
题目描述: 实现 MedianFinder,能动态添加数字并随时返回中位数。
解题思路: 双堆法。最大堆存较小一半元素,最小堆存较大一半元素。保持两堆大小差 <= 1。中位数 = 两堆顶的平均值(偶数)或较小一半的堆顶(奇数)。
java
class MedianFinder {
PriorityQueue<Integer> left; // 最大堆(存较小一半)
PriorityQueue<Integer> right; // 最小堆(存较大一半)
public MedianFinder() {
left = new PriorityQueue<>((a, b) -> b - a);
right = new PriorityQueue<>();
}
public void addNum(int num) {
left.offer(num);
right.offer(left.poll());
if (right.size() > left.size()) left.offer(right.poll());
}
public double findMedian() {
if (left.size() > right.size()) return left.peek();
return (left.peek() + right.peek()) / 2.0;
}
}
python
class MedianFinder:
def __init__(self):
# Python heapq 是最小堆,要模拟最大堆需要将值取负
self.left = [] # 最大堆(取负值),存较小一半;堆顶是较小一半的最大值
self.right = [] # 最小堆,存较大一半;堆顶是较大一半的最小值
def addNum(self, num: int) -> None:
# 新元素先放入 left(较小一半),再从 left 弹出最大值放入 right
# 这样保证 left 中元素始终 <= right 中元素
heapq.heappush(self.left, -num)
heapq.heappush(self.right, -heapq.heappop(self.left))
# 平衡:如果 right 比 left 多,把 right 最小的移回 left
# 最终 left.size >= right.size,中位数从 left 取(或平均)
if len(self.right) > len(self.left):
heapq.heappush(self.left, -heapq.heappop(self.right))
def findMedian(self) -> float:
if len(self.left) > len(self.right):
return -self.left[0] # 注意取负恢复原值
return (-self.left[0] + self.right[0]) / 2
十四、贪心算法
77. 买卖股票的最佳时机(简单)
题目描述: 找出一天买入、一天卖出能获得的最大利润。
解题思路: 遍历中维护历史最低价格 minPrice。每天计算 price - minPrice 并更新最大利润。O(n)。
java
class Solution {
public int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE, maxProfit = 0;
for (int price : prices) {
minPrice = Math.min(minPrice, price);
maxProfit = Math.max(maxProfit, price - minPrice);
}
return maxProfit;
}
}
python
class Solution:
def maxProfit(self, prices: List[int]) -> int:
min_price = float('inf')
max_profit = 0
for price in prices:
min_price = min(min_price, price)
max_profit = max(max_profit, price - min_price)
return max_profit
78. 跳跃游戏(中等)
题目描述: 判断从数组第一个位置出发,能否到达最后一个位置。
解题思路: 贪心,维护最远可达位置 maxReach。遍历时若 i > maxReach 返回 false;否则更新 maxReach = max(maxReach, i + nums[i])。
java
class Solution {
public boolean canJump(int[] nums) {
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = Math.max(maxReach, i + nums[i]);
}
return true;
}
}
python
class Solution:
def canJump(self, nums: List[int]) -> bool:
max_reach = 0
for i, num in enumerate(nums):
if i > max_reach:
return False
max_reach = max(max_reach, i + num)
return True
79. 跳跃游戏 II(中等)
题目描述: 求从数组第一个位置到最后一个位置的最少跳跃次数。
解题思路: 贪心 BFS。curEnd 当前步能到的最远位置,curMax 下一步能到的最远位置。当 i == curEnd 时步数 +1。
java
class Solution {
public int jump(int[] nums) {
int jumps = 0, curEnd = 0, curMax = 0;
for (int i = 0; i < nums.length - 1; i++) {
curMax = Math.max(curMax, i + nums[i]);
if (i == curEnd) {
jumps++;
curEnd = curMax;
}
}
return jumps;
}
}
python
class Solution:
def jump(self, nums: List[int]) -> int:
jumps = cur_end = cur_max = 0
# 只遍历到 n-2:到达 n-2 时,最后一步必然能跳到 n-1(题目保证可达)
# 如果遍历到 n-1,会在最后一个位置多算一次跳跃
for i in range(len(nums) - 1):
# cur_max:当前这一步和之前所有步中,能跳到的最远位置
cur_max = max(cur_max, i + nums[i])
# cur_end:当前跳跃步数所能覆盖的边界
# 走到边界时,必须再跳一步(步数+1),更新边界为新的最远可达位置
if i == cur_end:
jumps += 1
cur_end = cur_max
return jumps
80. 划分字母区间(中等)
题目描述: 将字符串划分为尽可能多的片段,使同一字母只出现在一个片段中。
解题思路: 先记录每个字母最后出现的位置 last[]。再遍历,维护当前片段的结束位置 end。当 i == end 时即为片段终点。
java
class Solution {
public List<Integer> partitionLabels(String s) {
int[] last = new int[26];
for (int i = 0; i < s.length(); i++) last[s.charAt(i) - 'a'] = i;
List<Integer> res = new ArrayList<>();
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
end = Math.max(end, last[s.charAt(i) - 'a']);
if (i == end) {
res.add(end - start + 1);
start = i + 1;
}
}
return res;
}
}
python
class Solution:
def partitionLabels(self, s: str) -> List[int]:
last = {ch: i for i, ch in enumerate(s)}
res, start, end = [], 0, 0
for i, ch in enumerate(s):
end = max(end, last[ch])
if i == end:
# 当前片段结束,记录长度
res.append(end - start + 1)
# 下一个片段的起始位置是 i+1(不是 end+1)
# 因为 end 只是当前片段内字母的最后出现位置,片段边界就是 i(= end)
start = i + 1
return res
十五、动态规划
81. 爬楼梯(简单)
题目描述: 每次爬 1 或 2 阶,共有多少种方式爬到第 n 阶。
解题思路: dp[i] = dp[i-1] + dp[i-2],即斐波那契数列。可优化为 O(1) 空间:a, b = 1, 1,每次 a, b = b, a + b。
java
class Solution {
public int climbStairs(int n) {
int a = 1, b = 1;
for (int i = 2; i <= n; i++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
}
python
class Solution:
def climbStairs(self, n: int) -> int:
a = b = 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
82. 杨辉三角(简单)
题目描述: 生成杨辉三角前 numRows 行。
解题思路: 迭代。每行首尾为 1,中间元素为上一行相邻两数之和。
java
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
List<Integer> row = new ArrayList<>();
row.add(1);
for (int j = 1; j < i; j++)
row.add(res.get(i - 1).get(j - 1) + res.get(i - 1).get(j));
if (i > 0) row.add(1);
res.add(row);
}
return res;
}
}
python
class Solution:
def generate(self, numRows: int) -> List[List[int]]:
res = []
for i in range(numRows):
row = [1] * (i + 1)
for j in range(1, i):
row[j] = res[i - 1][j - 1] + res[i - 1][j]
res.append(row)
return res
83. 打家劫舍(中等)
题目描述: 不能偷相邻房屋,求最大金额。
解题思路: dp[i] = max(dp[i-1], dp[i-2] + nums[i])。偷或不偷第 i 家。可简化为两个变量 prev2(i-2)和 prev1(i-1)。
java
class Solution {
public int rob(int[] nums) {
int prev2 = 0, prev1 = 0;
for (int num : nums) {
int cur = Math.max(prev1, prev2 + num);
prev2 = prev1;
prev1 = cur;
}
return prev1;
}
}
python
class Solution:
def rob(self, nums: List[int]) -> int:
prev2 = prev1 = 0
for num in nums:
cur = max(prev1, prev2 + num)
prev2, prev1 = prev1, cur
return prev1
84. 完全平方数(中等)
题目描述: 找到最少的完全平方数个数,使其和等于 n。
解题思路: BFS 按层找最小步数,或 DP:dp[i] = min(dp[i - j*j]) + 1,j 从 1 到 sqrt(i)。
java
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j * j <= i; j++)
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
return dp[n];
}
}
python
class Solution:
def numSquares(self, n: int) -> int:
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
j = 1
while j * j <= i:
dp[i] = min(dp[i], dp[i - j * j] + 1)
j += 1
return dp[n]
85. 零钱兑换(中等)
题目描述: 用最少数量的硬币凑出目标金额,不限使用次数。
解题思路: DP:dp[i] = min(dp[i - coin]) + 1。dpi 表示凑出金额 i 的最少硬币数。初始化为大数。
java
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++)
for (int coin : coins)
if (i >= coin) dp[i] = Math.min(dp[i], dp[i - coin] + 1);
return dp[amount] > amount ? -1 : dp[amount];
}
}
python
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [amount + 1] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if i >= coin:
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] <= amount else -1
86. 单词拆分(中等)
题目描述: 判断字符串 s 是否可以由 wordDict 中的单词拼接而成。
解题思路: DP:dp[i] 表示 s0:i 能否拆分为字典中的单词。对于每个 j < i,若 dp[j] 且 s[j:i] 在字典中,则 dp[i] = true。
java
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> set = new HashSet<>(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++)
for (int j = 0; j < i; j++)
if (dp[j] && set.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
return dp[s.length()];
}
}
python
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
word_set = set(wordDict)
dp = [False] * (len(s) + 1)
dp[0] = True
for i in range(1, len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[len(s)]
87. 最长递增子序列(中等)
题目描述: 找出最长严格递增子序列的长度(不要求连续)。
解题思路: tails 数组:tails[i] 表示长度为 i+1 的递增子序列的最小尾元素。二分查找更新 tails。O(nlogn)。
java
class Solution {
public int lengthOfLIS(int[] nums) {
List<Integer> tails = new ArrayList<>();
for (int num : nums) {
int idx = Collections.binarySearch(tails, num);
if (idx < 0) idx = -idx - 1;
if (idx == tails.size()) tails.add(num);
else tails.set(idx, num);
}
return tails.size();
}
}
python
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
import bisect
# tails[i] = 长度为 i+1 的递增子序列的最小尾元素
# tails 本身是有序的,因此可以用二分查找
tails = []
for num in nums:
# bisect_left:找到第一个 >= num 的位置
# 如果 num 比所有 tails 都大,idx == len(tails),扩展 tails
# 否则,替换 tails[idx]:用更小的尾元素更新等长子序列
# 替换不会影响当前 LIS 长度,但为未来更长的子序列铺路
idx = bisect.bisect_left(tails, num)
if idx == len(tails):
tails.append(num)
else:
tails[idx] = num
return len(tails)
88. 乘积最大子数组(中等)
题目描述: 找出乘积最大的连续子数组。
解题思路: 维护当前最大乘积 curMax 和最小乘积 curMin(因为负数乘以最小可能变最大)。遇到负数时交换两者。
java
class Solution {
public int maxProduct(int[] nums) {
int curMax = nums[0], curMin = nums[0], result = nums[0];
for (int i = 1; i < nums.length; i++) {
if (nums[i] < 0) {
int tmp = curMax;
curMax = curMin;
curMin = tmp;
}
curMax = Math.max(nums[i], curMax * nums[i]);
curMin = Math.min(nums[i], curMin * nums[i]);
result = Math.max(result, curMax);
}
return result;
}
}
python
class Solution:
def maxProduct(self, nums: List[int]) -> int:
cur_max = cur_min = result = nums[0]
for num in nums[1:]:
# 关键:遇到负数时,最大值和最小值互换
# 因为 cur_max * 负数 → 变最小,cur_min * 负数 → 变最大
# 不交换的话,cur_max = max(num, cur_max * num) 会丢失"负负得正"的情况
if num < 0:
cur_max, cur_min = cur_min, cur_max
# 注意:cur_max/cur_min 都要与 num 自身比较(可以重新开始一个子数组)
cur_max = max(num, cur_max * num)
cur_min = min(num, cur_min * num)
result = max(result, cur_max)
return result
89. 分割等和子集(中等)
题目描述: 能否将数组分割成两个和相等的子集。
解题思路: 0-1 背包变形。若总和为奇数返回 false。转化为能否选出若干数使和等于 sum/2。dp[j] 表示能否凑出和为 j。
java
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) sum += num;
if (sum % 2 != 0) return false;
int target = sum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums)
for (int j = target; j >= num; j--)
dp[j] |= dp[j - num];
return dp[target];
}
}
python
class Solution:
def canPartition(self, nums: List[int]) -> bool:
total = sum(nums)
if total % 2 != 0: return False
target = total // 2
dp = [False] * (target + 1)
dp[0] = True
for num in nums:
for j in range(target, num - 1, -1):
dp[j] = dp[j] or dp[j - num]
return dp[target]
90. 最长有效括号(困难)
题目描述: 找出只含 '(' 和 ')' 的字符串中最长有效括号子串长度。
解题思路: DP:dp[i] 表示以 si 结尾的最长有效括号长度。若 s[i]=')' 且 s[i-1]='(' 则 dp[i]=dp[i-2]+2;若 s[i]=')' 且 s[i-1]=')' 且 s[i-1-dp[i-1]]='(',则 dp[i]=dp[i-1]+2+dp[i-2-dp[i-1]]。
java
class Solution {
public int longestValidParentheses(String s) {
int n = s.length(), maxLen = 0;
int[] dp = new int[n];
for (int i = 1; i < n; i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i - 1) == '(') {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i] = dp[i - 1] + 2;
if (i - dp[i - 1] >= 2)
dp[i] += dp[i - dp[i - 1] - 2];
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}
}
python
class Solution:
def longestValidParentheses(self, s: str) -> int:
n, max_len = len(s), 0
dp = [0] * n # dp[i] = 以 s[i] 结尾的最长有效括号长度
for i in range(1, n):
if s[i] == ')': # 只有右括号才可能形成有效子串
if s[i - 1] == '(':
# 情况1: ...(),直接配对,加上之前的结果
dp[i] = (dp[i - 2] if i >= 2 else 0) + 2
elif i - dp[i - 1] > 0 and s[i - dp[i - 1] - 1] == '(':
# 情况2: ...)),需要跳过 dp[i-1] 的长度去找配对
# i - dp[i-1] - 1:跳过"以 i-1 结尾的有效括号子串"后的前一个位置
# 如果那个位置是 '(',就能和当前的 ')' 配对,把两段拼起来
dp[i] = dp[i - 1] + 2
# 再加上更前面可能存在的有效括号段
if i - dp[i - 1] >= 2:
dp[i] += dp[i - dp[i - 1] - 2]
max_len = max(max_len, dp[i])
return max_len
十六、多维动态规划
91. 不同路径(中等)
题目描述: 在 m×n 网格中从左上角走到右下角的不同路径数(只能向右或向下)。
解题思路: DP:dp[i][j] = dp[i-1][j] + dp[i][j-1]。可优化为一维数组:dp[j] += dp[j-1]。
java
class Solution {
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++)
for (int j = 1; j < n; j++)
dp[j] += dp[j - 1];
return dp[n - 1];
}
}
python
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [1] * n
for _ in range(1, m):
for j in range(1, n):
dp[j] += dp[j - 1]
return dp[-1]
92. 最小路径和(中等)
题目描述: 找出网格从左上到右下的最小路径和(只能向右或向下)。
解题思路: DP:dp[j] = min(dp[j], dp[j-1]) + grid[i][j]。原地或一维数组。
java
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[] dp = new int[n];
dp[0] = grid[0][0];
for (int j = 1; j < n; j++) dp[j] = dp[j - 1] + grid[0][j];
for (int i = 1; i < m; i++) {
dp[0] += grid[i][0];
for (int j = 1; j < n; j++)
dp[j] = Math.min(dp[j], dp[j - 1]) + grid[i][j];
}
return dp[n - 1];
}
}
python
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
dp = [0] * n
dp[0] = grid[0][0]
for j in range(1, n):
dp[j] = dp[j - 1] + grid[0][j]
for i in range(1, m):
dp[0] += grid[i][0]
for j in range(1, n):
dp[j] = min(dp[j], dp[j - 1]) + grid[i][j]
return dp[-1]
93. 最长回文子串(中等)
题目描述: 找出字符串中最长的回文子串。
解题思路: 中心扩散法。从每个字符(或两个字符之间)向两边扩展。O(n^2) 时间,O(1) 空间。
java
class Solution {
public String longestPalindrome(String s) {
int start = 0, maxLen = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expand(s, i, i);
int len2 = expand(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > maxLen) {
maxLen = len;
start = i - (len - 1) / 2;
}
}
return s.substring(start, start + maxLen);
}
int expand(String s, int l, int r) {
while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
l--; r++;
}
return r - l - 1;
}
}
python
class Solution:
def longestPalindrome(self, s: str) -> str:
start, max_len = 0, 0
for i in range(len(s)):
for l, r in [(i, i), (i, i + 1)]:
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1; r += 1
length = r - l - 1
if length > max_len:
max_len = length
start = l + 1
return s[start:start + max_len]
94. 最长公共子序列(中等)
题目描述: 求两个字符串的最长公共子序列长度(LCS)。
解题思路: DP:dp[i][j] = 若 a[i-1]==b[j-1] 则 dp[i-1][j-1]+1,否则 max(dp[i-1][j], dp[i][j-1])。可优化为一维数组 + 一个变量存左上角值。
java
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[] dp = new int[n + 1];
for (int i = 1; i <= m; i++) {
int prev = 0; // 存 dp[i-1][j-1]
for (int j = 1; j <= n; j++) {
int temp = dp[j];
if (text1.charAt(i - 1) == text2.charAt(j - 1))
dp[j] = prev + 1;
else
dp[j] = Math.max(dp[j], dp[j - 1]);
prev = temp;
}
}
return dp[n];
}
}
python
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [0] * (n + 1)
for i in range(1, m + 1):
prev = 0 # dp[i-1][j-1]
for j in range(1, n + 1):
temp = dp[j]
if text1[i - 1] == text2[j - 1]:
dp[j] = prev + 1
else:
dp[j] = max(dp[j], dp[j - 1])
prev = temp
return dp[n]
95. 编辑距离(中等)
题目描述: 将 word1 转换为 word2 的最少操作数(插入/删除/替换)。
解题思路: DP:若 w1[i-1]==w2[j-1] 则 dp[i][j]=dp[i-1][j-1];否则 dp[i][j]=1+min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])。
java
class Solution {
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[] dp = new int[n + 1];
for (int j = 0; j <= n; j++) dp[j] = j;
for (int i = 1; i <= m; i++) {
int prev = dp[0]; // dp[i-1][j-1]
dp[0] = i;
for (int j = 1; j <= n; j++) {
int temp = dp[j];
if (word1.charAt(i - 1) == word2.charAt(j - 1))
dp[j] = prev;
else
dp[j] = 1 + Math.min(Math.min(dp[j], dp[j - 1]), prev);
prev = temp;
}
}
return dp[n];
}
}
python
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
dp = list(range(n + 1)) # dp[j] 对应二维 dp[i][j]
for i in range(1, m + 1):
# prev 存储二维 DP 中的 dp[i-1][j-1](左上角/对角线值)
# 因为 1D 滚动数组只保留上一行,prev 需要在更新前单独保存
prev = dp[0] # 初始时 prev = dp[i-1][0](上一行的第0列)
dp[0] = i # 更新 dp[i][0] = i
for j in range(1, n + 1):
temp = dp[j] # 保存旧的 dp[j](即 dp[i-1][j]),下一轮它就是 prev
if word1[i - 1] == word2[j - 1]:
dp[j] = prev # 字符相等:不需要操作,取对角线值
else:
# dp[j] → dp[i-1][j] (删除)
# dp[j-1] → dp[i][j-1] (插入)
# prev → dp[i-1][j-1] (替换)
dp[j] = 1 + min(dp[j], dp[j - 1], prev)
prev = temp # 更新 prev 为下一列的"左上角"
return dp[n]
十七、技巧
96. 只出现一次的数字(简单)
题目描述: 找出数组中只出现一次的数字(其余都出现两次)。
解题思路: 异或。a ^ a = 0,a ^ 0 = a。所有元素异或后即为只出现一次的数。
java
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int num : nums) res ^= num;
return res;
}
}
python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0
for num in nums:
res ^= num
return res
97. 多数元素(简单)
题目描述: 找出出现次数 > n/2 的元素。
解题思路: Boyer-Moore 投票算法。维护候选人和计数器。遇相同数则 count++,遇不同则 count--;count=0 时换候选人。最终候选人即多数元素。
java
class Solution {
public int majorityElement(int[] nums) {
int candidate = 0, count = 0;
for (int num : nums) {
if (count == 0) candidate = num;
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
}
python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
candidate, count = 0, 0
for num in nums:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
return candidate
98. 颜色分类(中等)
题目描述: 对仅含 0/1/2 的数组原地排序(荷兰国旗问题)。
解题思路: 三指针。p0(0的边界)、p2(2的边界)、i(当前)。nums[i]=0则与 p0 交换,nums[i]=2则与 p2 交换,nums[i]=1则 i++。
java
class Solution {
public void sortColors(int[] nums) {
int p0 = 0, p2 = nums.length - 1, i = 0;
while (i <= p2) {
if (nums[i] == 0) {
swap(nums, i, p0);
p0++; i++; // 与p0交换过来的数已经处理过,放心i++
} else if (nums[i] == 2) {
swap(nums, i, p2);
p2--; // 与p2交换过来的数未知,i不能前进
} else {
i++;
}
}
}
void swap(int[] nums, int a, int b) {
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}
}
python
class Solution:
def sortColors(self, nums: List[int]) -> None:
p0, p2, i = 0, len(nums) - 1, 0
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 # 与p2交换过来的数未知,i不能前进
else:
i += 1
99. 下一个排列(中等)
题目描述: 将数组原地修改为字典序中的下一个更大排列。
解题思路: ①从右向左找到第一个 nums[i] < nums[i+1] 的位置 i;②在 i 右侧找比 numsi 大的最小元素并交换;③反转 i+1 到末尾。若找不到 i,整个数组反转。
java
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) i--;
if (i >= 0) {
int j = nums.length - 1;
while (j > i && nums[j] <= nums[i]) j--;
swap(nums, i, j);
}
reverse(nums, i + 1, nums.length - 1);
}
void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }
void reverse(int[] a, int l, int r) {
while (l < r) swap(a, l++, r--);
}
}
python
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
# 步骤1: 从右向左找第一个下降点 i(nums[i] < nums[i+1])
# 如果找不到(i == -1),说明数组是降序的,直接反转整个数组
i = len(nums) - 2
while i >= 0 and nums[i] >= nums[i + 1]:
i -= 1
if i >= 0:
# 步骤2: 在 i 右侧找比 nums[i] 大的最小元素(从右向左找第一个 > nums[i] 的)
j = len(nums) - 1
while j > i and nums[j] <= nums[i]:
j -= 1
nums[i], nums[j] = nums[j], nums[i]
# 步骤3: 反转 i+1 到末尾
# 为什么反转?交换后 i+1 到末尾仍是降序的(从右向左第一个上升点保证的)
# 要让排列变成"下一个",需要让后半段变成最小的升序排列 → 反转即可
l, r = i + 1, len(nums) - 1
while l < r:
nums[l], nums[r] = nums[r], nums[l]
l += 1; r -= 1
100. 寻找重复数(中等)
题目描述: 在 n+1 个数(取值 1~n)中找唯一的重复数,O(1) 额外空间。
解题思路: 快慢指针(环形链表 II 思路)。将数组值视为 next 指针,因为至少存在一个重复数所以一定有环。先找到相遇点,再从头和相遇点各走一步相遇即为重复数。
java
class Solution {
public int findDuplicate(int[] nums) {
// 将数组视为链表:nums[i] 是 i→nums[i] 的指针,有重复数=有环
// 用 Floyd 判圈找环入口(同环形链表 II)
int slow = nums[0], fast = nums[0];
do {
slow = nums[slow]; // 一步
fast = nums[nums[fast]]; // 两步
} while (slow != fast);
int p = nums[0];
while (p != slow) {
p = nums[p];
slow = nums[slow];
}
return p;
}
}
python
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
# 将数组视为链表:nums[i] 表示从节点 i 指向节点 nums[i] 的指针
# 因为有重复数,至少两个节点指向同一个节点 → 形成环
# 重复数就是环的入口(环形链表 II 的 Floyd 判圈算法)
slow = fast = nums[0]
# 阶段1: 快慢指针找相遇点(环内任意一点)
while True:
slow = nums[slow] # 走一步
fast = nums[nums[fast]] # 走两步
if slow == fast:
break
# 阶段2: 从头和相遇点同时出发,相遇点就是环入口(重复数)
# 数学证明:head到环入口 = 相遇点到环入口
p = nums[0]
while p != slow:
p = nums[p]
slow = nums[slow]
return p
本文档共 100 题,涵盖了 LeetCode Hot 100 最新版全部题目,按算法分类整理。每题只给出一种最适合面试记忆和理解的方法,并附 Java 和 Python 实现。