【leetcodehot100】leetcode-hot100-20260809更新

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. 接雨水(困难)

题目描述: 给定高度数组表示柱子,计算下雨后能接多少雨水。

解题思路(双指针法,易于记忆): 左右双指针,维护 leftMaxrightMax 记录左右两侧最高柱子。哪边低就处理哪边:当前高度低于该侧最大值时,差值就是当前列的雨水。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. 反转链表(简单)

题目描述: 反转单链表。

解题思路: 迭代法:precurnext 三指针。next = cur.nextcur.next = prepre = curcur = 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.vall.leftr.right 对称、l.rightr.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/2dp[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 = 0a ^ 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 实现。

相关推荐
数模竞赛Paid answer1 小时前
2021年深圳杯数学建模A题火星探测器着陆控制方案解题全过程论文及程序
算法·数学建模·深圳杯
hold?fish:palm1 小时前
19 螺旋矩阵
线性代数·算法·矩阵
码完就睡2 小时前
数据结构——树、二叉树基础概念
数据结构·算法
VL——MOESR3 小时前
【LuoguP1967】货车运输【生成树】【倍增】
c++·算法·题解·倍增·生成树
手写码匠3 小时前
华为云Flexus+DeepSeek征文|Agent 记忆系统实战:用 DeepSeek-R1/V3 + Dify 会话变量打造跨会话长期记忆
人工智能·深度学习·算法·aigc
Olafur_zbj3 小时前
【AI】CUDA编程中的维度
人工智能·算法
坚持编程的菜鸟4 小时前
模拟实现memcpy
c语言·算法·模拟实现my_memcpy
wabs6664 小时前
关于图论【最短路径之Bellman_ford 算法|卡码网94.城市间货物运输的思考】
数据结构·算法·图论·卡码网·bellman_ford·求最短路径
MrZhao4004 小时前
On-Policy Distillation(OPD):为什么大模型后训练要在学生自己的轨迹上蒸馏?
算法