LeetCode51~75题目解析

LeetCode 51-75 题解合集

文章目录


51. N-Queens(N 皇后)

题目描述

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n,返回所有不同的 n 皇后问题的解决方案 。每一种解法包含一个不同的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例:

复制代码
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]

约束: 1 <= n <= 9

解题思路

核心思想:回溯逐行放置皇后,用列、主对角线、副对角线三组标记剪枝。

解法:回溯 + 对角线标记(推荐) 。逐行 row 遍历每一列 col:用 cols[col]、diag[row+col](主对角线)、antiDiag[row-col+n-1](副对角线,加偏移避免负数下标)判断是否可放。可放则放置并递归下一行。行号天然避免同行冲突。n≤9,组合数有限。时间 O(n!),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    private List<List<String>> res = new ArrayList<List<String>>();
    private boolean[] cols, diag, antiDiag;
    private char[][] board;

    // 回溯 + 对角线标记(推荐)
    public List<List<String>> solveNQueens(int n) {
        cols = new boolean[n];
        diag = new boolean[2 * n];
        antiDiag = new boolean[2 * n];
        board = new char[n][n];
        for (char[] row : board) java.util.Arrays.fill(row, '.');
        dfs(0, n);
        return res;
    }

    private void dfs(int row, int n) {
        if (row == n) { // 全部行都放好
            List<String> list = new ArrayList<String>();
            for (char[] r : board) list.add(new String(r));
            res.add(list);
            return;
        }
        for (int col = 0; col < n; col++) {
            int d = row + col, ad = row - col + n; // 主/副对角线索引
            if (cols[col] || diag[d] || antiDiag[ad]) continue;
            board[row][col] = 'Q';
            cols[col] = diag[d] = antiDiag[ad] = true;
            dfs(row + 1, n);
            board[row][col] = '.';
            cols[col] = diag[d] = antiDiag[ad] = false; // 回溯
        }
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.solveNQueens(4)); // [[.Q..,...Q,Q...,..Q.],[..Q.,Q...,...Q,.Q..]]
        System.out.println(s.solveNQueens(1)); // [[Q]]
    }
}

复杂度分析

  • 时间复杂度:O(n!) --- 每行尝试 n 个位置,剪枝大幅削减
  • 空间复杂度:O(n) --- 标记数组 + 递归栈

52. N-Queens II(N 皇后 II)

题目描述

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n,返回 n 皇后问题不同的解决方案的数量。

示例:

复制代码
输入:n = 4
输出:2

约束: 1 <= n <= 9

解题思路

核心思想:与 51 题完全相同的回溯,但只用计数不用记录棋盘。

解法:回溯计数(推荐) 。同上题的三组布尔标记,dfs(row) 每放一个合法位置就累加计数,不再构造棋盘。时间 O(n!),空间 O(n)。

代码实现

java 复制代码
class Solution {
    private int count = 0;
    private boolean[] cols, diag, antiDiag;

    // 回溯计数(推荐)
    public int totalNQueens(int n) {
        cols = new boolean[n];
        diag = new boolean[2 * n];
        antiDiag = new boolean[2 * n];
        dfs(0, n);
        return count;
    }

    private void dfs(int row, int n) {
        if (row == n) { count++; return; }
        for (int col = 0; col < n; col++) {
            int d = row + col, ad = row - col + n;
            if (cols[col] || diag[d] || antiDiag[ad]) continue;
            cols[col] = diag[d] = antiDiag[ad] = true;
            dfs(row + 1, n);
            cols[col] = diag[d] = antiDiag[ad] = false;
        }
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.totalNQueens(4)); // 2
        System.out.println(s.totalNQueens(1)); // 1
    }
}

复杂度分析

  • 时间复杂度:O(n!) --- 同 51 题
  • 空间复杂度:O(n) --- 标记数组

53. Maximum Subarray(最大子数组和)

题目描述

给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。

示例:

复制代码
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6。

约束: 1 <= nums.length <= 10^5,-10^4 <= nums[i] <= 10^4

解题思路

核心思想:贪心/Kadane 算法------遍历时维持"以当前位置结尾的最大子数组和",若前面积累为负则果断丢弃重新开始。

解法一:Kadane 动态规划(推荐) 。cur = max(nums[i], cur + nums[i])(要么从 numsi 重新开始,要么接上前面的最佳累加),ans = max(ans, cur)。时间 O(n),空间 O(1)。

解法二:分治。把数组一分为二,最大子数组要么完全在左/右半区,要么跨越中点(从中点向两侧扩展求最大和再拼接)。时间 O(n log n),空间 O(log n)。作为对比写法。

代码实现

java 复制代码
class Solution {
    // 解法一:Kadane(推荐)
    public int maxSubArray(int[] nums) {
        int cur = 0, ans = Integer.MIN_VALUE;
        for (int x : nums) {
            cur = Math.max(x, cur + x); // 重新开始 or 延续累加
            ans = Math.max(ans, cur);
        }
        return ans;
    }

    // 解法二:分治
    public int maxSubArrayDC(int[] nums) {
        return divide(nums, 0, nums.length - 1);
    }
    private int divide(int[] a, int l, int r) {
        if (l == r) return a[l];
        int mid = (l + r) / 2;
        int leftBest = divide(a, l, mid);
        int rightBest = divide(a, mid + 1, r);
        // 跨中点:向左扩展 + 向右扩展
        int leftSum = 0, sum = 0;
        for (int i = mid; i >= l; i--) { sum += a[i]; leftSum = Math.max(leftSum, sum); }
        int rightSum = 0; sum = 0;
        for (int i = mid + 1; i <= r; i++) { sum += a[i]; rightSum = Math.max(rightSum, sum); }
        return Math.max(Math.max(leftBest, rightBest), leftSum + rightSum);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.maxSubArray(new int[]{-2,1,-3,4,-1,2,1,-5,4})); // 6
        System.out.println(s.maxSubArray(new int[]{1}));                     // 1
        System.out.println(s.maxSubArray(new int[]{-1}));                    // -1
    }
}

复杂度分析

解法 时间复杂度 空间复杂度 说明
Kadane(推荐) O(n) O(1) 一维滚动
分治 O(n log n) O(log n) 递归栈

54. Spiral Matrix(螺旋矩阵)

题目描述

给你一个 m 行 n 列的矩阵 matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。

示例:

复制代码
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

约束: m == matrix.length,n == matrix[i].length,1 <= m, n <= 10,-100 <= matrix[i][j] <= 100

解题思路

核心思想:用上下左右四个边界不断收缩,按 右→下→左→上 循环输出。

解法:边界收缩(推荐) 。维护 top, bottom, left, right 四条边界与方向。每次沿当前方向收集元素,走完一条边就收缩对应边界;边界交叉即结束。时间 O(m×n),空间 O(1)(不含输出)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    // 边界收缩(推荐)
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<Integer>();
        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 || left > right) break; // 防止单行/单列重复
            for (int j = right; j >= left; j--) res.add(matrix[bottom][j]); // 下边←
            bottom--;
            for (int i = bottom; i >= top; i--) res.add(matrix[i][left]); // 左边↑
            left++;
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.spiralOrder(new int[][]{{1,2,3},{4,5,6},{7,8,9}})); // [1,2,3,6,9,8,7,4,5]
        System.out.println(s.spiralOrder(new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12}})); // 螺旋输出
    }
}

复杂度分析

  • 时间复杂度:O(m×n) --- 每个元素访问一次
  • 空间复杂度:O(1) --- 不含输出列表

55. Jump Game(跳跃游戏)

题目描述

给你一个非负整数数组 nums,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以返回 true,否则返回 false。

示例:

复制代码
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1,然后再从下标 1 跳 3 步到达最后一个下标。
输入:nums = [3,2,1,0,4]
输出:false

约束: 1 <= nums.length <= 10^4,0 <= nums[i] <= 10^5

解题思路

核心思想:贪心维护"最远可达位置",只要可覆盖到终点即可。

解法:贪心(推荐) 。遍历每个位置 i:若 i > farthest 说明中途断档,返回 false;否则更新 farthest = max(farthest, i + nums[i])。遍历完若能覆盖 n-1 则 true。时间 O(n),空间 O(1)。

代码实现

java 复制代码
class Solution {
    // 贪心(推荐)
    public boolean canJump(int[] nums) {
        int farthest = 0;
        for (int i = 0; i < nums.length; i++) {
            if (i > farthest) return false; // 当前位置已不可达
            farthest = Math.max(farthest, i + nums[i]); // 扩展最远可达
        }
        return true;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.canJump(new int[]{2,3,1,1,4})); // true
        System.out.println(s.canJump(new int[]{3,2,1,0,4})); // false
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 单次遍历
  • 空间复杂度:O(1)

56. Merge Intervals(合并区间)

题目描述

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例:

复制代码
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠,合并为 [1,6]

约束: 1 <= intervals.length <= 10^4,intervals[i].length == 2,0 <= starti <= endi <= 10^4

解题思路

核心思想:先按区间起点排序,再线性合并重叠区间。

解法:排序 + 线性合并(推荐) 。按 starti 升序排序。遍历时维护当前合并区间 [curStart, curEnd]:若新区间起点 <= curEnd 则重叠,curEnd = max(curEnd, 新end);否则把当前区间加入结果并重置。时间 O(n log n)(排序主导),空间 O(log n)(排序栈,不含输出)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    // 排序 + 线性合并(推荐)
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]); // 按起点排序
        List<int[]> res = new ArrayList<int[]>();
        int curStart = intervals[0][0], curEnd = intervals[0][1];
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] <= curEnd) {           // 重叠(含相接)
                curEnd = Math.max(curEnd, intervals[i][1]);
            } else {
                res.add(new int[]{curStart, curEnd});  // 结束当前区间
                curStart = intervals[i][0];
                curEnd = intervals[i][1];
            }
        }
        res.add(new int[]{curStart, curEnd});
        return res.toArray(new int[res.size()][]);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(Arrays.deepToString(s.merge(new int[][]{{1,3},{2,6},{8,10},{15,18}}))); // [[1,6],[8,10],[15,18]]
        System.out.println(Arrays.deepToString(s.merge(new int[][]{{1,4},{4,5}})));                // [[1,5]]
    }
}

复杂度分析

  • 时间复杂度:O(n log n) --- 排序主导
  • 空间复杂度:O(log n) --- 排序栈空间,不含输出

57. Insert Interval(插入区间)

题目描述

给你一个无重叠 的、按照区间起始端点排序的区间列表 intervals,其中 intervals[i] = [starti, endi] 表示第 i 个区间的开始和结束,并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval = [start, end] 表示另一个区间的开始和结束。在 intervals 中插入 newInterval,使 intervals 仍然按照 starti 升序排列,且不重叠(如有必要则合并区间)。返回插入之后的区间列表。

示例:

复制代码
输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]
输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
输出:[[1,2],[3,10],[12,16]]

约束: 0 <= intervals.length <= 10^4,intervals[i].length == 2,0 <= starti <= endi <= 10^5,intervals 互不重叠且有序,newInterval.length == 2

解题思路

核心思想 :一次遍历找出与 newInterval 重叠的所有区间合并,其余原样放入。

解法:三阶段线性扫描(推荐) 。分三段处理:① intervals[i].end < newInterval.start 直接加入结果(nei 在右侧之前);② intervals[i].start > newInterval.end 时把 newInterval 加入并开始收尾(后续原样加入);③ 中间重叠的区间不断取并集 [minStart, maxEnd]。时间 O(n),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    // 三阶段线性扫描(推荐)
    public int[][] insert(int[][] intervals, int[] newInterval) {
        List<int[]> res = new ArrayList<int[]>();
        int i = 0, n = intervals.length;
        // 1. 新区间右侧之前、无重叠的区间
        while (i < n && intervals[i][1] < newInterval[0]) {
            res.add(intervals[i++]);
        }
        // 2. 与 newInterval 重叠的区间合并
        int start = newInterval[0], end = newInterval[1];
        while (i < n && intervals[i][0] <= end) {
            start = Math.min(start, intervals[i][0]);
            end = Math.max(end, intervals[i][1]);
            i++;
        }
        res.add(new int[]{start, end});
        // 3. 剩余区间原样加入
        while (i < n) res.add(intervals[i++]);
        return res.toArray(new int[res.size()][]);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(Arrays.deepToString(s.insert(new int[][]{{1,3},{6,9}}, new int[]{2,5})));        // [[1,5],[6,9]]
        System.out.println(Arrays.deepToString(s.insert(new int[][]{{1,2},{3,5},{6,7},{8,10},{12,16}}, new int[]{4,8}))); // [[1,2],[3,10],[12,16]]
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 一次遍历
  • 空间复杂度:O(n) --- 结果列表

58. Length of Last Word(最后一个单词的长度)

题目描述

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。单词是指仅由字母组成、不包含任何空格字符的最大子字符串。

示例:

复制代码
输入:s = "Hello World"
输出:5
输入:s = "   fly me   to   the moon  "
输出:4

约束: 1 <= s.length <= 10^4,s 仅有英文字母和空格 ' ' 组成,s 中至少存在一个单词

解题思路

核心思想:从末尾向前跳过空格,再数单词字母数。

解法:反向扫描(推荐) 。先 i = s.length()-1 跳过结尾空格;然后从 i 向前数连续非空格字符个数。时间 O(n),空间 O(1)。

代码实现

java 复制代码
class Solution {
    // 反向扫描(推荐)
    public int lengthOfLastWord(String s) {
        int i = s.length() - 1;
        while (i >= 0 && s.charAt(i) == ' ') i--; // 跳过末尾空格
        int len = 0;
        while (i >= 0 && s.charAt(i) != ' ') {    // 数单词
            len++; i--;
        }
        return len;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.lengthOfLastWord("Hello World"));                 // 5
        System.out.println(s.lengthOfLastWord("   fly me   to   the moon  ")); // 4
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 单次反向扫描
  • 空间复杂度:O(1)

59. Spiral Matrix II(螺旋矩阵 II)

题目描述

给你一个正整数 n,生成一个包含 1 到 n² 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix。

示例:

复制代码
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

约束: 1 <= n <= 20

解题思路

核心思想:与 54 题相反------用边界收缩 + 递增计数器填充螺旋矩阵。

解法:边界收缩填充(推荐) 。维护 top/bottom/left/right 四边界,按 右→下→左→上 循环填充 num 1...n²,每条边填完收缩对应边界。时间 O(n²),空间 O(1)(不含输出矩阵)。

代码实现

java 复制代码
import java.util.Arrays;

class Solution {
    // 边界收缩填充(推荐)
    public int[][] generateMatrix(int n) {
        int[][] m = new int[n][n];
        int top = 0, bottom = n - 1, left = 0, right = n - 1;
        int num = 1;
        while (top <= bottom && left <= right) {
            for (int j = left; j <= right; j++) m[top][j] = num++;   // →
            top++;
            for (int i = top; i <= bottom; i++) m[i][right] = num++; // ↓
            right--;
            for (int j = right; j >= left; j--) m[bottom][j] = num++; // ←
            bottom--;
            for (int i = bottom; i >= top; i--) m[i][left] = num++;  // ↑
            left++;
        }
        return m;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(Arrays.deepToString(s.generateMatrix(3))); // [[1,2,3],[8,9,4],[7,6,5]]
        System.out.println(Arrays.deepToString(s.generateMatrix(1))); // [[1]]
    }
}

复杂度分析

  • 时间复杂度:O(n²) --- 填充 n² 个元素
  • 空间复杂度:O(1) --- 不含输出

60. Permutation Sequence(排列序列)

题目描述

给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。按大小顺序列出所有排列情况,并一一标记。给定 n 和 k,返回第 k 个排列。题目保证 1 <= k <= n!。

示例:

复制代码
输入:n = 3, k = 3
输出:"213"
输入:n = 4, k = 9
输出:"2314"

约束: 1 <= n <= 9,1 <= k <= n!

解题思路

核心思想:数学构造------康托展开的逆过程,按阶乘定位每一位数字,无需生成全部排列。

解法:阶乘定位(推荐) 。维护候选数字列表 1..n 与阶乘表 fact[0..n]。对第 pos 位(从 0 起):block = fact[n-1-pos],idx = (k-1) / block,该位取候选列表中第 idx 个数字并移除,k -= idx * block。每次移动一位后在候选里继续定位。时间 O(n²)(List 移除 O(n)),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    // 阶乘定位(康托展开逆过程,推荐)
    public String getPermutation(int n, int k) {
        int[] fact = new int[n + 1];
        fact[0] = 1;
        for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;
        List<Integer> nums = new ArrayList<Integer>();
        for (int i = 1; i <= n; i++) nums.add(i);
        StringBuilder sb = new StringBuilder();
        k--; // 转 0 基
        for (int pos = 0; pos < n; pos++) {
            int block = fact[n - 1 - pos]; // 剩余数字的全排列数
            int idx = k / block;           // 当前位取第几个候选
            k %= block;
            sb.append(nums.get(idx));
            nums.remove(idx);              // 使用后移除
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.getPermutation(3, 3)); // 213
        System.out.println(s.getPermutation(4, 9)); // 2314
    }
}

复杂度分析

  • 时间复杂度:O(n²) --- n 次定位,每次 List 移除 O(n)
  • 空间复杂度:O(n) --- 候选列表与阶乘表

61. Rotate List(旋转链表)

题目描述

给你一个链表的头节点 head,旋转链表,将链表每个节点向右移动 k 个位置。

示例:

复制代码
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
输入:head = [0,1,2], k = 4
输出:[2,0,1]

约束: 链表中节点的数目在范围 [0, 500] 内,-100 <= Node.val <= 100,0 <= k <= 2 * 10^9

解题思路

核心思想:先成环再拆开------k 对链长取模后,从"新头"位置断开。

解法:成环 + 定位断点(推荐) 。先求链长 len,把尾节点接到头节点形成环。k %= len,新头在"从 head 出发走 len-k 步"处(新尾是第 len-k 个节点,它的 next 即新头)。断开 newTail.next = null 返回新头。时间 O(n),空间 O(1)。

代码实现

java 复制代码
class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

class Solution {
    // 成环 + 定位断点(推荐)
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) return head;
        int len = 1;
        ListNode tail = head;
        while (tail.next != null) { tail = tail.next; len++; } // 求长并到尾
        tail.next = head; // 成环
        k %= len;
        int steps = len - k;        // 从 head 走到新尾的步数
        ListNode newTail = head;
        for (int i = 1; i < steps; i++) newTail = newTail.next;
        ListNode newHead = newTail.next; // 新头
        newTail.next = null;             // 断开环
        return newHead;
    }

    private static String str(ListNode h) {
        StringBuilder sb = new StringBuilder("[");
        while (h != null) { sb.append(h.val).append(h.next == null ? "" : ","); h = h.next; }
        return sb.append("]").toString();
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        ListNode h1 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5)))));
        System.out.println(str(s.rotateRight(h1, 2))); // [4,5,1,2,3]
        ListNode h2 = new ListNode(0, new ListNode(1, new ListNode(2)));
        System.out.println(str(s.rotateRight(h2, 4))); // [2,0,1]
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 求长 + 定位
  • 空间复杂度:O(1)

62. Unique Paths(不同路径)

题目描述

一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。问总共有多少条不同的路径?

示例:

复制代码
输入:m = 3, n = 7
输出:28
输入:m = 3, n = 2
输出:3

约束: 1 <= m, n <= 100,答案保证在 32 位整数范围内

解题思路

核心思想 :动态规划------dp[i][j] = dp[i-1][j] + dp[i][j-1](只能从上方或左方来);或用组合数学 C(m+n-2, m-1)。

解法一:二维 DP(推荐) 。dp[i][j] 表示到 (i,j) 的路径数,第一行与第一列全为 1(只能直线走),其余按状态转移累加。时间 O(m×n),空间 O(m×n),可滚动数组优化到 O(n)。

解法二:组合数学 。总共走 m+n-2 步,其中向下 m-1 步,答案是 C(m+n-2, m-1),用 long 计算。时间 O(min(m,n)),空间 O(1)。

代码实现

java 复制代码
class Solution {
    // 解法一:二维 DP(推荐)
    public int uniquePaths(int m, int n) {
        int[] dp = new int[n]; // 滚动数组,空间优化 O(n)
        for (int j = 0; j < n; j++) dp[j] = 1; // 第一行全 1
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[j] += dp[j - 1]; // dp[j](上) + dp[j-1](左)
            }
        }
        return dp[n - 1];
    }

    // 解法二:组合数学 C(m+n-2, m-1)
    public int uniquePathsComb(int m, int n) {
        long ans = 1;
        int steps = m + n - 2;
        int k = Math.min(m - 1, n - 1);
        for (int i = 1; i <= k; i++) {
            ans = ans * (steps - k + i) / i; // 组合数公式逐步乘除防溢出
        }
        return (int) ans;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.uniquePaths(3, 7));     // 28
        System.out.println(s.uniquePaths(3, 2));     // 3
        System.out.println(s.uniquePathsComb(3, 7)); // 28
    }
}

复杂度分析

解法 时间复杂度 空间复杂度 说明
滚动 DP(推荐) O(m×n) O(n) 每行只依赖上一行
组合数学 O(min(m,n)) O(1) 公式一步到位

63. Unique Paths II(不同路径 II)

题目描述

一个机器人位于一个 m x n 网格的左上角。网格中的障碍物和空位置分别用 1 和 0 来表示。机器人每次只能向下或者向右移动一步,试图达到网格的右下角。问总共有多少条不同的路径从左上角到达右下角?(障碍物处不可通过)

示例:

复制代码
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2

约束: 1 <= m, n <= 100,obstacleGridij 为 0 或 1

解题思路

核心思想 :与 62 题几乎相同,但遇到障碍物 dp[j] = 0。

解法:滚动 DP + 障碍处理(推荐) 。首行首列初始化时遇到障碍即后续全 0(被阻断);状态转移时若 obstacleGrid[i][j] == 1 则 dp[j] = 0,否则 dp[j] += dp[j-1]。时间 O(m×n),空间 O(n)。

代码实现

java 复制代码
class Solution {
    // 滚动 DP + 障碍处理(推荐)
    public int uniquePathsWithObstacles(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        if (grid[0][0] == 1) return 0; // 起点即障碍
        int[] dp = new int[n];
        dp[0] = 1;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    dp[j] = 0;               // 障碍处路径数为 0
                } else if (j > 0) {
                    dp[j] += dp[j - 1];      // 上方 + 左侧
                }
            }
        }
        return dp[n - 1];
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.uniquePathsWithObstacles(new int[][]{{0,0,0},{0,1,0},{0,0,0}})); // 2
        System.out.println(s.uniquePathsWithObstacles(new int[][]{{1}}));                     // 0
    }
}

复杂度分析

  • 时间复杂度:O(m×n)
  • 空间复杂度:O(n) --- 滚动数组

64. Minimum Path Sum(最小路径和)

题目描述

给定一个包含非负整数的 m x n 网格 grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。每次只能向下或者向右移动一步。

示例:

复制代码
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:路径 1→3→1→1→1 的总和最小,为 7。

约束: 1 <= m, n <= 200,0 <= grid[i][j] <= 200

解题思路

核心思想:动态规划------每个格子的最小路径和 = 该格值 + min(上方, 左侧)。

解法:原地 DP(推荐) 。直接复用 grid 数组:第一行/第一列先前缀累加(只能直走);其余 grid[i][j] += min(grid[i-1][j], grid[i][j-1])。时间 O(m×n),空间 O(1)(原地)。

代码实现

java 复制代码
class Solution {
    // 原地 DP(推荐)
    public int minPathSum(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        for (int i = 1; i < m; i++) grid[i][0] += grid[i - 1][0]; // 第一列
        for (int j = 1; j < n; j++) grid[0][j] += grid[0][j - 1]; // 第一行
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]); // 取上方/左侧较小
            }
        }
        return grid[m - 1][n - 1];
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minPathSum(new int[][]{{1,3,1},{1,5,1},{4,2,1}})); // 7
        System.out.println(s.minPathSum(new int[][]{{1,2,3},{4,5,6}}));        // 12
    }
}

复杂度分析

  • 时间复杂度:O(m×n)
  • 空间复杂度:O(1) --- 原地修改

65. Valid Number(有效数字)

题目描述

给定一个字符串 s,返回 s 是否是一个有效数字。有效数字按顺序必须满足以下格式(按顺序):可选符号 (+/-) → 有效整数/有效小数 → 可选指数部分(e/E + 可选符号 + 有效整数)。空格只允许出现在最前和最后。

示例:

复制代码
有效:"2"、"0089"、"-0.1"、"+3.14"、"4."、"-.9"、"2e10"、"-90E3"、"3e+7"、"+6e-1"、"53.5e93"、"-123.456e789"
无效:"abc"、"1a"、"1e"、"e3"、"99e2.5"、"--6"、"-+3"、"95a54e53"

约束: 1 <= s.length <= 20,s 仅含英文字母(大写和小写)、数字、+、-、空格、. 之一

解题思路

核心思想:有限状态机或分段解析------先把最简"无空串"判断做掉,再用布尔标记逐步扫描。

解法:状态标记扫描(推荐) 。去掉首尾空格后遍历,维护 numSeen / dotSeen / eSeen 标记:数字则 numSeen=true;. 要求未见过点且未在指数中;e/E 要求已见数字且未见过 e;+/- 只能出现在开头或 e 之后紧邻;其余字符 false。结尾必须 numSeen。时间 O(n),空间 O(1)。

代码实现

java 复制代码
class Solution {
    // 状态标记扫描(推荐)
    public boolean isNumber(String s) {
        s = s.trim();
        if (s.length() == 0) return false;
        boolean numSeen = false, dotSeen = false, eSeen = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (Character.isDigit(c)) {
                numSeen = true;
            } else if (c == '.') {
                if (dotSeen || eSeen) return false; // 小数点唯一且不能在指数部分
                dotSeen = true;
            } else if (c == 'e' || c == 'E') {
                if (eSeen || !numSeen) return false; // e 唯一且前面必须有数字
                eSeen = true;
                numSeen = false; // 指数部分必须重新出现数字
            } else if (c == '+' || c == '-') {
                if (i > 0 && s.charAt(i - 1) != 'e' && s.charAt(i - 1) != 'E') return false;
            } else {
                return false; // 非法字符
            }
        }
        return numSeen;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.isNumber("0"));        // true
        System.out.println(s.isNumber("2e10"));     // true
        System.out.println(s.isNumber("-90E3"));    // true
        System.out.println(s.isNumber(".1"));       // true
        System.out.println(s.isNumber("1e"));       // false
        System.out.println(s.isNumber("99e2.5"));   // false
        System.out.println(s.isNumber("--6"));      // false
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 单次扫描
  • 空间复杂度:O(1)

66. Plus One(加一)

题目描述

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位,数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。

示例:

复制代码
输入:digits = [1,2,3]
输出:[1,2,4]
输入:digits = [9,9,9]
输出:[1,0,0,0]

约束: 1 <= digits.length <= 100,0 <= digits[i] <= 9

解题思路

核心思想:从末位模拟进位,若全为 9 则数组整体进位加长。

解法:倒序模拟进位(推荐) 。从最后一位开始 digits[i]++,若小于 10 则直接返回;否则置 0 继续前一位。若全部进位(如 999),new 一个 n+1 长度数组,首位 1 即可。时间 O(n),空间 O(1)(除 999 场景 O(n))。

代码实现

java 复制代码
import java.util.Arrays;

class Solution {
    // 倒序模拟进位(推荐)
    public int[] plusOne(int[] digits) {
        for (int i = digits.length - 1; i >= 0; i--) {
            digits[i]++;
            if (digits[i] < 10) return digits; // 无进位直接返回
            digits[i] = 0;                    // 进位继续
        }
        int[] res = new int[digits.length + 1]; // 999 -> 1000
        res[0] = 1;
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(Arrays.toString(s.plusOne(new int[]{1,2,3}))); // [1,2,4]
        System.out.println(Arrays.toString(s.plusOne(new int[]{9,9,9}))); // [1,0,0,0]
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 最坏遍历全部
  • 空间复杂度:O(1)(一般) / O(n)(全 9 进位)

67. Add Binary(二进制求和)

题目描述

给你两个二进制字符串 a 和 b,以二进制字符串的形式返回它们的和。

示例:

复制代码
输入:a = "11", b = "1"
输出:"100"
输入:a = "1010", b = "1011"
输出:"10101"

约束: 1 <= a.length, b.length <= 10^4,a 和 b 仅由字符 '0' 和 '1' 组成,不含前导零

解题思路

核心思想:模拟竖式二进制加法,从低位到高位逐位相加并维护进位。

解法:双指针模拟(推荐) 。i, j 分别从两串末尾前移,carry 进位。每轮 sum = carry + (i>=0 ? a[i]-'0' : 0) + (j>=0 ? b[j]-'0' : 0),结果位 sum % 2,进位 sum / 2。最后若 carry=1 追加。时间 O(m+n),空间 O(max(m,n))(结果串)。

代码实现

java 复制代码
class Solution {
    // 双指针模拟(推荐)
    public String addBinary(String a, String b) {
        StringBuilder sb = new StringBuilder();
        int i = a.length() - 1, j = b.length() - 1, carry = 0;
        while (i >= 0 || j >= 0 || carry > 0) {
            int sum = carry;
            if (i >= 0) sum += a.charAt(i--) - '0';
            if (j >= 0) sum += b.charAt(j--) - '0';
            sb.append(sum % 2); // 当前位
            carry = sum / 2;    // 进位
        }
        return sb.reverse().toString(); // 逆序构建需反转
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.addBinary("11", "1"));     // 100
        System.out.println(s.addBinary("1010", "1011")); // 10101
    }
}

复杂度分析

  • 时间复杂度:O(m+n)
  • 空间复杂度:O(max(m,n)) --- 结果串

68. Text Justification(文本左右对齐)

题目描述

给定一个单词数组 words 和一个长度 maxWidth,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。要求:尽可能多地往每行中放置单词;必要时用额外空格 ' ' 填充,使每行恰好有 maxWidth 字符;要求均匀分配单词间的空格数量,如果某行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧。文本的最后一行应为左对齐,且单词之间不插入额外的空格。

示例:

复制代码
输入:words = ["This", "is", "an", "example", "of", "text", "justification."], maxWidth = 16
输出:
[
   "This    is    an",
   "example  of text",
   "justification.  "
]

约束: 1 <= words.length <= 300,1 <= words[i].length <= 20,wordsi 由小写英文字母和符号组成,1 <= maxWidth <= 100,wordsi.length <= maxWidth

解题思路

核心思想:贪心凑行 + 均匀分配空格(多解用左右余数分配)。

解法:按行贪心构建(推荐) 。对每行:先贪心收集能放下的单词(至少 1 个空格间隔)。然后分三种情况填充:① 最后一行或只有一个单词 → 左对齐,右侧补空格;② 普通行 → 总空格数 = maxWidth - 所有单词长度和,gap = 空格数 / (k-1) 均分,extra = 空格数 % (k-1) 前 extra 个间隔多放 1 个空格(左侧多放)。时间 O(n×maxWidth),空间 O(maxWidth)(行缓冲)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    // 按行贪心构建(推荐)
    public List<String> fullJustify(String[] words, int maxWidth) {
        List<String> res = new ArrayList<String>();
        int i = 0, n = words.length;
        while (i < n) {
            // 1. 贪心收集本行单词
            int j = i, len = 0; // len = 已计入的字符(不含间隔)
            while (j < n && len + words[j].length() + (j - i) <= maxWidth) {
                len += words[j].length();
                j++;
            }
            int cnt = j - i;                 // 本行单词数
            int totalSpaces = maxWidth - len; // 需分配的空格总数
            // 2. 构建本行
            StringBuilder line = new StringBuilder();
            if (j == n || cnt == 1) {        // 最后一行或单单词:左对齐
                for (int k = i; k < j; k++) {
                    line.append(words[k]);
                    if (k < j - 1) line.append(' ');
                }
                while (line.length() < maxWidth) line.append(' ');
            } else {                          // 普通行:均匀分配空格
                int gap = totalSpaces / (cnt - 1);
                int extra = totalSpaces % (cnt - 1); // 前 extra 个间隔多 1 空格
                for (int k = i; k < j; k++) {
                    line.append(words[k]);
                    if (k < j - 1) {
                        for (int s = 0; s < gap; s++) line.append(' ');
                        if (k - i < extra) line.append(' ');
                    }
                }
            }
            res.add(line.toString());
            i = j;
        }
        return res;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        String[] words = {"This", "is", "an", "example", "of", "text", "justification."};
        System.out.println(s.fullJustify(words, 16));
    }
}

复杂度分析

  • 时间复杂度:O(n×maxWidth) --- 每个单词被处理一次,填充空格 O(maxWidth)
  • 空间复杂度:O(maxWidth) --- 行缓冲(不含结果)

69. Sqrt(x)(x 的平方根)

题目描述

给你一个非负整数 x,计算并返回 x 的算术平方根 。由于返回类型是整数,结果只保留整数部分,小数部分将被舍去。不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或 x ** 0.5。

示例:

复制代码
输入:x = 4   输出:2
输入:x = 8   输出:2

约束: 0 <= x <= 2^31 - 1

解题思路

核心思想 :在 0, x 上二分找最大的 mid 使 mid*mid <= x,用除法防溢出。

解法一:二分(推荐) 。lo=0, hi=x,mid = lo + (hi-lo)/2,用 mid <= x/mid 判断(等价 mid² ≤ x 且不溢出)。满足则 ans=mid, lo=mid+1,否则 hi=mid-1。时间 O(log x),空间 O(1)。

解法二:牛顿迭代 。x_{n+1} = (x_n + a/x_n)/2,收敛很快。时间 O(log x) 级,常数更小。

代码实现

java 复制代码
class Solution {
    // 解法一:二分(推荐)
    public int mySqrt(int x) {
        int lo = 0, hi = x, ans = 0;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (mid <= x / mid) { // mid*mid <= x 防溢出写法
                ans = mid;
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return ans;
    }

    // 解法二:牛顿迭代
    public int mySqrtNewton(int x) {
        long r = x;
        while (r * r > x) {
            r = (r + x / r) / 2; // 迭代逼近
        }
        return (int) r;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.mySqrt(4)); // 2
        System.out.println(s.mySqrt(8)); // 2
        System.out.println(s.mySqrt(2147483647)); // 46340
    }
}

复杂度分析

解法 时间复杂度 空间复杂度 说明
二分(推荐) O(log x) O(1) 标准二分
牛顿迭代 O(log x) O(1) 收敛更快,常数小

70. Climbing Stairs(爬楼梯)

题目描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例:

复制代码
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。1. 1 阶 + 1 阶  2. 2 阶
输入:n = 3
输出:3

约束: 1 <= n <= 45

解题思路

核心思想 :斐波那契数列------dp[n] = dp[n-1] + dp[n-2](最后一步爬 1 或 2 阶)。

解法一:滚动 DP(推荐) 。a=1, b=1(dp0=1, dp1=1),循环 n-1 次:next = a + b; a = b; b = next。时间 O(n),空间 O(1)。

解法二:快速幂矩阵。斐波那契可用矩阵 \[1,1,1,0] 的 n 次幂 O(log n) 求解,n ≤ 45 时收益不大。

代码实现

java 复制代码
class Solution {
    // 解法一:滚动 DP(推荐)
    public int climbStairs(int n) {
        int a = 1, b = 1; // dp[0]=1, dp[1]=1
        for (int i = 2; i <= n; i++) {
            int next = a + b;
            a = b;
            b = next;
        }
        return b;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.climbStairs(2)); // 2
        System.out.println(s.climbStairs(3)); // 3
        System.out.println(s.climbStairs(45)); // 1836311903
    }
}

复杂度分析

解法 时间复杂度 空间复杂度 说明
滚动 DP(推荐) O(n) O(1) 斐波那契
矩阵快速幂 O(log n) O(1) n 大时的进阶写法

71. Simplify Path(简化路径)

题目描述

给你一个字符串 path,表示指向某一文件或目录的 Unix 风格绝对路径(以 / 开头),请你将其转化为更加简洁的规范路径 。在 Unix 风格的文件系统中,一个点 . 表示当前目录本身;两个点 .. 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即 //)都被视为单个斜杠 /。对于此问题,任何其他格式的点(例如 ...)均被视为文件/目录名称。

返回的规范路径必须遵循下述格式:始终以斜杠 / 开头;两个目录名之间必须只有一个斜杠 /;最后一个目录名(如果存在)不能以 / 结尾;根目录 / 除外。

示例:

复制代码
输入:path = "/home/"         输出:"/home"
输入:path = "/../"           输出:"/"
输入:path = "/home//foo/"    输出:"/home/foo"
输入:path = "/a/./b/../../c/" 输出:"/c"
输入:path = "/a//b////c/d//././/.." 输出:"/a/b/c"

约束: 1 <= path.length <= 3000,path 由英文字母、数字、.、/ 或 _ 组成

解题思路

核心思想 :用 / 切分路径段,用栈处理 .(跳过)与 ..(弹栈)。

解法:切分 + 栈(推荐) 。按 / split 后遍历各段:空串或 . 忽略;.. 若非空则弹栈;其余入栈。最后用 / 拼接并前缀 /,空栈输出 /。时间 O(n),空间 O(n)。

代码实现

java 复制代码
import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    // 切分 + 栈(推荐)
    public String simplifyPath(String path) {
        Deque<String> stack = new ArrayDeque<String>();
        for (String part : path.split("/")) {
            if (part.isEmpty() || part.equals(".")) continue; // 空或当前目录
            if (part.equals("..")) {
                if (!stack.isEmpty()) stack.pop(); // 回退父目录
            } else {
                stack.push(part);
            }
        }
        if (stack.isEmpty()) return "/";
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()) {
            sb.insert(0, "/" + stack.pop()); // 栈顶是最后段,前插拼接
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.simplifyPath("/home/"));               // /home
        System.out.println(s.simplifyPath("/../"));                 // /
        System.out.println(s.simplifyPath("/home//foo/"));          // /home/foo
        System.out.println(s.simplifyPath("/a/./b/../../c/"));      // /c
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 扫描与入栈
  • 空间复杂度:O(n) --- 栈

72. Edit Distance(编辑距离)

题目描述

给你两个单词 word1 和 word2,请返回将 word1 转换成 word2 所使用的最少操作数。你可以对一个单词进行如下三种操作:插入一个字符、删除一个字符、替换一个字符。

示例:

复制代码
输入:word1 = "horse", word2 = "ros"
输出:3
解释:horse -> rorse(将 'h' 替换为 'r')-> rose(删除 'r')-> ros(删除 'e')

约束: 0 <= word1.length, word2.length <= 500,word1 和 word2 由小写英文字母组成

解题思路

核心思想 :经典双串 DP------dp[i][j] 表示 word1 前 i 个字符转成 word2 前 j 个字符的最少操作数。

解法:二维 DP(推荐)。初始化第一行/第一列为自然增长(插入/删除)。转移:

  • word1[i-1] == word2[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] 替换)。

时间 O(m×n),空间 O(m×n)(可滚动到 O(n))。

代码实现

java 复制代码
class Solution {
    // 二维 DP(推荐)
    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; // 第一行:word1 空 -> 插入
        for (int i = 1; i <= m; i++) {
            int prev = dp[0]; // 上一行的 dp[i-1][0]
            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(prev, Math.min(dp[j], dp[j - 1])); // 替换/删除/插入
                }
                prev = temp;
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.minDistance("horse", "ros"));      // 3
        System.out.println(s.minDistance("intention", "execution")); // 5
    }
}

复杂度分析

  • 时间复杂度:O(m×n)
  • 空间复杂度:O(n) --- 滚动数组

73. Set Matrix Zeroes(矩阵置零)

题目描述

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

示例:

复制代码
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]

约束: 1 <= m, n <= 200,-2^31 <= matrix[i][j] <= 2^31 - 1。进阶:常数空间方案?

解题思路

核心思想:用矩阵第一行/第一列作为"是否有零"的标记,再用两个标志位处理首行首列本身。

解法:首行首列作为标记(推荐,O(1) 空间) 。先记录第一行与第一列是否本身含 0(row0Zero / col0Zero)。然后遍历 [1..m)×[1..n):若 matrix[i][j] == 0,标记 matrix[i][0] = 0 与 matrix[0][j] = 0。第二次遍历按标记清零(除首行首列),最后根据标志位处理首行首列。时间 O(m×n),空间 O(1)。

代码实现

java 复制代码
import java.util.Arrays;

class Solution {
    // 首行首列作为标记(推荐)
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean row0Zero = false, col0Zero = false;
        for (int j = 0; j < n; j++) if (matrix[0][j] == 0) row0Zero = true; // 首行含 0?
        for (int i = 0; i < m; i++) if (matrix[i][0] == 0) col0Zero = true; // 首列含 0?
        // 1. 用首行首列记录零位置
        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; }
            }
        }
        // 2. 按标记清零
        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;
            }
        }
        // 3. 处理首行首列本身
        if (row0Zero) for (int j = 0; j < n; j++) matrix[0][j] = 0;
        if (col0Zero) for (int i = 0; i < m; i++) matrix[i][0] = 0;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        int[][] m = {{1,1,1},{1,0,1},{1,1,1}};
        s.setZeroes(m);
        System.out.println(Arrays.deepToString(m)); // [[1,0,1],[0,0,0],[1,0,1]]
    }
}

复杂度分析

  • 时间复杂度:O(m×n) --- 三趟遍历
  • 空间复杂度:O(1) --- 原地

题目描述

给你一个满足下述两条属性的 m x n 整数矩阵:每行中的整数从左到右按非递减顺序排列;每行的第一个整数大于前一行的最后一个整数。给你一个整数 target,如果 target 在矩阵中,返回 true,否则返回 false。

示例:

复制代码
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

约束: 1 <= m, n <= 100,-10^4 <= matrix[i][j], target <= 10^4

解题思路

核心思想:矩阵整体按行展开是一个升序数组,直接用一维二分(下标换算)。

解法一:整体二分(推荐) 。把 m×n 矩阵视作长度 m×n 的递增序列:lo=0, hi=m*n-1,mid 换算行列 matrix[mid/n][mid%n]。时间 O(log(m×n)),空间 O(1)。

解法二:Z 字形搜索。从右上角开始:比 target 大则左移列,比 target 小则下移行。时间 O(m+n),空间 O(1)。

代码实现

java 复制代码
class Solution {
    // 解法一:整体二分(推荐)
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length, n = matrix[0].length;
        int lo = 0, hi = m * n - 1;
        while (lo <= hi) {
            int mid = (lo + hi) / 2;
            int val = matrix[mid / n][mid % n]; // 一维下标换算二维
            if (val == target) return true;
            else if (val < target) lo = mid + 1;
            else hi = mid - 1;
        }
        return false;
    }

    // 解法二:右上角 Z 字形
    public boolean searchMatrixZ(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;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        int[][] m = {{1,3,5,7},{10,11,16,20},{23,30,34,60}};
        System.out.println(s.searchMatrix(m, 3));  // true
        System.out.println(s.searchMatrix(m, 13)); // false
    }
}

复杂度分析

解法 时间复杂度 空间复杂度 说明
整体二分(推荐) O(log(m×n)) O(1) 最优
Z 字形搜索 O(m+n) O(1) 适合二分不可行的变体

75. Sort Colors(颜色分类)

题目描述

给定一个包含红色、白色和蓝色、一共 n 个元素的数组 nums,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0、1 和 2 分别表示红色、白色和蓝色。不使用代码库中的排序函数,且仅使用常数级额外空间的条件下解决。

示例:

复制代码
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

约束: n == nums.length,1 <= n <= 300,numsi 为 0、1 或 2

解题思路

核心思想:三指针(荷兰国旗问题)------p0 放 0,p2 放 2,i 遍历中间。

解法:三指针分区(推荐) 。p0=0, i=0, p2=n-1。遍历 i ≤ p2:nums[i]==0 则与 p0 交换,p0++、i++;nums[i]==2 则与 p2 交换,p2--(i 不动,因为换来的可能是 0);nums[i]==1 则 i++。时间 O(n),空间 O(1)。

代码实现

java 复制代码
import java.util.Arrays;

class Solution {
    // 三指针分区(荷兰国旗,推荐)
    public void sortColors(int[] nums) {
        int p0 = 0, i = 0, p2 = nums.length - 1;
        while (i <= p2) {
            if (nums[i] == 0) { // 0 放左边
                swap(nums, i++, p0++);
            } else if (nums[i] == 2) { // 2 放右边,i 不动待检查换回的值
                swap(nums, i, p2--);
            } else {
                i++; // 1 留在中间
            }
        }
    }

    private void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }

    public static void main(String[] args) {
        Solution s = new Solution();
        int[] a = {2,0,2,1,1,0};
        s.sortColors(a);
        System.out.println(Arrays.toString(a)); // [0,0,1,1,2,2]
    }
}

复杂度分析

  • 时间复杂度:O(n) --- 单趟
  • 空间复杂度:O(1)
相关推荐
学电子她就能回来吗2 小时前
LeetCode Hot 100|普通数组专题学习笔记(零基础版)
笔记·算法·leetcode
文丑颜不良啊6 小时前
LeetCode1~50题目解析
leetcode
Pniubi6 小时前
力扣146.LRU缓存机制
算法·leetcode·缓存
Nil2081 天前
leetcode 49字母异位分组
算法·leetcode·哈希算法
Nil2082 天前
leetcode 5最长回文子串
算法·leetcode·职场和发展
m0_674571972 天前
力扣 hot 100 python
python·算法·leetcode
圣保罗的大教堂2 天前
leetcode 3727. 最大交替平方和 中等
leetcode
Xin7702 天前
LeetCode 200. 岛屿数量(DFS 深度优先遍历法)
leetcode