LeetCode2481~2500题目解析

LeetCode 2481-2500. 题解合集

文章目录


2481. Minimum Cuts to Divide a Circle(分割圆的最少切割次数)

题目描述

圆内一个有效切割,符合以下二者之一:

  • 该切割是两个端点在圆上的线段,且该线段经过圆心。
  • 该切割是一端在圆心另一端在圆上的线段。
    给你一个整数 n,请你返回将圆切割成相等的 n 等分的最少切割次数。

示例 1

复制代码
`n = 4` → 2(两次直径切割可得四等分)

示例 2

复制代码
`n = 3` → 3(需要三次半径切割)

示例 3

复制代码
`n = 1` → 0(无需切割)

提示:

  • 1 <= n <= 100

解题思路

解法一:奇偶讨论(主提交版本)

  • 直径切割一刀同时产生两条分界线,半径切割一刀只产生一条分界线。
  • 若 n 为偶数:用 n/2 条直径即可分出 2*(n/2) = n 块 → 答案 n/2。
  • 若 n 为奇数:无法只用直径(直径总是产生偶数块),需 n 条半径 → 答案 n。
  • n = 1 时不需要任何刀 → 0。
    解法二:统一公式(对照)
  • n == 1 ? 0 : (n % 2 == 0 ? n / 2 : n),与解法一逻辑等价。
    复杂度:
  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

代码实现

java 复制代码
public class Solution {
    // 解法一:奇偶讨论(主提交版本)
    public int numberOfCuts(int n) {
        if (n == 1) {
            return 0;
        }
        if (n % 2 == 1) {
            return n;
        }
        return n / 2;
    }

    // 解法二:统一公式(对照)
    public int numberOfCutsFormula(int n) {
        return n == 1 ? 0 : (n % 2 == 0 ? n / 2 : n);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.numberOfCuts(4) == 2 : "2481 例1 主";
        assert sol.numberOfCutsFormula(4) == 2 : "2481 例1 公式";
        assert sol.numberOfCuts(3) == 3 : "2481 例2 主";
        assert sol.numberOfCutsFormula(3) == 3 : "2481 例2 公式";
        assert sol.numberOfCuts(1) == 0 : "2481 例3 主";
        assert sol.numberOfCutsFormula(1) == 0 : "2481 例3 公式";
        for (int n = 1; n <= 100; n++) {
            int a = sol.numberOfCuts(n);
            int b = sol.numberOfCutsFormula(n);
            assert a == b : "2481 不一致 n=" + n;
        }
        System.out.println("2481 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

2482. Difference Between Ones and Zeros in Row and Column(行和列中一和零的差值)

题目描述

给你一个下标从 0 开始的 m x n 二进制矩阵 grid。

定义 diff[i][j] = onesRowi + onesColj - zerosRowi - zerosColj,

其中 onesRowi 为第 i 行 1 的个数,onesColj 为第 j 列 1 的个数,

zerosRowi 为第 i 行 0 的个数,zerosColj 为第 j 列 0 的个数。

返回差值矩阵 diff。

示例 1

复制代码
`grid = [[0,1,1],[1,0,1],[0,0,1]]` → `[[0,0,4],[0,0,4],[-2,-2,2]]`

示例 2

复制代码
`grid = [[1,1,1],[1,1,1]]` → `[[5,5,5],[5,5,5]]`

提示:

  • m == grid.length,n == grid[i].length
  • 1 <= m, n <= 10^5,1 <= m * n <= 10^5
  • grid[i][j] 要么是 0,要么是 1

解题思路

解法一:按行/列统计 1 的个数(主提交版本)

  • 统计每行 1 的个数 rowOnes[i]、每列 1 的个数 colOnes[j]。
  • 由于一行的 0 数 = n - rowOnesi,一列的 0 数 = m - colOnesj,代入公式:
    diff[i][j] = rowOnes[i] + colOnes[j] - (n - rowOnes[i]) - (m - colOnes[j]) = 2 * (rowOnes[i] + colOnes[j]) - m - n
    解法二:直接按定义累加(对照)
  • 每格用公式原样计算(预统计行/列 1、0 数),写法更直白。
    复杂度:
  • 时间复杂度:O(m * n)
  • 空间复杂度:O(m + n)

代码实现

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

public class Solution {
    // 解法一:行/列 1 计数 + 化简公式(主提交版本)
    public int[][] onesMinusZeros(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[] rowOnes = new int[m];
        int[] colOnes = new int[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    rowOnes[i]++;
                    colOnes[j]++;
                }
            }
        }
        int[][] diff = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                diff[i][j] = 2 * (rowOnes[i] + colOnes[j]) - m - n;
            }
        }
        return diff;
    }

    // 解法二:按定义原样计算(对照)
    public int[][] onesMinusZerosBrute(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[] onesRow = new int[m];
        int[] zerosRow = new int[m];
        int[] onesCol = new int[n];
        int[] zerosCol = new int[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    onesRow[i]++;
                    onesCol[j]++;
                } else {
                    zerosRow[i]++;
                    zerosCol[j]++;
                }
            }
        }
        int[][] diff = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                diff[i][j] = onesRow[i] + onesCol[j] - zerosRow[i] - zerosCol[j];
            }
        }
        return diff;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        int[][] g1 = {{0, 1, 1}, {1, 0, 1}, {0, 0, 1}};
        int[][] r1 = sol.onesMinusZeros(g1);
        int[][] r1b = sol.onesMinusZerosBrute(g1);
        int[][] exp1 = {{0, 0, 4}, {0, 0, 4}, {-2, -2, 2}};
        assert Arrays.deepEquals(r1, exp1) : "2482 例1 主: " + Arrays.deepToString(r1);
        assert Arrays.deepEquals(r1b, exp1) : "2482 例1 暴力: " + Arrays.deepToString(r1b);

        int[][] g2 = {{1, 1, 1}, {1, 1, 1}};
        int[][] exp2 = {{5, 5, 5}, {5, 5, 5}};
        assert Arrays.deepEquals(sol.onesMinusZeros(g2), exp2) : "2482 例2 主";
        assert Arrays.deepEquals(sol.onesMinusZerosBrute(g2), exp2) : "2482 例2 暴力";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(13);
        for (int t = 0; t < 300; t++) {
            int m = 1 + rnd.nextInt(8);
            int n = 1 + rnd.nextInt(8);
            int[][] g = new int[m][n];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    g[i][j] = rnd.nextInt(2);
                }
            }
            if (!Arrays.deepEquals(sol.onesMinusZeros(g), sol.onesMinusZerosBrute(g))) {
                System.out.println("对拍失败 " + Arrays.deepToString(g));
                System.exit(1);
            }
        }
        System.out.println("2482 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(m * n)
  • 空间复杂度:O(m + n)

2483. Minimum Penalty for a Shop(商店的最少代价)

题目描述

给你一个顾客访问商店的日志,用一个下标从 0 开始且只包含字符 'N' 和 'Y' 的字符串 customers 表示:

  • 如果第 i 个字符是 'Y',表示第 i 小时有顾客到达。
  • 如果第 i 个字符是 'N',表示第 i 小时没有顾客到达。
    如果商店在第 j 小时关门(0 <= j <= n),代价按如下方式计算:
  • 在开门期间,如果某一个小时没有顾客到达,代价增加 1。
  • 在关门期间,如果某一个小时有顾客到达,代价增加 1。
    返回在确保代价最小的前提下,商店的最早关门时间。
    注意,商店在第 j 小时关门表示在第 j 小时以及之后商店处于关门状态。

示例 1

复制代码
`customers = "YYNY"` → 2(第 2 或第 4 小时关门代价都最小,取最早的 2)

示例 2

复制代码
`customers = "NNNNN"` → 0

示例 3

复制代码
`customers = "YYYY"` → 4

提示:

  • 1 <= customers.length <= 10^5
  • customers 只包含字符 'Y' 和 'N'

解题思路

解法一:前缀 Y 计数(主提交版本)

  • 设在第 j 小时关门(0 <= j <= n):
  • 开门段 [0, j):未关闭时遇到 'N' 计 1 → 这段代价 = 区间内 'N' 的个数 = j - 区间内 'Y' 个数。
  • 关门段 [j, n):关门时遇到 'Y' 计 1 → 这段代价 = 区间内 'Y' 的个数。
  • 用前缀 Y 计数 preYi = 前 i 个字符中 'Y' 的个数:
    代价(j) = (j - preYj) + (preYn - preYj) = preYn + j - 2 * preYj。
  • 枚举 j 求最小代价,相同代价取最早 j。
    解法二:两遍扫描(对照)
  • 先统计总 'Y' 数 totalY;从左向右扫描,维护左侧 N 数、右侧 Y 数,
    代价 = leftN + rightY,逐步取最小 j。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)(解法一 O(n) 也可,解法二 O(1))

代码实现

java 复制代码
public class Solution {
    // 解法一:前缀 Y 计数公式(主提交版本)
    public int bestClosingTime(String customers) {
        int n = customers.length();
        int[] preY = new int[n + 1];
        for (int i = 0; i < n; i++) {
            preY[i + 1] = preY[i] + (customers.charAt(i) == 'Y' ? 1 : 0);
        }
        int totalY = preY[n];
        int bestIdx = 0;
        int bestCost = Integer.MAX_VALUE;
        for (int j = 0; j <= n; j++) {
            int cost = totalY + j - 2 * preY[j];
            if (cost < bestCost) {
                bestCost = cost;
                bestIdx = j;
            }
        }
        return bestIdx;
    }

    // 解法二:两遍扫描(对照)
    public int bestClosingTimeScan(String customers) {
        int n = customers.length();
        int totalY = 0;
        for (int i = 0; i < n; i++) {
            if (customers.charAt(i) == 'Y') {
                totalY++;
            }
        }
        int leftN = 0;
        int rightY = totalY;
        int bestIdx = 0;
        int bestCost = Integer.MAX_VALUE;
        for (int j = 0; j <= n; j++) {
            int cost = leftN + rightY;
            if (cost < bestCost) {
                bestCost = cost;
                bestIdx = j;
            }
            if (j < n) {
                if (customers.charAt(j) == 'N') {
                    leftN++; // j 小时后进入关门段,不再计 N
                } else {
                    rightY--; // 其变成开门段遇到的 Y,关闭代价取消
                }
            }
        }
        return bestIdx;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.bestClosingTime("YYNY") == 2 : "2483 例1 主: " + sol.bestClosingTime("YYNY");
        assert sol.bestClosingTimeScan("YYNY") == 2 : "2483 例1 扫描";
        assert sol.bestClosingTime("NNNNN") == 0 : "2483 例2 主";
        assert sol.bestClosingTimeScan("NNNNN") == 0 : "2483 例2 扫描";
        assert sol.bestClosingTime("YYYY") == 4 : "2483 例3 主";
        assert sol.bestClosingTimeScan("YYYY") == 4 : "2483 例3 扫描";
        assert sol.bestClosingTime("Y") == 1 : "2483 例4 主";
        assert sol.bestClosingTimeScan("Y") == 1 : "2483 例4 扫描";
        assert sol.bestClosingTime("N") == 0 : "2483 例5 主";
        assert sol.bestClosingTimeScan("N") == 0 : "2483 例5 扫描";

        // 穷举所有长度 <= 12 的字符串对拍
        for (int len = 1; len <= 12; len++) {
            int total = 1 << len;
            for (int mask = 0; mask < total; mask++) {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < len; i++) {
                    sb.append(((mask >> i) & 1) == 1 ? 'Y' : 'N');
                }
                String s = sb.toString();
                int a = sol.bestClosingTime(s);
                int b = sol.bestClosingTimeScan(s);
                if (a != b) {
                    System.out.println("对拍失败 s=" + s + " 主=" + a + " 扫描=" + b);
                    System.exit(1);
                }
            }
        }
        System.out.println("2483 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)(解法一 O(n) 也可,解法二 O(1))

2484. Count Palindromic Subsequences(统计回文子序列数目)

题目描述

给你数字字符串 s,返回 s 中长度为 5 的回文子序列数目。由于答案可能很大,请将答案对 10^9 + 7 取余后返回。

回文字符串:从前往后和从后往前读相同的字符串。

子序列:删除若干个字符后,不改变字符顺序,剩余字符构成的字符串。

示例 1

复制代码
`s = "103301"` → 2(两个 "10301")

示例 2

复制代码
`s = "0000000"` → 21(所有 C(7,5) = 21 个长度 5 子序列都是 "00000")

示例 3

复制代码
`s = "9999900000"` → 2("99999" 和 "00000")

提示:

  • 1 <= s.length <= 10^4
  • s 只包含数字字符

解题思路

解法一:左右双向计数(主提交版本,O(10^2 * n))

  • 长度为 5 的回文形如 a b c b a:两端字符相同(a = a)、内侧字符相同(b = b),中间为 c。
  • 枚举中间位置 i(字符 c),则需要在左侧(0...i-1)选两个位置 p < q 使字符为 (a, b),
    在右侧(i+1...n-1)选两个位置 p' < q' 使字符为 (b, a)。
  • 维护:
  • right[i][x][y]:si...n-1 中形如 x y 的子序列个数(从右向左构建)。
  • 从左向右扫描,动态维护 left[x][y] = s0...i-1 中形如 x y 的子序列个数。
  • 对每个位置 i:ans += Σ_{x,y} left[x][y] * right[i+1][y][x],
    因为左侧需 (a, b) = (x, y),右侧需 (b, a) = (y, x)。
    解法二:暴力五重循环(对照,仅小规模对拍)
  • 枚举五个下标 i<j<k<l<m,判断 si==sm 且 sj==sl。
    复杂度:
  • 时间复杂度:O(100 * n)
  • 空间复杂度:O(100 * n)

代码实现

java 复制代码
public class Solution {
    private static final int MOD = 1_000_000_007;

    // 解法一:左右双向计数(主提交版本)
    public int countPalindromes(String s) {
        int n = s.length();
        int[] d = new int[n];
        for (int i = 0; i < n; i++) {
            d[i] = s.charAt(i) - '0';
        }
        // right[i][x][y] = s[i..n-1] 中形如 x y(存在位置 p<q,s[p]=x, s[q]=y)的子序列数
        long[][][] right = new long[n + 1][10][10];
        int[] cntR = new int[10];
        for (int i = n - 1; i >= 0; i--) {
            for (int x = 0; x < 10; x++) {
                System.arraycopy(right[i + 1][x], 0, right[i][x], 0, 10);
            }
            int c = d[i];
            // 以位置 i 的字符 c 作为子序列的第一个字符,y 在 i 之后出现
            for (int y = 0; y < 10; y++) {
                right[i][c][y] += cntR[y]; // c y 子序列新增 cntR[y] 个
            }
            cntR[c]++;
        }
        long ans = 0;
        long[][] left = new long[10][10]; // left[x][y] = 已扫过前缀中形如 x y 的子序列数
        int[] cntL = new int[10];
        for (int i = 0; i < n; i++) {
            int c = d[i];
            // i 作为正中间字符:左侧取 (x, y),右侧取 (y, x),构成 x y c y x
            for (int x = 0; x < 10; x++) {
                for (int y = 0; y < 10; y++) {
                    if (left[x][y] > 0 && right[i + 1][y][x] > 0) {
                        ans = (ans + left[x][y] * right[i + 1][y][x]) % MOD;
                    }
                }
            }
            // 更新 left:以 c 结尾的 x c 子序列新增 cntL[x] 个
            for (int x = 0; x < 10; x++) {
                left[x][c] += cntL[x];
            }
            cntL[c]++;
        }
        return (int) ans;
    }

    // 解法二:暴力五重循环(对照,仅小规模对拍)
    public int countPalindromesBrute(String s) {
        int n = s.length();
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                for (int k = j + 1; k < n; k++) {
                    for (int l = k + 1; l < n; l++) {
                        for (int m = l + 1; m < n; m++) {
                            if (s.charAt(i) == s.charAt(m) && s.charAt(j) == s.charAt(l)) {
                                ans++;
                            }
                        }
                    }
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.countPalindromes("103301") == 2 : "2484 例1 主: " + sol.countPalindromes("103301");
        assert sol.countPalindromesBrute("103301") == 2 : "2484 例1 暴力";
        assert sol.countPalindromes("0000000") == 21 : "2484 例2 主: " + sol.countPalindromes("0000000");
        assert sol.countPalindromesBrute("0000000") == 21 : "2484 例2 暴力";
        assert sol.countPalindromes("9999900000") == 2 : "2484 例3 主: " + sol.countPalindromes("9999900000");
        assert sol.countPalindromesBrute("9999900000") == 2 : "2484 例3 暴力";

        // 随机对拍(小规模)
        java.util.Random rnd = new java.util.Random(31);
        for (int t = 0; t < 150; t++) {
            int n = 5 + rnd.nextInt(7);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                sb.append((char) ('0' + rnd.nextInt(4))); // 4 种数字保证有回文
            }
            String s = sb.toString();
            int a = sol.countPalindromes(s);
            int b = sol.countPalindromesBrute(s);
            if (a != b) {
                System.out.println("对拍失败 s=" + s + " 主=" + a + " 暴力=" + b);
                System.exit(1);
            }
        }
        System.out.println("2484 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(100 * n)
  • 空间复杂度:O(100 * n)

2485. Find the Pivot Integer(找出中枢整数)

题目描述

给你一个正整数 n,找出满足下述条件的中枢整数 x:

  • 1 和 x 之间的所有元素之和等于 x 和 n 之间所有元素之和。
    返回中枢整数 x;如果不存在中枢整数,则返回 -1。
    题目保证对于给定的输入,至多存在一个中枢整数。

示例 1

复制代码
`n = 8` → 6(1+2+3+4+5+6 = 6+7+8 = 21)

示例 2

复制代码
`n = 1` → 1

示例 3

复制代码
`n = 4` → -1

提示:

  • 1 <= n <= 1000

解题思路

解法一:数学公式(主提交版本)

  • 1 到 n 的总和 total = n*(n+1)/2。
  • 若存在 x,则 1 到 x 的和 = x 到 n 的和 = total / 2 与 x 需要满足:
    x*(x+1)/2 >= total/2。更直接:x² 可能成立,因为:
    1+...+x = x+...+n ⇔ sum(1...x) = sum(x...n)
    展开:x(x+1)/2 = (x+n)(n-x+1)/2
    化简得 x^2 = n(n+1)/2 − 反推:本式等价于 2x² = n²+n,即 x = sqrt(n(n+1)/2)。
  • 计算 t = sqrt(n*(n+1)/2),若 t*t == n*(n+1)/2(允许整数开方检查,注意用 long),返回 t,否则 -1。
    解法二:枚举(对照)
  • 直接枚举 x 从 1 到 n,比较 1...x 和 x...n 两个累加和。
    复杂度:
  • 时间复杂度:O(1)(解法二 O(n))
  • 空间复杂度:O(1)

代码实现

java 复制代码
public class Solution {
    // 解法一:数学公式(主提交版本)
    public int pivotInteger(int n) {
        long total = (long) n * (n + 1) / 2;
        long t = (long) Math.sqrt(total);
        // 浮点开方可能向下取整,做一次 ±1 校验
        for (long x = Math.max(1, t - 1); x <= t + 1; x++) {
            if (x * x == total) {
                return (int) x;
            }
        }
        return -1;
    }

    // 解法二:枚举(对照)
    public int pivotIntegerBrute(int n) {
        int[] pre = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            pre[i] = pre[i - 1] + i;
        }
        for (int x = 1; x <= n; x++) {
            int left = pre[x];                   // 1..x
            int right = pre[n] - pre[x - 1];     // x..n
            if (left == right) {
                return x;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        for (int n = 1; n <= 1000; n++) {
            int a = sol.pivotInteger(n);
            int b = sol.pivotIntegerBrute(n);
            assert a == b : "2485 不一致 n=" + n + " 主=" + a + " 枚举=" + b;
        }
        assert sol.pivotInteger(8) == 6 : "2485 例1";
        assert sol.pivotInteger(1) == 1 : "2485 例2";
        assert sol.pivotInteger(4) == -1 : "2485 例3";
        System.out.println("2485 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(1)(解法二 O(n))
  • 空间复杂度:O(1)

2486. Append Characters to String to Make Subsequence(追加字符以获得子序列)

题目描述

给你两个仅由小写英文字母组成的字符串 s 和 t。

现在需要通过向 s 末尾追加字符的方式使 t 变成 s 的一个子序列,返回需要追加的最少字符数。

子序列是一个可以由其他字符串删除部分(或不删除)字符但不改变剩下字符顺序得到的字符串。

示例 1

复制代码
`s = "coaching", t = "coding"` → 4

示例 2

复制代码
`s = "abcde", t = "a"` → 0

示例 3

复制代码
`s = "z", t = "abcde"` → 5

提示:

  • 1 <= s.length, t.length <= 10^5
  • s 和 t 仅由小写英文字母组成

解题思路

解法一:双指针贪心(主提交版本)

  • 用指针 i 扫 s、指针 j 扫 t:若 si == tj,则 j 前进(t 中该字符已被匹配)。
  • 扫描完成后 t 还有 j...t.length-1 未匹配,这些字符只能追加到 s 末尾,
    追加数量 = t.length - j。
  • 等价于:t 中能被 s 作为子序列匹配掉的最大前缀长度为 j,剩余全要追加。
    解法二:朴素逐字符搜索(对照)
  • 对 t 的每个字符在 s 中顺序查找,找不到的第一个位置以后全部要追加。
    复杂度:
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(1)

代码实现

java 复制代码
public class Solution {
    // 解法一:双指针(主提交版本)
    public int appendCharacters(String s, String t) {
        int i = 0, j = 0;
        int n = s.length(), m = t.length();
        while (i < n && j < m) {
            if (s.charAt(i) == t.charAt(j)) {
                j++;
            }
            i++;
        }
        return m - j;
    }

    // 解法二:逐字符推进(对照)
    public int appendCharactersBrute(String s, String t) {
        int idx = 0;
        for (int i = 0; i < s.length(); i++) {
            if (idx < t.length() && s.charAt(i) == t.charAt(idx)) {
                idx++;
            }
        }
        return t.length() - idx;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.appendCharacters("coaching", "coding") == 4 : "2486 例1 主";
        assert sol.appendCharactersBrute("coaching", "coding") == 4 : "2486 例1 暴力";
        assert sol.appendCharacters("abcde", "a") == 0 : "2486 例2 主";
        assert sol.appendCharactersBrute("abcde", "a") == 0 : "2486 例2 暴力";
        assert sol.appendCharacters("z", "abcde") == 5 : "2486 例3 主";
        assert sol.appendCharactersBrute("z", "abcde") == 5 : "2486 例3 暴力";
        assert sol.appendCharacters("cba", "abc") == 3 : "2486 特例主";
        assert sol.appendCharactersBrute("cba", "abc") == 3 : "2486 特例暴力";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(19);
        for (int t = 0; t < 300; t++) {
            int ns = 1 + rnd.nextInt(40);
            int nt = 1 + rnd.nextInt(40);
            StringBuilder sb1 = new StringBuilder();
            StringBuilder sb2 = new StringBuilder();
            for (int i = 0; i < ns; i++) {
                sb1.append((char) ('a' + rnd.nextInt(6)));
            }
            for (int i = 0; i < nt; i++) {
                sb2.append((char) ('a' + rnd.nextInt(6)));
            }
            String s = sb1.toString(), tt = sb2.toString();
            int a = sol.appendCharacters(s, tt);
            int b = sol.appendCharactersBrute(s, tt);
            if (a != b) {
                System.out.println("对拍失败 s=" + s + " t=" + tt + " 主=" + a + " 暴力=" + b);
                System.exit(1);
            }
        }
        System.out.println("2486 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n + m)
  • 空间复杂度:O(1)

2487. Remove Nodes From Linked List(从链表中移除节点)

题目描述

给你一个链表的头节点 head。

移除每个右侧有一个更大数值的节点。

返回修改后链表的头节点 head。

示例 1

复制代码
`head = [5,2,13,3,8]` → `[13,8]`(5、2、3 右侧均有更大值被移除)

示例 2

复制代码
`head = [1,1,1,1]` → `[1,1,1,1]`(没有需要移除的节点)

提示:

  • 列表中的节点数目在范围 [1, 10^5]
  • 1 <= Node.val <= 10^5

解题思路

解法一:单调栈(主提交版本)

  • 栈中自底向上保持「严格递减」:依次遍历节点,若栈顶值 < 当前值,弹出(被移除),
    直到栈顶值 ≥ 当前值或栈空,再把当前节点压栈。
  • 遍历完栈中自底向上 递减(非严格,相等保留),正好是答案链表;重新链接 next 指针返回。
    解法二:反转链表 + 一趟清扫(对照)
  • 先反转链表,从右到左处理:记右侧见过最大值 maxRight,若当前值 < maxRight 则删除,
    否则保留并更新 maxRight;最后再反转回来。等价于「删除比右侧最大值小的节点」。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)(栈)/ O(1)(反转版)

代码实现

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

public class Solution {
    public static class ListNode {
        int val;
        ListNode next;

        ListNode(int val) {
            this.val = val;
        }
    }

    // 解法一:单调栈(主提交版本)
    public ListNode removeNodes(ListNode head) {
        Deque<ListNode> st = new ArrayDeque<>();
        ListNode cur = head;
        while (cur != null) {
            while (!st.isEmpty() && st.peek().val < cur.val) {
                st.pop();
            }
            st.push(cur);
            cur = cur.next;
        }
        // 栈中自底向上递减,重新串联
        ListNode result = null;
        while (!st.isEmpty()) {
            ListNode node = st.pop();
            node.next = result;
            result = node;
        }
        return result;
    }

    // 解法二:反转 + 清扫(对照)
    public ListNode removeNodesReverse(ListNode head) {
        ListNode rev = reverse(head);
        ListNode dummy = new ListNode(0);
        dummy.next = rev;
        ListNode prev = dummy;
        ListNode cur = rev;
        int maxRight = -1;
        while (cur != null) {
            if (cur.val < maxRight) {
                prev.next = cur.next; // 删除 cur
            } else {
                maxRight = cur.val;
                prev = cur;
            }
            cur = cur.next;
        }
        return reverse(dummy.next);
    }

    private ListNode reverse(ListNode head) {
        ListNode prev = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode nxt = cur.next;
            cur.next = prev;
            prev = cur;
            cur = nxt;
        }
        return prev;
    }

    // ---- 测试辅助 ----
    private static ListNode build(int[] arr) {
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        for (int v : arr) {
            cur.next = new ListNode(v);
            cur = cur.next;
        }
        return dummy.next;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        int[] r1 = toArr(sol.removeNodes(build(new int[]{5, 2, 13, 3, 8})));
        int[] r1b = toArr(sol.removeNodesReverse(build(new int[]{5, 2, 13, 3, 8})));
        assert java.util.Arrays.equals(r1, new int[]{13, 8}) : "2487 例1 主: " + java.util.Arrays.toString(r1);
        assert java.util.Arrays.equals(r1b, new int[]{13, 8}) : "2487 例1 反转: " + java.util.Arrays.toString(r1b);

        int[] r2 = toArr(sol.removeNodes(build(new int[]{1, 1, 1, 1})));
        int[] r2b = toArr(sol.removeNodesReverse(build(new int[]{1, 1, 1, 1})));
        assert java.util.Arrays.equals(r2, new int[]{1, 1, 1, 1}) : "2487 例2 主";
        assert java.util.Arrays.equals(r2b, new int[]{1, 1, 1, 1}) : "2487 例2 反转";

        int[] r3 = toArr(sol.removeNodes(build(new int[]{5, 4, 3, 2, 1})));
        assert java.util.Arrays.equals(r3, new int[]{5, 4, 3, 2, 1}) : "2487 递减主";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(23);
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(20);
            int[] arr = new int[n];
            for (int i = 0; i < n; i++) {
                arr[i] = 1 + rnd.nextInt(50);
            }
            int[] a = toArr(sol.removeNodes(build(arr)));
            int[] b = toArr(sol.removeNodesReverse(build(arr)));
            if (!java.util.Arrays.equals(a, b)) {
                System.out.println("对拍失败 " + java.util.Arrays.toString(arr)
                        + " 主=" + java.util.Arrays.toString(a) + " 反转=" + java.util.Arrays.toString(b));
                System.exit(1);
            }
        }
        System.out.println("2487 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)(栈)/ O(1)(反转版)

2488. Count Subarrays With Median K(统计中位数为 K 的子数组)

题目描述

给你一个长度为 n 的数组 nums,该数组由从 1 到 n 的不同整数组成。另给你一个正整数 k。

统计并返回 nums 中的中位数等于 k 的非空子数组的数目。

注意:

  • 数组的中位数是按递增顺序排列后位于中间的那个元素;如果长度为偶数,则中位数是中间靠左的那个元素。
    例:[2,3,1,4] 中位数为 2,[8,4,3,5,1] 中位数为 4。

示例 1

复制代码
`nums = [3,2,1,4,5], k = 4` → 3([4]、[4,5]、[1,4,5])

示例 2

复制代码
`nums = [2,3,1], k = 3` → 1([3] 是唯一)

提示:

  • n == nums.length,1 <= n <= 10^5
  • 1 <= nums[i], k <= n,nums 中的整数互不相同

解题思路

解法一:哈希表映射「大-小」前缀差(主提交版本)

  • 中位数是 k 当且仅当子数组中 > k 的个数与 < k 的个数满足特定关系:
  • 由于元素互异且 k 出现在数组中唯一位置 pos,含 k 的子数组一定有 pos。
  • 对包含 k 的区间 l, r(l ≤ pos ≤ r):
    令 greater = 区间内 > k 的个数,less = 区间内 < k 的个数,长度 len = greater + less + 1。
  • len 奇数:中位数 = k ⟺ greater == less。
  • len 偶数:中位数(靠左) = k ⟺ greater == less + 1。
  • 把条件统一:记 b = greater - less,
    满足 b == 0(奇长)或 b == 1(偶长,即 greater = less + 1)。
    两个条件都要求区间同时包含 k;统计时以 pos 为界分成左右两半:
  • 左半(含 pos)到右侧扩展,用哈希表记录从 pos 往左的 (b) 出现次数,
    往右扩展并累加:ans += leftb + leftb-1。
    解法二:暴力枚举(对照,仅小规模对拍)
  • 枚举全部 (l, r) 并统计大于/小于 k 的个数按中位数定义判断。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

代码实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class Solution {
    // 解法一:前缀差计数(主提交版本)
    public int countSubarrays(int[] nums, int k) {
        int n = nums.length;
        int pos = -1;
        for (int i = 0; i < n; i++) {
            if (nums[i] == k) {
                pos = i;
                break;
            }
        }
        // 以 pos 为中心的左侧:从 pos 向左累计 (大于-小于)
        Map<Integer, Integer> left = new HashMap<>();
        left.put(0, 1); // 左半只含 k 自身(空延伸)
        int bal = 0;
        for (int i = pos - 1; i >= 0; i--) {
            bal += nums[i] > k ? 1 : -1;
            left.put(bal, left.getOrDefault(bal, 0) + 1);
        }
        // 右侧扩展
        int ans = 0;
        bal = 0;
        for (int j = pos; j < n; j++) {
            if (j > pos) {
                bal += nums[j] > k ? 1 : -1;
            }
            // 右侧前缀差与左侧某 bal0 组合:
            // 总差 = bal0 + bal;奇数长需 0,偶数长需 1
            ans += left.getOrDefault(-bal, 0);       // 奇长 → 总差 0
            ans += left.getOrDefault(1 - bal, 0);    // 偶长 → 总差 1
        }
        return ans;
    }

    // 解法二:暴力(对照,小规模对拍)
    public int countSubarraysBrute(int[] nums, int k) {
        int n = nums.length;
        int ans = 0;
        for (int l = 0; l < n; l++) {
            int greater = 0, less = 0;
            boolean hasK = false;
            for (int r = l; r < n; r++) {
                if (nums[r] == k) {
                    hasK = true;
                } else if (nums[r] > k) {
                    greater++;
                } else {
                    less++;
                }
                if (!hasK) {
                    continue;
                }
                int len = r - l + 1;
                // 中位数靠左:排序后下标 (len-1)/2
                int[] sorted = new int[len]; // 小规模可用
                int idx = 0;
                for (int t = l; t <= r; t++) {
                    sorted[idx++] = nums[t];
                }
                java.util.Arrays.sort(sorted);
                if (sorted[(len - 1) / 2] == k) {
                    ans++;
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.countSubarrays(new int[]{3, 2, 1, 4, 5}, 4) == 3 : "2488 例1 主: " + sol.countSubarrays(new int[]{3, 2, 1, 4, 5}, 4);
        assert sol.countSubarraysBrute(new int[]{3, 2, 1, 4, 5}, 4) == 3 : "2488 例1 暴力";
        assert sol.countSubarrays(new int[]{2, 3, 1}, 3) == 1 : "2488 例2 主";
        assert sol.countSubarraysBrute(new int[]{2, 3, 1}, 3) == 1 : "2488 例2 暴力";

        // 随机排列对拍
        java.util.Random rnd = new java.util.Random(37);
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(12);
            int[] arr = new int[n];
            for (int i = 0; i < n; i++) {
                arr[i] = i + 1;
            }
            // Fisher-Yates 洗牌
            for (int i = n - 1; i > 0; i--) {
                int j = rnd.nextInt(i + 1);
                int tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
            int k = 1 + rnd.nextInt(n);
            int a = sol.countSubarrays(arr, k);
            int b = sol.countSubarraysBrute(arr, k);
            if (a != b) {
                System.out.println("对拍失败 nums=" + java.util.Arrays.toString(arr) + " k=" + k + " 主=" + a + " 暴力=" + b);
                System.exit(1);
            }
        }
        System.out.println("2488 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

2489. Number of Substrings With Fixed Ratio(固定比率的子字符串数)

题目描述

给定一个二进制字符串 s 和两个整数 num1 和 num2(互质)。

比率子串是 s 的子串,其中子串中 0 的数量与 1 的数量之比正好是 num1 : num2。

返回 s 的非空比率子串的个数。

示例 1

复制代码
`s = "0110011", num1 = 1, num2 = 2` → 4

示例 2

复制代码
`s = "10101", num1 = 3, num2 = 1` → 0

提示:

  • 1 <= s.length <= 10^5
  • 1 <= num1, num2 <= s.length
  • num1 和 num2 互质

解题思路

解法一:前缀计数 + 关键值哈希(主提交版本)

  • 前缀统计 zerosi、onesi:子串 [l, r) 的比为 zeros®-zeros(l) : ones®-ones(l) = num1:num2
    ⟺ (zeros®-zeros(l)) * num2 == (ones®-ones(l)) * num1
    ⟺ zeros®*num2 - ones®*num1 == zeros(l)*num2 - ones(l)*num1。
  • 定义 key(i) = zerosi num2 - onesnum1,则子串合法 ⟺ key(l) == key®。
  • 用哈希表统计每个 key 的出现次数,对每个位置 r,贡献 = 此前同 key 的 l 个数。
    (l 中可以允许 0/1 个数为 0 吗?若 num1, num2 都为正,则子串必须同时含 0、1;
    但 key 相同已经保证比例正确。当 num1>0、num2>0 时不需额外条件,若恰好两者为 0 的情况
    由 key 唯一性自然排除------两个正数比例不为 0。)
    解法二:暴力枚举(对照,仅小规模对拍)
  • 直接枚举所有子串并统计 0、1 数量按比例判断。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

代码实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class Solution {
    // 解法一:前缀 key 哈希(主提交版本)
    public long fixedRatio(String s, int num1, int num2) {
        long zeros = 0, ones = 0;
        Map<Long, Long> cnt = new HashMap<>();
        cnt.put(0L, 1L); // 空前缀
        long ans = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '0') {
                zeros++;
            } else {
                ones++;
            }
            long key = zeros * num2 - ones * num1;
            long prev = cnt.getOrDefault(key, 0L);
            ans += prev;
            cnt.put(key, prev + 1);
        }
        return ans;
    }

    // 解法二:暴力(对照,小规模对拍)
    public long fixedRatioBrute(String s, int num1, int num2) {
        int n = s.length();
        long ans = 0;
        for (int l = 0; l < n; l++) {
            long z = 0, o = 0;
            for (int r = l; r < n; r++) {
                if (s.charAt(r) == '0') {
                    z++;
                } else {
                    o++;
                }
                if (z * num2 == o * num1) {
                    ans++;
                }
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.fixedRatio("0110011", 1, 2) == 4 : "2489 例1 主: " + sol.fixedRatio("0110011", 1, 2);
        assert sol.fixedRatioBrute("0110011", 1, 2) == 4 : "2489 例1 暴力";
        assert sol.fixedRatio("10101", 3, 1) == 0 : "2489 例2 主";
        assert sol.fixedRatioBrute("10101", 3, 1) == 0 : "2489 例2 暴力";
        assert sol.fixedRatio("0", 1, 1) == 0 : "2489 单0 主";
        assert sol.fixedRatioBrute("0", 1, 1) == 0 : "2489 单0 暴力";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(41);
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(20);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                sb.append((char) ('0' + rnd.nextInt(2)));
            }
            String s = sb.toString();
            int a1 = 1 + rnd.nextInt(8);
            int a2 = 1 + rnd.nextInt(8);
            long x = sol.fixedRatio(s, a1, a2);
            long y = sol.fixedRatioBrute(s, a1, a2);
            if (x != y) {
                System.out.println("对拍失败 s=" + s + " num1=" + a1 + " num2=" + a2 + " 主=" + x + " 暴力=" + y);
                System.exit(1);
            }
        }
        System.out.println("2489 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

2490. Circular Sentence(回环句)

题目描述

句子是由单个空格分隔的一组单词,且不含前导或尾随空格。单词仅由大写和小写英文字母组成,

且大小写视作不同字符。

如果句子满足下述全部条件,则认为它是一个回环句:

  • 句子中每个单词的最后一个字符等于下一个单词的第一个字符。
  • 最后一个单词的最后一个字符和第一个单词的第一个字符相等。
    返回判定结果。

示例 1

复制代码
`sentence = "leetcode exercises sound delightful"` → true

示例 2

复制代码
`sentence = "eetcode"` → true

示例 3

复制代码
`sentence = "Leetcode is cool"` → false

示例 4

复制代码
`sentence = "Leetcode"` → false(单单词也要首尾相等)

提示:

  • 1 <= sentence.length <= 500
  • sentence 仅由大小写英文字母和空格组成
  • 单词由单个空格分隔,不含前导或尾随空格

解题思路

解法一:相邻字符判定(主提交版本)

  • 回环条件等价于:对每个空格位置 i(si==' '),其左侧字符 si-1 必须等于右侧字符 si+1。
    (这同时覆盖「每个单词末 == 下一单词首」)
  • 另外首字符必须等于末字符(覆盖首尾环接)。
    解法二:split 分词检查(对照)
  • 按空格切分句子,检查相邻词的末/首字符及首词首=末词末。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码实现

java 复制代码
public class Solution {
    // 解法一:相邻字符判定(主提交版本)
    public boolean isCircularSentence(String sentence) {
        int n = sentence.length();
        if (sentence.charAt(0) != sentence.charAt(n - 1)) {
            return false;
        }
        for (int i = 0; i < n; i++) {
            if (sentence.charAt(i) == ' ') {
                if (sentence.charAt(i - 1) != sentence.charAt(i + 1)) {
                    return false;
                }
            }
        }
        return true;
    }

    // 解法二:split 分词(对照)
    public boolean isCircularSentenceSplit(String sentence) {
        String[] words = sentence.split(" ");
        int m = words.length;
        for (int i = 0; i < m; i++) {
            char last = words[i].charAt(words[i].length() - 1);
            char first = words[(i + 1) % m].charAt(0);
            if (last != first) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.isCircularSentence("leetcode exercises sound delightful") : "2490 例1 主";
        assert sol.isCircularSentenceSplit("leetcode exercises sound delightful") : "2490 例1 split";
        assert sol.isCircularSentence("eetcode") : "2490 例2 主";
        assert sol.isCircularSentenceSplit("eetcode") : "2490 例2 split";
        assert !sol.isCircularSentence("Leetcode is cool") : "2490 例3 主";
        assert !sol.isCircularSentenceSplit("Leetcode is cool") : "2490 例3 split";
        assert !sol.isCircularSentence("Leetcode") : "2490 例4 主";
        assert !sol.isCircularSentenceSplit("Leetcode") : "2490 例4 split";
        assert sol.isCircularSentence("a") : "2490 单字符主";
        assert sol.isCircularSentenceSplit("a") : "2490 单字符 split";

        System.out.println("2490 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2491. Divide Players Into Teams of Equal Skill(划分技能点相等的团队)

题目描述

给你一个正整数数组 skill,数组长度为偶数 n,其中 skill[i] 表示第 i 个玩家的技能点。

将所有玩家分成 n/2 个 2 人团队,使每一个团队的技能点之和相等。

团队的化学反应等于团队中玩家的技能点乘积。

返回所有团队的化学反应之和;如果无法使每个团队的技能点之和相等,则返回 -1。

示例 1

复制代码
`skill = [3,2,5,1,3,4]` → 22((1,5),(2,4),(3,3) 和均为 6)

示例 2

复制代码
`skill = [3,4]` → 12

示例 3

复制代码
`skill = [1,1,2,3]` → -1

提示:

  • 2 <= skill.length <= 10^5,长度是偶数
  • 1 <= skill[i] <= 1000

解题思路

解法一:排序 + 双指针(主提交版本)

  • 排序后,和相等的配对必然是「最小 + 最大」:若存在可行配对,任意最优配对均可化为
    最小配最大(若最小 a 配 b、最大 c 配 d,可交换使 a 与 c 配对,和不变)。
  • 排序后首尾配对,校验每对和都等于 target = 总和 * 2 / n(先整体除以队数),
    乘积累加即可;一旦有对和不等于 target 返回 -1。
    解法二:频次哈希(对照)
  • 统计每个技能点出现频次,若 skilli=x 则必须有 cnttarget-x 相应匹配,校验配对并累乘。
    复杂度:
  • 时间复杂度:O(n log n)
  • 空间复杂度:O(1)(排序原地)

代码实现

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

public class Solution {
    // 解法一:排序 + 双指针(主提交版本)
    public long dividePlayers(int[] skill) {
        int n = skill.length;
        long total = 0;
        for (int v : skill) {
            total += v;
        }
        int teams = n / 2;
        if (total % teams != 0) {
            return -1;
        }
        long target = total / teams;
        Arrays.sort(skill);
        long ans = 0;
        int i = 0, j = n - 1;
        while (i < j) {
            if (skill[i] + skill[j] != target) {
                return -1;
            }
            ans += (long) skill[i] * skill[j];
            i++;
            j--;
        }
        return ans;
    }

    // 解法二:频次哈希(对照)
    public long dividePlayersFreq(int[] skill) {
        int n = skill.length;
        int maxV = 1000;
        int[] cnt = new int[maxV + 1];
        long total = 0;
        for (int v : skill) {
            cnt[v]++;
            total += v;
        }
        int teams = n / 2;
        if (total % teams != 0) {
            return -1;
        }
        long target = total / teams;
        long ans = 0;
        for (int x = 1; x <= maxV; x++) {
            if (cnt[x] == 0) {
                continue;
            }
            int y = (int) (target - x);
            if (y < 1 || y > maxV || cnt[y] < cnt[x]) {
                return -1;
            }
            if (y == x) {
                if (cnt[x] % 2 != 0) {
                    return -1;
                }
                ans += (long) x * y * (cnt[x] / 2);
                cnt[x] = 0;
            } else {
                ans += (long) x * y * cnt[x];
                cnt[y] -= cnt[x];
                cnt[x] = 0;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.dividePlayers(new int[]{3, 2, 5, 1, 3, 4}) == 22 : "2491 例1 主";
        assert sol.dividePlayersFreq(new int[]{3, 2, 5, 1, 3, 4}) == 22 : "2491 例1 频次";
        assert sol.dividePlayers(new int[]{3, 4}) == 12 : "2491 例2 主";
        assert sol.dividePlayersFreq(new int[]{3, 4}) == 12 : "2491 例2 频次";
        assert sol.dividePlayers(new int[]{1, 1, 2, 3}) == -1 : "2491 例3 主";
        assert sol.dividePlayersFreq(new int[]{1, 1, 2, 3}) == -1 : "2491 例3 频次";

        java.util.Random rnd = new java.util.Random(43);
        for (int t = 0; t < 300; t++) {
            int n = 2 + 2 * rnd.nextInt(20);
            int[] arr = new int[n];
            for (int i = 0; i < n; i++) {
                arr[i] = 1 + rnd.nextInt(50);
            }
            long a = sol.dividePlayers(arr);
            long b = sol.dividePlayersFreq(arr);
            if (a != b) {
                System.out.println("对拍失败 " + Arrays.toString(arr) + " 主=" + a + " 频次=" + b);
                System.exit(1);
            }
        }
        System.out.println("2491 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(1)(排序原地)

2492. Minimum Score of a Path Between Two Cities(两个城市间路径的最小分数)

题目描述

给你一个正整数 n,表示总共有 n 个城市,城市从 1 到 n 编号。

roads[i] = [ai, bi, distancei] 表示城市 ai 和 bi 之间有一条双向道路,道路距离为 distancei。

城市构成的图不一定是连通的。

两个城市之间一条路径的分数定义为这条路径中道路的最小距离。

返回城市 1 和城市 n 之间的所有路径的最小分数。

注意:一条路径可以多次包含同一条道路,也可以多次到达城市 1 和城市 n。

测试数据保证城市 1 和城市 n 之间至少有一条路径。

示例 1

复制代码
`n = 4, roads = [[1,2,9],[2,3,6],[2,4,5],[1,4,7]]` → 5

示例 2

复制代码
`n = 4, roads = [[1,2,2],[1,3,4],[3,4,7]]` → 2(路径 1->2->1->3->4,分数 min(2,2,4,7)=2)

提示:

  • 2 <= n <= 10^5
  • 1 <= roads.length <= 10^5
  • 1 <= distancei <= 10^4

解题思路

解法一:连通分量内最小边(主提交版本)

  • 题目允许来回走,因此「连通分量」内任意两点都可到达:城市 1 与城市 n 同属一个连通分量
    (题目保证有路径),该分量内所有边都可以被某条路径覆盖(重复走即可)。
  • 因此答案 = 1 所在连通分量中权重最小的边。
  • 实现:BFS/DFS 从 1 出发遍历并记录最小边权。
    解法二:并查集(对照)
  • 并查集合并所有边,找到 1 与 n 的共同根后扫描全部边,取连接同分量两点中的最小权。
    复杂度:
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(n + m)

代码实现

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

public class Solution {
    // 解法一:BFS 连通分量(主提交版本)
    public int minScore(int n, int[][] roads) {
        List<int[]>[] g = new List[n + 1];
        for (int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>();
        }
        for (int[] r : roads) {
            g[r[0]].add(new int[]{r[1], r[2]});
            g[r[1]].add(new int[]{r[0], r[2]});
        }
        boolean[] vis = new boolean[n + 1];
        ArrayDeque<Integer> q = new ArrayDeque<>();
        q.offer(1);
        vis[1] = true;
        int ans = Integer.MAX_VALUE;
        while (!q.isEmpty()) {
            int u = q.poll();
            for (int[] e : g[u]) {
                ans = Math.min(ans, e[1]); // 覆盖该边(进/出都算)
                if (!vis[e[0]]) {
                    vis[e[0]] = true;
                    q.offer(e[0]);
                }
            }
        }
        return ans;
    }

    // 解法二:并查集(对照)
    public int minScoreUnion(int n, int[][] roads) {
        int[] parent = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            parent[i] = i;
        }
        for (int[] r : roads) {
            union(parent, r[0], r[1]);
        }
        int root = find(parent, 1);
        int ans = Integer.MAX_VALUE;
        for (int[] r : roads) {
            if (find(parent, r[0]) == root) { // 两点都在 1 的分量(r[1] 也必然同根)
                ans = Math.min(ans, r[2]);
            }
        }
        return ans;
    }

    private int find(int[] p, int x) {
        while (p[x] != x) {
            p[x] = p[p[x]];
            x = p[x];
        }
        return x;
    }

    private void union(int[] p, int a, int b) {
        int ra = find(p, a), rb = find(p, b);
        if (ra != rb) {
            p[ra] = rb;
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.minScore(4, new int[][]{{1, 2, 9}, {2, 3, 6}, {2, 4, 5}, {1, 4, 7}}) == 5 : "2492 例1 主";
        assert sol.minScoreUnion(4, new int[][]{{1, 2, 9}, {2, 3, 6}, {2, 4, 5}, {1, 4, 7}}) == 5 : "2492 例1 并查集";
        assert sol.minScore(4, new int[][]{{1, 2, 2}, {1, 3, 4}, {3, 4, 7}}) == 2 : "2492 例2 主";
        assert sol.minScoreUnion(4, new int[][]{{1, 2, 2}, {1, 3, 4}, {3, 4, 7}}) == 2 : "2492 例2 并查集";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(47);
        for (int t = 0; t < 200; t++) {
            int n = 2 + rnd.nextInt(10);
            java.util.Set<Long> used = new java.util.HashSet<>();
            List<int[]> roads = new ArrayList<>();
            // 保证 1 与 n 连通:建一条链
            for (int i = 1; i < n; i++) {
                int w = 1 + rnd.nextInt(50);
                roads.add(new int[]{i, i + 1, w});
                used.add(key(i, i + 1));
            }
            int extra = rnd.nextInt(10);
            for (int i = 0; i < extra; i++) {
                int a = 1 + rnd.nextInt(n);
                int b = 1 + rnd.nextInt(n);
                if (a == b || !used.add(key(a, b))) {
                    continue;
                }
                roads.add(new int[]{a, b, 1 + rnd.nextInt(50)});
            }
            int[][] rr = roads.toArray(new int[0][]);
            int x = sol.minScore(n, rr);
            int y = sol.minScoreUnion(n, rr);
            if (x != y) {
                System.out.println("对拍失败 n=" + n + " roads=" + Arrays.deepToString(rr) + " 主=" + x + " 并查集=" + y);
                System.exit(1);
            }
        }
        System.out.println("2492 全部 PASS");
    }

    private static long key(int a, int b) {
        return a < b ? a * 10000L + b : b * 10000L + a;
    }
}

复杂度分析

  • 时间复杂度:O(n + m)
  • 空间复杂度:O(n + m)

2493. Divide Nodes Into the Maximum Number of Groups(将节点分成尽可能多的组)

题目描述

给你一个正整数 n,表示一个无向图中的节点数目,节点编号从 1 到 n。

edges[i] = [ai, bi] 表示节点 ai 和 bi 之间有一条双向边。注意给定的图可能是不连通的。

请你将图划分为 m 个组(编号从 1 开始),满足:

  • 图中每个节点都只属于一个组。
  • 图中每条边连接的两个点 `ai`, `bi`,若 ai 属于 x 组、bi 属于 y 组,则 |y - x| = 1。
    返回最多可以将节点分为多少个组(最大的 m);如果没办法在给定条件下分组,返回 -1。

示例 1

复制代码
`n = 6, edges = [[1,2],[1,4],[1,5],[2,6],[2,3],[4,6]]` → 4

示例 2

复制代码
`n = 3, edges = [[1,2],[2,3],[3,1]]` → -1(奇环不满足条件)

提示:

  • 1 <= n <= 500
  • 1 <= edges.length <= 10^4

解题思路

解法一:BFS 分层 + 按连通块取最大值(主提交版本)

  • 分组条件 |组号差| = 1 等价于给图做「二分染色且相邻层号连续」:
    固定某个根 u 的组号为 1,则 u 可达的每个节点组号由最短路长度唯一决定(BFS 层数)。
  • 由于可能有多种起点,对每个连通分量:
  1. 先检查是否二分图(奇环 → -1)。
  2. 枚举分量内每个节点作为起点做 BFS,得到的最大层数即该起点的「直径层数」;
    取所有起点中的最大值作为该分量的贡献。
  • 各分量贡献相加即答案。
    解法二:并查集分组 + 逐起点 BFS(对照)
  • 并查集把边两端合并确定分量,对每个分量枚举起点 BFS 计算层数,逻辑同解法一,写法不同。
    复杂度:
  • 时间复杂度:O(n * (n + m)),n ≤ 500 可行
  • 空间复杂度:O(n + m)

代码实现

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

public class Solution {
    private List<Integer>[] g;

    // 解法一:连通分量 + 枚举起点 BFS(主提交版本)
    public int magnificentSets(int n, int[][] edges) {
        g = new List[n + 1];
        for (int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>();
        }
        for (int[] e : edges) {
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        int[] comp = new int[n + 1];
        int compCnt = 0;
        List<List<Integer>> comps = new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            if (comp[i] == 0) {
                compCnt++;
                List<Integer> nodes = new ArrayList<>();
                // BFS 收集分量
                ArrayDeque<Integer> q = new ArrayDeque<>();
                q.offer(i);
                comp[i] = compCnt;
                while (!q.isEmpty()) {
                    int u = q.poll();
                    nodes.add(u);
                    for (int v : g[u]) {
                        if (comp[v] == 0) {
                            comp[v] = compCnt;
                            q.offer(v);
                        }
                    }
                }
                comps.add(nodes);
            }
        }
        int ans = 0;
        for (List<Integer> nodes : comps) {
            int best = -1;
            for (int start : nodes) {
                int depth = bfsDepth(start); // 从 start 出发的最大层数
                if (depth == -1) {
                    return -1; // 奇环 → 不可染色
                }
                best = Math.max(best, depth);
            }
            ans += best;
        }
        return ans;
    }

    // 返回以 start 为第 1 层时最大层数;若发现奇环(同层边)返回 -1
    private int bfsDepth(int start) {
        int[] level = new int[g.length];
        Arrays.fill(level, -1);
        level[start] = 1;
        ArrayDeque<Integer> q = new ArrayDeque<>();
        q.offer(start);
        int maxLevel = 1;
        while (!q.isEmpty()) {
            int u = q.poll();
            for (int v : g[u]) {
                if (level[v] == -1) {
                    level[v] = level[u] + 1;
                    q.offer(v);
                } else if (level[v] != level[u] - 1 && level[v] != level[u] + 1) {
                    // 相邻节点层号差必须为 1:违反则奇环
                    return -1;
                }
            }
            maxLevel = Math.max(maxLevel, level[u]);
        }
        return maxLevel;
    }

    // 解法二:并查集确定分量 + BFS(对照)
    public int magnificentSetsUnion(int n, int[][] edges) {
        g = new List[n + 1];
        for (int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>();
        }
        int[] parent = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            parent[i] = i;
        }
        for (int[] e : edges) {
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
            union(parent, e[0], e[1]);
        }
        // root -> nodes
        java.util.Map<Integer, List<Integer>> compMap = new java.util.HashMap<>();
        for (int i = 1; i <= n; i++) {
            int r = find(parent, i);
            compMap.computeIfAbsent(r, k -> new ArrayList<>()).add(i);
        }
        int ans = 0;
        for (List<Integer> nodes : compMap.values()) {
            int best = -1;
            for (int start : nodes) {
                int depth = bfsDepth(start);
                if (depth == -1) {
                    return -1;
                }
                best = Math.max(best, depth);
            }
            ans += best;
        }
        return ans;
    }

    private int find(int[] p, int x) {
        while (p[x] != x) {
            p[x] = p[p[x]];
            x = p[x];
        }
        return x;
    }

    private void union(int[] p, int a, int b) {
        int ra = find(p, a), rb = find(p, b);
        if (ra != rb) {
            p[ra] = rb;
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.magnificentSets(6, new int[][]{{1, 2}, {1, 4}, {1, 5}, {2, 6}, {2, 3}, {4, 6}}) == 4 : "2493 例1 主";
        assert sol.magnificentSetsUnion(6, new int[][]{{1, 2}, {1, 4}, {1, 5}, {2, 6}, {2, 3}, {4, 6}}) == 4 : "2493 例1 并查集";
        assert sol.magnificentSets(3, new int[][]{{1, 2}, {2, 3}, {3, 1}}) == -1 : "2493 例2 主";
        assert sol.magnificentSetsUnion(3, new int[][]{{1, 2}, {2, 3}, {3, 1}}) == -1 : "2493 例2 并查集";

        // 链式图(二分)对拍:n=4 链 1-2-3-4 → 最大分组 4
        assert sol.magnificentSets(4, new int[][]{{1, 2}, {2, 3}, {3, 4}}) == 4 : "2493 链主";
        assert sol.magnificentSetsUnion(4, new int[][]{{1, 2}, {2, 3}, {3, 4}}) == 4 : "2493 链并查集";
        // 偶环:4 环 1-2-3-4-1 → 最大 4
        assert sol.magnificentSets(4, new int[][]{{1, 2}, {2, 3}, {3, 4}, {4, 1}}) == 4 : "2493 偶环主";

        // 随机二分图对拍
        java.util.Random rnd = new java.util.Random(53);
        for (int t = 0; t < 120; t++) {
            int n = 2 + rnd.nextInt(10);
            java.util.Set<Long> used = new java.util.HashSet<>();
            List<int[]> es = new ArrayList<>();
            for (int i = 1; i < n; i++) { // 先随机树(保证连通二分)
                int p = 1 + rnd.nextInt(i);
                es.add(new int[]{p, i + 1});
                used.add(key(p, i + 1));
            }
            int extra = rnd.nextInt(6);
            for (int i = 0; i < extra; i++) {
                int a = 1 + rnd.nextInt(n), b = 1 + rnd.nextInt(n);
                if (a == b || !used.add(key(a, b))) {
                    continue;
                }
                es.add(new int[]{a, b});
            }
            int[][] ee = es.toArray(new int[0][]);
            int x = sol.magnificentSets(n, ee);
            int y = sol.magnificentSetsUnion(n, ee);
            if (x != y) {
                System.out.println("对拍失败 n=" + n + " edges=" + Arrays.deepToString(ee) + " 主=" + x + " 并查集=" + y);
                System.exit(1);
            }
        }
        System.out.println("2493 全部 PASS");
    }

    private static long key(int a, int b) {
        return a < b ? a * 1000L + b : b * 1000L + a;
    }
}

复杂度分析

  • 时间复杂度:O(n * (n + m)),n ≤ 500 可行
  • 空间复杂度:O(n + m)

2494. Merge Overlapping Events in the Same Hall(合并在同一个大厅重叠的活动)

题目类型:数据库(SQL)

题目描述

表: HallEvents

复制代码
+-------------+------+
| Column Name | Type |
+-------------+------+
| hall_id     | int  |
| start_day   | date |
| end_day     | date |
+-------------+------+

该表可能包含重复字段。该表的每一行表示活动的开始日期和结束日期,以及活动举行的大厅。

编写解决方案,合并在同一个大厅 举行的所有重叠活动。如果两个活动至少有一天相同,那么它们就是重叠的。

以任意顺序返回结果表。

示例 1:

复制代码
输入:
HallEvents 表:
+---------+------------+------------+
| hall_id | start_day  | end_day    |
+---------+------------+------------+
| 1       | 2023-01-13 | 2023-01-14 |
| 1       | 2023-01-14 | 2023-01-17 |
| 1       | 2023-01-18 | 2023-01-25 |
| 2       | 2022-12-09 | 2022-12-23 |
| 2       | 2022-12-13 | 2022-12-17 |
| 3       | 2022-12-01 | 2023-01-30 |
+---------+------------+------------+
输出:
+---------+------------+------------+
| hall_id | start_day  | end_day    |
+---------+------------+------------+
| 1       | 2023-01-13 | 2023-01-17 |
| 1       | 2023-01-18 | 2023-01-25 |
| 2       | 2022-12-09 | 2022-12-23 |
| 3       | 2022-12-01 | 2023-01-30 |
+---------+------------+------------+
解释:
大厅 1:
- 两个活动 ["2023-01-13", "2023-01-14"] 和 ["2023-01-14", "2023-01-17"] 重叠,合并为 ["2023-01-13", "2023-01-17"]。
- 活动 ["2023-01-18", "2023-01-25"] 不与任何其他活动重叠,保持原样。
大厅 2:
- 两个活动 ["2022-12-09", "2022-12-23"] 和 ["2022-12-13", "2022-12-17"] 重叠,合并为 ["2022-12-09", "2022-12-23"]。
大厅 3:
- 大厅只有一个活动,原样返回。注意:只分别考虑每个大厅的活动。

解题思路

解法一:窗口求「组起始标记」后分组(推荐)

  • 区间合并经典套路:按 hall_id 分组、按 start_day(再按 end_day)排序后,用窗口函数把每个活动与前一行比较。
  • 用 MAX(end_day) OVER (PARTITION BY hall_id ORDER BY start_day, end_day ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) 取「此前所有活动的最远结束日」prev_max_end。若当前 start_day <= prev_max_end,说明与前序段重叠(含端点相接:>= 重叠是指至少一天相同,即 start_day <= prev_max_end 即重叠),应归入同一组;否则开启新组。
  • 用累计和(SUM(flag) OVER (...)) 生成组号:flag = (start_day > prev_max_end) ? 1 : 0,组号 = 该行及其之前 flag 的累加。
  • 最后按 hall_id, 组号 聚合成 MIN(start_day)、MAX(end_day)。

解法二(对照):NOT EXISTS 找每组合并起点 + 终点同起点归并

  • 先找出「不与同一大厅任何更早活动重叠」的活动作为合并段的起点(每条记录是一个组的左端点);对每个组起点,在其左侧段内取 MAX(end_day) 作为组终点------但终点归属需要通过「在同一组内但不超过起点所在组的连通范围」判断,处理较繁琐。这里为便于理解改用自连接传递闭包思路:start_day 落在某个已合并段内则吸收。
  • 实际推荐直接使用解法一窗口法,简洁且易读。

代码实现

sql 复制代码
-- 解法一(推荐):窗口 prev_max_end + 组号累加
WITH ordered AS (
    SELECT hall_id, start_day, end_day,
           MAX(end_day) OVER (
               PARTITION BY hall_id
               ORDER BY start_day, end_day
               ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
           ) AS prev_max_end
    FROM HallEvents
),
flagged AS (
    SELECT hall_id, start_day, end_day,
           CASE WHEN prev_max_end IS NULL OR start_day > prev_max_end
                THEN 1 ELSE 0 END AS is_new_group
    FROM ordered
),
grouped AS (
    SELECT hall_id, start_day, end_day,
           SUM(is_new_group) OVER (
               PARTITION BY hall_id
               ORDER BY start_day, end_day
               ROWS UNBOUNDED PRECEDING
           ) AS grp
    FROM flagged
)
SELECT hall_id,
       MIN(start_day) AS start_day,
       MAX(end_day)   AS end_day
FROM grouped
GROUP BY hall_id, grp
ORDER BY hall_id, start_day;
sql 复制代码
-- 解法二(对照):NOT EXISTS 找组起点 + 组号传播
-- 每个活动的组号定义为:向前追溯到第一个不与更早活动重叠的活动的行号
WITH ordered AS (
    SELECT hall_id, start_day, end_day,
           ROW_NUMBER() OVER (PARTITION BY hall_id ORDER BY start_day, end_day) AS rn
    FROM HallEvents
),
is_start AS (
    SELECT o1.hall_id, o1.start_day, o1.end_day, o1.rn,
           CASE WHEN NOT EXISTS (
               SELECT 1 FROM ordered o2
               WHERE o2.hall_id = o1.hall_id
                 AND o2.rn < o1.rn
                 AND o2.start_day <= o1.end_day
                 AND o1.start_day <= o2.end_day
           ) THEN 1 ELSE 0 END AS is_new
    FROM ordered o1
),
grouped AS (
    SELECT hall_id, start_day, end_day,
           SUM(is_new) OVER (PARTITION BY hall_id ORDER BY rn) AS grp
    FROM is_start
)
SELECT hall_id,
       MIN(start_day) AS start_day,
       MAX(end_day)   AS end_day
FROM grouped
GROUP BY hall_id, grp
ORDER BY hall_id, start_day;

复杂度分析

  • 时间复杂度 :O(n log n),n 为 HallEvents 行数;每大厅按日期排序是主要开销。
  • 空间复杂度 :O(n),窗口函数的中间结果与行数成正比。

2495. Number of Subarrays Having Even Product(乘积为偶数的子数组数)

题目描述

给定一个整数数组 nums,返回具有偶数乘积的 nums 子数组的数目。

示例 1

复制代码
`nums = [9,6,7,13]` → 6(乘积为偶数的子数组共 6 个)

示例 2

复制代码
`nums = [7,3,5]` → 0(没有乘积为偶数的子数组)

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

解题思路

解法一:偶数组计数取反(主提交版本)

  • 乘积为偶数 ⟺ 子数组中至少含一个偶数元素。
  • 总数 = n*(n+1)/2,减去「全是奇数」的子数组数。
  • 对每段连续的奇数,长度为 len,贡献的纯奇子数组 = len*(len+1)/2。
    解法二:滑窗计数「以 r 结尾且乘积为偶数」的子数组(对照)
  • 维护 lastEven = 最后一个偶数位置:以 r 结尾的乘积偶数子数组起点 ∈ 0...lastEven,
    共 lastEven + 1 个;累加。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码实现

java 复制代码
public class Solution {
    // 解法一:总数 - 纯奇数子数组(主提交版本)
    public long evenProduct(int[] nums) {
        int n = nums.length;
        long total = (long) n * (n + 1) / 2;
        long odd = 0;
        long cur = 0;
        for (int v : nums) {
            if (v % 2 == 1) {
                cur++;
            } else {
                odd += cur * (cur + 1) / 2;
                cur = 0;
            }
        }
        odd += cur * (cur + 1) / 2;
        return total - odd;
    }

    // 解法二:以 r 结尾累加(对照)
    public long evenProductLast(int[] nums) {
        long ans = 0;
        int lastEven = -1;
        for (int r = 0; r < nums.length; r++) {
            if (nums[r] % 2 == 0) {
                lastEven = r;
            }
            if (lastEven != -1) {
                ans += lastEven + 1;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.evenProduct(new int[]{9, 6, 7, 13}) == 6 : "2495 例1 主";
        assert sol.evenProductLast(new int[]{9, 6, 7, 13}) == 6 : "2495 例1 滑窗";
        assert sol.evenProduct(new int[]{7, 3, 5}) == 0 : "2495 例2 主";
        assert sol.evenProductLast(new int[]{7, 3, 5}) == 0 : "2495 例2 滑窗";
        assert sol.evenProduct(new int[]{2}) == 1 : "2495 单偶 主";
        assert sol.evenProductLast(new int[]{2}) == 1 : "2495 单偶 滑窗";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(57);
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(60);
            int[] arr = new int[n];
            for (int i = 0; i < n; i++) {
                arr[i] = 1 + rnd.nextInt(30);
            }
            long a = sol.evenProduct(arr);
            long b = sol.evenProductLast(arr);
            if (a != b) {
                System.out.println("对拍失败 " + java.util.Arrays.toString(arr) + " 主=" + a + " 滑窗=" + b);
                System.exit(1);
            }
        }
        System.out.println("2495 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2496. Maximum Value of a String in an Array(数组中字符串的最大值)

题目描述

一个由字母和数字组成的字符串的值定义如下:

  • 如果字符串只包含数字,那么值为该字符串在 10 进制下所表示的数字。
  • 否则,值为字符串的长度。
    给你一个字符串数组 strs,每个字符串都只由字母和数字组成,返回 strs 中字符串的最大值。

示例 1

复制代码
`strs = ["alic3","bob","3","4","00000"]` → 5("00000" 纯数字值为 0,最大为 "alic3" 的长度 5)

示例 2

复制代码
`strs = ["1","01","001","0001"]` → 1(全是纯数字,值都为 1)

提示:

  • 1 <= strs.length <= 100
  • 1 <= strs[i].length <= 9
  • strs[i] 只包含小写英文字母和数字

解题思路

解法一:逐串判定(主提交版本)

  • 对每个字符串:检查是否全为数字;是则用 Integer.parseInt 求值,否则取长度。
  • 维护全局最大值。
    解法二:正则匹配(对照)
  • 用 matches("\\d+") 判断纯数字,逻辑等价。
    复杂度:
  • 时间复杂度:O(总字符数)
  • 空间复杂度:O(1)

代码实现

java 复制代码
public class Solution {
    // 解法一:逐串判定(主提交版本)
    public int maximumValue(String[] strs) {
        int ans = 0;
        for (String s : strs) {
            boolean allDigit = true;
            for (int i = 0; i < s.length(); i++) {
                if (!Character.isDigit(s.charAt(i))) {
                    allDigit = false;
                    break;
                }
            }
            int val = allDigit ? Integer.parseInt(s) : s.length();
            ans = Math.max(ans, val);
        }
        return ans;
    }

    // 解法二:正则(对照)
    public int maximumValueRegex(String[] strs) {
        int ans = 0;
        for (String s : strs) {
            int val = s.matches("\\d+") ? Integer.parseInt(s) : s.length();
            ans = Math.max(ans, val);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.maximumValue(new String[]{"alic3", "bob", "3", "4", "00000"}) == 5 : "2496 例1 主";
        assert sol.maximumValueRegex(new String[]{"alic3", "bob", "3", "4", "00000"}) == 5 : "2496 例1 正则";
        assert sol.maximumValue(new String[]{"1", "01", "001", "0001"}) == 1 : "2496 例2 主";
        assert sol.maximumValueRegex(new String[]{"1", "01", "001", "0001"}) == 1 : "2496 例2 正则";
        assert sol.maximumValue(new String[]{"000"}) == 0 : "2496 全零主";
        assert sol.maximumValueRegex(new String[]{"000"}) == 0 : "2496 全零正则";

        System.out.println("2496 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(总字符数)
  • 空间复杂度:O(1)

2497. Maximum Star Sum of a Graph(图中最大星和)

题目描述

给你一个 n 个点的无向图,节点从 0 到 n-1 编号。给你一个长度为 n 的下标从 0 开始的整数数组

vals,其中 vals[i] 表示第 i 个节点的值。

edges[i] = [ai, bi] 表示节点 ai 和 bi 之间有一条双向边。

星图是给定图中的一个子图,它包含一个中心节点和 0 个或更多个邻居。

星和定义为星图中所有节点值的和。

给你一个整数 k,返回至多包含 k 条边的星图中的最大星和。

示例 1

复制代码
`vals = [1,2,3,4,10,-10,-20], edges = [[0,1],[1,2],[1,3],[3,4],[3,5],[3,6]], k = 2` → 16(中心 3 + 邻居 1、4)

示例 2

复制代码
`vals = [-5], edges = [], k = 0` → -5(只有一个星图,节点 0 自己)

提示:

  • n == vals.length,1 <= n <= 10^5
  • -10^4 <= vals[i] <= 10^4
  • 0 <= edges.length <= min(n*(n-1)/2, 10^5)
  • 0 <= k <= n - 1

解题思路

解法一:贪心取正邻居(主提交版本)

  • 星图以某节点 u 为中心,可选的邻居为 u 的邻接点。为最大化星和:
  • 中心值固定要取(无论正负,中心都计入该星图)。
  • 邻居中最多取 k 个,显然取「值为正且最大」的若干邻居;值为负的邻居不取(除非必须,
    但 k 是「至多」,可取 0 条边,因此负邻居一律不取)。
  • 实现:为每个节点收集邻接点值,排序后累加前 min(k, 正数个数) 个正数,加上中心值取 max。
    解法二:小顶堆维护 Top-k 正邻居(对照)
  • 对每个节点维护 size ≤ k 的小顶堆只存正邻居,堆和为候选星和,取 max。
    复杂度:
  • 时间复杂度:O(m + Σ deg(u) log deg(u)),可简化 O(m log m)
  • 空间复杂度:O(n + m)

代码实现

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

public class Solution {
    // 解法一:邻接值排序贪心(主提交版本)
    public int maxStarSum(int[] vals, int[][] edges, int k) {
        int n = vals.length;
        List<List<Integer>> adj = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            adj.add(new ArrayList<>());
        }
        for (int[] e : edges) {
            adj.get(e[0]).add(vals[e[1]]);
            adj.get(e[1]).add(vals[e[0]]);
        }
        int ans = Integer.MIN_VALUE;
        for (int u = 0; u < n; u++) {
            List<Integer> neighbors = adj.get(u);
            neighbors.sort((a, b) -> b - a); // 降序
            int sum = vals[u];
            int take = Math.min(k, neighbors.size());
            for (int i = 0; i < take; i++) {
                if (neighbors.get(i) > 0) {
                    sum += neighbors.get(i);
                } else {
                    break; // 降序,后续都 ≤ 0
                }
            }
            ans = Math.max(ans, sum);
        }
        return ans;
    }

    // 解法二:小顶堆 Top-k(对照)
    public int maxStarSumHeap(int[] vals, int[][] edges, int k) {
        int n = vals.length;
        List<List<Integer>> adj = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            adj.add(new ArrayList<>());
        }
        for (int[] e : edges) {
            adj.get(e[0]).add(vals[e[1]]);
            adj.get(e[1]).add(vals[e[0]]);
        }
        int ans = Integer.MIN_VALUE;
        for (int u = 0; u < n; u++) {
            java.util.PriorityQueue<Integer> pq = new java.util.PriorityQueue<>(); // 小顶堆
            int sum = vals[u];
            for (int w : adj.get(u)) {
                if (w <= 0) {
                    continue;
                }
                if (pq.size() < k) {
                    pq.offer(w);
                    sum += w;
                } else if (!pq.isEmpty() && w > pq.peek()) {
                    sum += w - pq.poll();
                    pq.offer(w);
                }
            }
            ans = Math.max(ans, sum);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.maxStarSum(new int[]{1, 2, 3, 4, 10, -10, -20},
                new int[][]{{0, 1}, {1, 2}, {1, 3}, {3, 4}, {3, 5}, {3, 6}}, 2) == 16 : "2497 例1 主";
        assert sol.maxStarSumHeap(new int[]{1, 2, 3, 4, 10, -10, -20},
                new int[][]{{0, 1}, {1, 2}, {1, 3}, {3, 4}, {3, 5}, {3, 6}}, 2) == 16 : "2497 例1 堆";

        assert sol.maxStarSum(new int[]{-5}, new int[][]{}, 0) == -5 : "2497 例2 主";
        assert sol.maxStarSumHeap(new int[]{-5}, new int[][]{}, 0) == -5 : "2497 例2 堆";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(59);
        for (int t = 0; t < 200; t++) {
            int n = 1 + rnd.nextInt(15);
            int[] vals = new int[n];
            for (int i = 0; i < n; i++) {
                vals[i] = -10 + rnd.nextInt(21);
            }
            java.util.Set<Long> used = new java.util.HashSet<>();
            List<int[]> es = new ArrayList<>();
            int m = rnd.nextInt(Math.max(1, n * (n - 1) / 2));
            for (int i = 0; i < m; i++) {
                int a = rnd.nextInt(n), b = rnd.nextInt(n);
                if (a == b || !used.add(key(a, b))) {
                    continue;
                }
                es.add(new int[]{a, b});
            }
            int[][] ee = es.toArray(new int[0][]);
            int k = rnd.nextInt(Math.max(1, n));
            int x = sol.maxStarSum(vals, ee, k);
            int y = sol.maxStarSumHeap(vals, ee, k);
            if (x != y) {
                System.out.println("对拍失败 vals=" + java.util.Arrays.toString(vals)
                        + " edges=" + java.util.Arrays.deepToString(ee) + " k=" + k + " 主=" + x + " 堆=" + y);
                System.exit(1);
            }
        }
        System.out.println("2497 全部 PASS");
    }

    private static long key(int a, int b) {
        return a < b ? a * 1000L + b : b * 1000L + a;
    }
}

复杂度分析

  • 时间复杂度:O(m + Σ deg(u) log deg(u)),可简化 O(m log m)
  • 空间复杂度:O(n + m)

2498. Frog Jump II(青蛙过河 II)

题目描述

给你一个下标从 0 开始的整数数组 stones,数组中的元素严格递增,表示一条河中石头的位置。

一只青蛙一开始在第一块石头上,它想到达最后一块石头,然后回到第一块石头。同时每块石头至多到达一次。

一次跳跃的长度是青蛙跳跃前和跳跃后所在两块石头之间的距离。

一条路径的代价是这条路径里的最大跳跃长度。

返回这只青蛙的最小代价。

示例 1

复制代码
`stones = [0,2,5,6,7]` → 5

示例 2

复制代码
`stones = [0,3,9]` → 9

提示:

  • 2 <= stones.length <= 10^5
  • 0 <= stones[i] <= 10^9
  • stones[0] == 0,元素严格递增

解题思路

解法一:贪心跳「隔一格」(主提交版本)

  • 去程最优策略是「跳过一块石头落下一块」:从 0 → 2 → 4 → ...,回程落在奇数位 → ... → 1 → 0。
    这样每块石头恰好踩一次(终点/起点除外),且任意相邻两次使用的石头不相邻,最大跳跃长度
    出现在「隔着的那一跳」。
  • 形式化:最小代价 = max(stonesi - stonesi-2)(i 从 2 到 n-1),即隔一块石头的最大间距。
    因为:去程走偶数下标(0,2,4,...),回程走奇数下标(...,5,3,1,0),任何一跳都是
    stonesi - stonesi-2(或回程同样)。这些覆盖了所有连续两次使用的石头,故答案是隔距最大值。
    解法二:二分答案 + 可行性检查(对照)
  • 对答案 X 二分:检查是否存在一条路径使每跳 ≤ X。可行性:去程贪心跳最远可行石头(不同时被
    回程占用)。标准判定:每隔一块石头能被「隔跳」覆盖------直接暴力 check:用双数组奇偶交替模拟。
    更简单:若 X ≥ max(stonesi - stonesi-2) 即可行。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

代码实现

java 复制代码
public class Solution {
    // 解法一:隔一跳即答案(主提交版本)
    public int maxJump(int[] stones) {
        int n = stones.length;
        int ans = 0;
        // 去程:0,2,4,...;回程:...,3,1,0 → 每跳都是隔一块
        // 还需考虑最后一跳:无论去程到终点 n-1,回程从 n-2 起?终点 n-1 只到达一次。
        // 最小的最大跳 = 全局「隔一格」最大(含首尾特殊处理:去程 0->2 的最大 gap)
        // 标准解法:ans = max over i>=2 of (stones[i] - stones[i-2]),
        // 另考虑 n=2 时只有直接跳:ans = stones[1] - stones[0]。
        for (int i = 2; i < n; i++) {
            ans = Math.max(ans, stones[i] - stones[i - 2]);
        }
        // 首尾边界:从 0 跳到终点(n-1)与从 n-1 跳回 n-2(若只有两块则涵盖在 i=2 None)
        if (n == 2) {
            ans = Math.max(ans, stones[1] - stones[0]);
        } else {
            ans = Math.max(ans, stones[1] - stones[0]); // 回程最后一跳 1 -> 0
            ans = Math.max(ans, stones[n - 1] - stones[n - 2]); // 去程最后一段(若 n-1 偶)
        }
        return ans;
    }

    // 解法二:二分答案 + 可行性(对照)
    public int maxJumpBinary(int[] stones) {
        int n = stones.length;
        int lo = 0, hi = stones[n - 1];
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (feasible(stones, mid)) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }

    // 可行性:存在一条去程+回程路径每跳<=X,每块石头至多一次
    private boolean feasible(int[] stones, int x) {
        int n = stones.length;
        boolean[] used = new boolean[n]; // 模拟去程偶位置、回程奇位置
        // 去程:从 0 出发尽量跳到 <= x 的最远未用石头;由于要保证回程也有路,
        // 标准构造:去程走 0,2,4,...;回程 1,3,5,...
        int last = 0;
        used[0] = true;
        for (int i = 1; i < n; i++) {
            if (i >= 2 && stones[i] - stones[i - 2] > x) {
                return false;
            }
        }
        // 上述判断只覆盖隔跳;还需对去程/回程相邻跳(i 与 i-1 无跳跃,回程相邻差同):
        // 去程与回程相邻使用的两石之间最大为 stones[i]-stones[i-2];首尾特殊:
        if (n == 2) {
            return stones[1] - stones[0] <= x;
        }
        // 回程最后:1->0,差 stones[1]-stones[0]
        if (stones[1] - stones[0] > x) {
            return false;
        }
        // 去程第一跳 0->(最近的>=2 的石头) 由隔跳覆盖;去程到终点的跳由 i>=2 覆盖(n-1 与 n-3)
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.maxJump(new int[]{0, 2, 5, 6, 7}) == 5 : "2498 例1 主: " + sol.maxJump(new int[]{0, 2, 5, 6, 7});
        assert sol.maxJumpBinary(new int[]{0, 2, 5, 6, 7}) == 5 : "2498 例1 二分";
        assert sol.maxJump(new int[]{0, 3, 9}) == 9 : "2498 例2 主";
        assert sol.maxJumpBinary(new int[]{0, 3, 9}) == 9 : "2498 例2 二分";
        assert sol.maxJump(new int[]{0, 1}) == 1 : "2498 两块 主";
        assert sol.maxJumpBinary(new int[]{0, 1}) == 1 : "2498 两块 二分";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(61);
        for (int t = 0; t < 300; t++) {
            int n = 2 + rnd.nextInt(20);
            int[] stones = new int[n];
            stones[0] = 0;
            for (int i = 1; i < n; i++) {
                stones[i] = stones[i - 1] + 1 + rnd.nextInt(20);
            }
            int a = sol.maxJump(stones);
            int b = sol.maxJumpBinary(stones);
            if (a != b) {
                System.out.println("对拍失败 " + java.util.Arrays.toString(stones) + " 主=" + a + " 二分=" + b);
                System.exit(1);
            }
        }

        // 暴力 BFS 验证一个小例子的正确性(每块至多一次)
        assert bruteVerify(new int[]{0, 2, 5, 6, 7}) == 5 : "2498 例1 暴力验证";
        System.out.println("2498 全部 PASS");
    }

    // 暴力:全排列式 DFS 验证(仅小规模)
    private static int bruteVerify(int[] stones) {
        int n = stones.length;
        int[] best = {Integer.MAX_VALUE};
        boolean[] used = new boolean[n];
        used[0] = true;
        dfs(0, n - 1, 0, 0, stones, used, false, best);
        return best[0];
    }

    private static void dfs(int pos, int target, int curMax, int step, int[] stones,
                            boolean[] used, boolean returning, int[] best) {
        if (step == stones.length - 1) {
            // 必须回到 0
            if (pos == 0 && returning) {
                best[0] = Math.min(best[0], curMax);
                return;
            }
            if (pos == target && !returning) {
                // 终点 → 开始回程
                dfs(pos, 0, curMax, step + 1, stones, used, true, best);
            }
            return;
        }
        if (returning && pos == 0) {
            best[0] = Math.min(best[0], curMax);
            return;
        }
        for (int i = 0; i < stones.length; i++) {
            if (used[i]) {
                continue;
            }
            int jump = Math.abs(stones[pos] - stones[i]);
            if (returning) {
                if (i != 0 && i != target) { /* 一般位置可跳 */ }
            }
            used[i] = true;
            dfs(i, target, Math.max(curMax, jump), step + 1, stones, used, returning, best);
            used[i] = false;
        }
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2499. Minimum Total Cost to Make Arrays Unequal(让数组不相等的最小总代价)

题目描述

给你两个下标从 0 开始的整数数组 nums1 和 nums2,两者长度都为 n。

每次操作中,你可以选择交换 nums1 中任意两个下标处的值。操作的开销为两个下标的和。

你的目标是对于所有的 0 <= i <= n-1,都满足 nums1[i] != nums2[i],可以进行任意次操作,

返回达到这个目标的最小总代价。如果无法达成目标,返回 -1。

示例 1

复制代码
`nums1 = [1,2,3,4,5], nums2 = [1,2,3,4,5]` → 10

示例 2

复制代码
`nums1 = [2,2,2,1,3], nums2 = [1,2,2,3,3]` → 10

示例 3

复制代码
`nums1 = [1,2,2], nums2 = [1,2,2]` → -1

提示:

  • n == nums1.length == nums2.length
  • 1 <= n <= 10^5
  • 1 <= nums1[i], nums2[i] <= n

解题思路

解法一:冲突位分析 + 贪心凑对(主提交版本)

  • 冲突位置集合 bad(nums1i == nums2i)。每个冲突位 i 的值 nums1i 最终必须被换走,
    因此 i 至少要参与一次交换,代价贡献 ≥ i → 总代价下界 = sum(bad)。
  • 冲突位之间可以直接互换:两个冲突位 i、j(对应冲突值 v1 != v2)互换后双双变好,
    每次交换刚好覆盖两个冲突位 → 下界可达。
  • 但若某个冲突值 maxV 出现次数 c > m - c(m = |bad|),先让非 maxV 冲突位与 maxV 冲突位
    两两互配(最多 m-c 对),还剩 extra = 2c - m 个 maxV 冲突位找不到冲突位伙伴,
    只能与非冲突位 j 交换:要求交换后 j 不产生新冲突,即
    nums1[j] != maxV(j 换入 maxV 后不能等于自身新值?不------j 得到 maxV,要求 maxV != nums2j)
    且 nums2[j] != maxV(防止新冲突)。此类外部位每个解决一个冲突位,代价额外 + j。
  • 为最小化选最小下标的 extra 个外部位;不够则返回 -1。
    最终答案 = sum(bad) + sum(选中的外部位下标)。
    解法二:Dijkstra 置换图暴力(对照,仅小规模对拍)
  • 把「数组状态」作为节点、交换为边(代价 i+j),从初始状态跑最短路,
    在所有 nums1i != nums2i 的合法状态中取最小代价。
    复杂度:
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

代码实现

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

public class Solution {
    // 解法一:冲突位分析 + 贪心凑对(主提交版本)
    public long minimumTotalCost(int[] nums1, int[] nums2) {
        int n = nums1.length;
        long base = 0;
        int m = 0;
        int[] freqBad = new int[n + 1];
        List<Integer> badIdx = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (nums1[i] == nums2[i]) {
                int v = nums1[i];
                freqBad[v]++;
                badIdx.add(i);
                base += i;
                m++;
            }
        }
        if (m == 0) {
            return 0;
        }
        int maxV = 0;
        for (int v = 1; v <= n; v++) {
            if (freqBad[v] > freqBad[maxV]) {
                maxV = v;
            }
        }
        int c = freqBad[maxV];
        int extra = c - (m - c); // 超出内部可互配数量的 maxV 冲突位
        if (extra > 0) {
            // 收集外部(非冲突)位:nums1[j] != maxV 且 nums2[j] != maxV
            List<Integer> outIdx = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                if (nums1[j] != nums2[j] && nums1[j] != maxV && nums2[j] != maxV) {
                    outIdx.add(j);
                    if (outIdx.size() == extra) {
                        break;
                    }
                }
            }
            if (outIdx.size() < extra) {
                return -1;
            }
            for (int j : outIdx) {
                base += j;
            }
        }
        return base;
    }

    // 解法二:Dijkstra 置换图(对照,小规模对拍)
    public long minimumTotalCostDijkstra(int[] nums1, int[] nums2) {
        int n = nums1.length;
        long[] init = new long[n];
        for (int i = 0; i < n; i++) {
            init[i] = nums1[i];
        }
        Map<String, Long> dist = new HashMap<>();
        String start = encode(init);
        dist.put(start, 0L);
        java.util.PriorityQueue<long[]> pq = new java.util.PriorityQueue<>((a, b) -> Long.compare(a[1], b[1]));
        // 编码成本太高,改用 String -> long 代价,这里用简单状态: 直接塞入编码后的长整数
        // 为了简化,返回一个由递归深搜代替(n<=7 时状态数 ≤ 5040,可直接 BFS 全部枚举)。
        best4 = Long.MAX_VALUE;
        dfs(new long[n], 0, nums1, nums2, 0, new boolean[n]);
        return best4 == Long.MAX_VALUE ? -1 : best4;
    }

    private long best4 = Long.MAX_VALUE;

    // 深搜构造「当前排列 cur」------复杂度 n! 太大;改用 BFS 交换。这里用置换目标枚举 + 环代价计算。
    // 简化实现:直接用「枚举目标排列 + Dijkstra 距离」在 main 里用更小规模。
    private void dfs(long[] cur, int pos, int[] nums1, int[] nums2, long cost, boolean[] used) {
        // 未使用;实际对拍走 minimumTotalCostSearch
    }

    // 正确暴力:从初始数组 BFS 全部可达状态,取合法最小代价(n ≤ 6)
    public long minimumTotalCostSearch(int[] nums1, int[] nums2) {
        int n = nums1.length;
        Map<Long, Long> dist = new HashMap<>();
        long start = encodeLong(nums1);
        dist.put(start, 0L);
        java.util.PriorityQueue<long[]> pq = new java.util.PriorityQueue<>((a, b) -> Long.compare(a[1], b[1]));
        pq.offer(new long[]{start, 0});
        long best = Long.MAX_VALUE;
        while (!pq.isEmpty()) {
            long[] top = pq.poll();
            long state = top[0];
            long d = top[1];
            if (d > dist.getOrDefault(state, Long.MAX_VALUE)) {
                continue;
            }
            int[] arr = decodeLong(state, n);
            boolean valid = true;
            for (int i = 0; i < n; i++) {
                if (arr[i] == nums2[i]) {
                    valid = false;
                    break;
                }
            }
            if (valid) {
                best = Math.min(best, d);
                // 不能提前终止:还要继续探索更优路径(Dijkstra 首弹即最优,但合法状态未必首弹)
                // 继续即可
            }
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    int[] nxt = arr.clone();
                    int tmp = nxt[i];
                    nxt[i] = nxt[j];
                    nxt[j] = tmp;
                    long ns = encodeLong(nxt);
                    long nd = d + i + j;
                    if (nd < dist.getOrDefault(ns, Long.MAX_VALUE)) {
                        dist.put(ns, nd);
                        pq.offer(new long[]{ns, nd});
                    }
                }
            }
        }
        return best == Long.MAX_VALUE ? -1 : best;
    }

    private String encode(long[] a) {
        StringBuilder sb = new StringBuilder();
        for (long x : a) {
            sb.append(x).append(',');
        }
        return sb.toString();
    }

    private long encodeLong(int[] a) {
        long key = 0;
        for (int i = 0; i < a.length; i++) {
            key = key * 37 + a[i]; // 值 ≤ n ≤ 6,37 进制足够
        }
        return key;
    }

    private int[] decodeLong(long key, int n) {
        int[] a = new int[n];
        for (int i = n - 1; i >= 0; i--) {
            a[i] = (int) (key % 37);
            key /= 37;
        }
        return a;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        // 官方示例
        assert sol.minimumTotalCost(new int[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3, 4, 5}) == 10 : "2499 例1 主: " + sol.minimumTotalCost(new int[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3, 4, 5});
        assert sol.minimumTotalCost(new int[]{2, 2, 2, 1, 3}, new int[]{1, 2, 2, 3, 3}) == 10 : "2499 例2 主: " + sol.minimumTotalCost(new int[]{2, 2, 2, 1, 3}, new int[]{1, 2, 2, 3, 3});
        assert sol.minimumTotalCost(new int[]{1, 2, 2}, new int[]{1, 2, 2}) == -1 : "2499 例3 主: " + sol.minimumTotalCost(new int[]{1, 2, 2}, new int[]{1, 2, 2});
        assert sol.minimumTotalCost(new int[]{1, 2, 3}, new int[]{2, 3, 1}) == 0 : "2499 无冲突主";

        // 小规模随机对拍(n ≤ 6)
        java.util.Random rnd = new java.util.Random(67);
        for (int t = 0; t < 400; t++) {
            int n = 1 + rnd.nextInt(6);
            int[] a = new int[n];
            int[] b = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = 1 + rnd.nextInt(Math.max(1, n)); // 值域小 → 冲突概率高
                b[i] = 1 + rnd.nextInt(Math.max(1, n));
            }
            long x = sol.minimumTotalCost(a, b);
            long y = sol.minimumTotalCostSearch(a, b);
            if (x != y) {
                System.out.println("对拍失败 nums1=" + java.util.Arrays.toString(a)
                        + " nums2=" + java.util.Arrays.toString(b) + " 主=" + x + " 搜索=" + y);
                System.exit(1);
            }
        }

        // 特别验证官方可能反例:大量相同冲突值
        assert sol.minimumTotalCost(new int[]{1, 1, 1}, new int[]{1, 1, 1}) == -1 : "2499 全同主";
        assert sol.minimumTotalCost(new int[]{1, 1, 2, 2}, new int[]{1, 1, 2, 2}) == 2 : "2499 双对主: " + sol.minimumTotalCost(new int[]{1, 1, 2, 2}, new int[]{1, 1, 2, 2});
        System.out.println("2499 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

2500. Delete Greatest Value in Each Row(删除每行中的最大值)

题目描述

给你一个 m x n 大小的矩阵 grid,由若干正整数组成。

执行下述操作,直到 grid 变为空矩阵:

  • 从每一行删除值最大的元素。如果存在多个这样的值,删除其中任何一个。
  • 将删除元素中的最大值与答案相加。
    注意每执行一次操作,矩阵中列的数据就会减 1。
    返回执行上述操作后的答案。

示例 1

复制代码
`grid = [[1,2,4],[3,3,1]]` → 8(4+3+1)

示例 2

复制代码
`grid = [[10]]` → 10

提示:

  • m == grid.length,n == grid[i].length
  • 1 <= m, n <= 50
  • 1 <= grid[i][j] <= 100

解题思路

解法一:逐行排序 + 列最大值累加(主提交版本)

  • 每轮删除「每行当前最大值」等价于把每行降序排列后取第 t 列的值(t 从 0 到 n-1),
    累加该列的最大值。
  • 实现:每行升序排序,对每一列 j 求所有行 gridij 的最大值并累加。
    解法二:直接模拟删除(对照)
  • 实际循环 n 轮,每轮从每行找出未删的最大值(标记删除),累加本轮最大值。
    复杂度:
  • 时间复杂度:O(m * n log n)
  • 空间复杂度:O(1)

代码实现

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

public class Solution {
    // 解法一:逐行排序 + 列最大值累加(主提交版本)
    public int deleteGreatestValue(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        for (int i = 0; i < m; i++) {
            Arrays.sort(grid[i]); // 升序,第 t 列即第 t 轮删除值
        }
        int ans = 0;
        for (int j = 0; j < n; j++) {
            int colMax = 0;
            for (int i = 0; i < m; i++) {
                colMax = Math.max(colMax, grid[i][j]);
            }
            ans += colMax;
        }
        return ans;
    }

    // 解法二:直接模拟(对照)
    public int deleteGreatestValueSim(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        boolean[][] removed = new boolean[m][n];
        int ans = 0;
        for (int round = 0; round < n; round++) {
            int roundMax = 0;
            for (int i = 0; i < m; i++) {
                int bestVal = -1, bestJ = -1;
                for (int j = 0; j < n; j++) {
                    if (!removed[i][j] && grid[i][j] > bestVal) {
                        bestVal = grid[i][j];
                        bestJ = j;
                    }
                }
                removed[i][bestJ] = true;
                roundMax = Math.max(roundMax, bestVal);
            }
            ans += roundMax;
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();

        assert sol.deleteGreatestValue(new int[][]{{1, 2, 4}, {3, 3, 1}}) == 8 : "2500 例1 主";
        assert sol.deleteGreatestValueSim(new int[][]{{1, 2, 4}, {3, 3, 1}}) == 8 : "2500 例1 模拟";
        assert sol.deleteGreatestValue(new int[][]{{10}}) == 10 : "2500 例2 主";
        assert sol.deleteGreatestValueSim(new int[][]{{10}}) == 10 : "2500 例2 模拟";

        // 随机对拍
        java.util.Random rnd = new java.util.Random(71);
        for (int t = 0; t < 300; t++) {
            int m = 1 + rnd.nextInt(8);
            int n = 1 + rnd.nextInt(8);
            int[][] g = new int[m][n];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    g[i][j] = 1 + rnd.nextInt(50);
                }
            }
            int a = sol.deleteGreatestValue(g);
            int b = sol.deleteGreatestValueSim(g);
            if (a != b) {
                System.out.println("对拍失败 " + Arrays.deepToString(g) + " 主=" + a + " 模拟=" + b);
                System.exit(1);
            }
        }
        System.out.println("2500 全部 PASS");
    }
}

复杂度分析

  • 时间复杂度:O(m * n log n)
  • 空间复杂度:O(1)
相关推荐
Flynn Liang1 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
聪明的笨猪猪2 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
愿有所息2 小时前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
秋饼2 小时前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
SL_staff4 小时前
JVS-BI 实战:业务人员如何用零代码 ELT + 可视化配置构建实时经营分析闭环
java·spring·全栈
SL_staff4 小时前
企业AI落地难?JVS-AI渐进式实践:安全可控、小切口验证、标准化沉淀与规模化复制
java·开源·aigc
SL_staff5 小时前
JVS数字底座实践:构建企业级统一内容管理平台的技术路径
java·产品
nice_lcj5206 小时前
【从零理解 Spring AI · 12】MCP 的两种角色:做客户端还是做服务端
java·后端·spring·spring ai·ai应用开发
桦说编程6 小时前
TransmittableThreadLocal 解读:从 CRR 机制到上下文所有权
java·后端·源码