LeetCode2541~2560题目解析

LeetCode 2541-2560. 题解合集

文章目录


2541. Minimum Operations to Make Array Equal II(使数组中所有元素相等的最小操作数 II)

题目描述

给你两个长度为 n 的整数数组 nums1 和 nums2,和一个整数 k。

你可以对 nums1 进行操作:选择两个下标 i 和 j,将 nums1[i] 增加 k,将 nums1[j] 减少 k。

如果所有 i 都有 nums1[i] == nums2[i],则称 nums1 等于 nums2。

返回使 nums1 等于 nums2 的最少操作数;如果没办法,返回 -1。

示例 1

复制代码
nums1 = [4,3,1,4], nums2 = [1,3,7,1], k = 3 -> 2

示例 2

复制代码
nums1 = [3,8,5,2], nums2 = [2,4,1,6], k = 1 -> -1

提示:

  • n == nums1.length == nums2.length,2 <= n <= 10^5,
  • 0 <= nums1i, nums2j <= 10^9,0 <= k <= 10^5

解题思路

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

每对下标最终要求相等。一次操作在一个下标 +k 另一个下标 -k,

即所有下标的总变化量为 0 → 必须有 Σ(nums1i-nums2i) == 0 且可被 k 整除。

对每个 i,若 |nums1i-nums2i| 不是 k 的倍数则无解。

需要增加量 sum(positive diffs) 就是总操作次数:

每次操作把一个需要 +k 的下标和一个需要 -k 的下标配对,共

ans = Σ max(0, nums2i-nums1i) / k。

解法二:模拟暴力,对照

直接对需要的差值做配对计数(同公式),此对照改用逐项模拟

小数据下验证。

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

代码实现

java 复制代码
public class Solution {

    public long minOperations(int[] nums1, int[] nums2, int k) {
        int n = nums1.length;
        if (k == 0) {
            // k=0 时只能做零变化,直接比较两个数组
            for (int i = 0; i < n; i++) {
                if (nums1[i] != nums2[i]) return -1;
            }
            return 0;
        }
        long pos = 0, neg = 0;
        for (int i = 0; i < n; i++) {
            long diff = (long) nums2[i] - nums1[i];
            if (diff % k != 0) return -1;
            if (diff > 0) pos += diff;
            else neg += -diff;
        }
        if (pos != neg) return -1;
        return pos / k;
    }

    public long minOperationsSim(int[] nums1, int[] nums2, int k) {
        int n = nums1.length;
        if (k == 0) {
            for (int i = 0; i < n; i++) if (nums1[i] != nums2[i]) return -1;
            return 0;
        }
        long[] need = new long[n]; // 正:需要增加,负:需要减少
        long total = 0;
        for (int i = 0; i < n; i++) {
            need[i] = (long) nums2[i] - nums1[i];
            if (need[i] % k != 0) return -1;
            total += need[i];
        }
        if (total != 0) return -1;
        long ops = 0;
        int i = 0, j = 0;
        while (i < n && j < n) {
            while (i < n && need[i] <= 0) i++;
            while (j < n && need[j] >= 0) j++;
            if (i >= n || j >= n) break;
            long pair = Math.min(need[i], -need[j]); // 一次操作 i 增 k、j 减 k
            ops += pair / k;
            need[i] -= pair;
            need[j] += pair;
        }
        return ops;
    }

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

        // 示例测试
        System.out.println(s.minOperations(new int[]{4, 3, 1, 4}, new int[]{1, 3, 7, 1}, 3) == 2 ? "PASS" : "FAIL");
        System.out.println(s.minOperations(new int[]{3, 8, 5, 2}, new int[]{2, 4, 1, 6}, 1) == -1 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(12);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 2 + rnd.nextInt(8);
            int k = rnd.nextInt(4);
            int[] a = new int[n];
            int[] b = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = rnd.nextInt(12);
                b[i] = a[i] + (rnd.nextBoolean() ? 1 : -1) * k * rnd.nextInt(4);
                if (b[i] < 0) b[i] = a[i];
            }
            long r1 = s.minOperations(a, b, k);
            long r2 = s.minOperationsSim(a, b, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch k=" + k + " a=" + java.util.Arrays.toString(a)
                        + " b=" + java.util.Arrays.toString(b) + " main=" + r1 + " sim=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2541 全部 PASS" : "2541 存在不一致");
    }
}

复杂度分析

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


2542. Maximum Subsequence Score(最大子序列的分数)

题目描述

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

从 nums1 中选一个长度为 k 的子序列对应的下标 i0, i1, ..., i(k-1),分数定义为:

(nums1i0 + nums1i1 + ... + nums1`i`(`k`-1)) * min(nums2i0, ..., nums2`i`(`k`-1))。

返回最大可能的分数。

示例 1

复制代码
nums1 = [1,3,3,2], nums2 = [2,1,3,4], k = 3 -> 12

示例 2

复制代码
nums1 = [4,2,3,1,1], nums2 = [7,5,10,9,6], k = 1 -> 30

提示:

  • n == nums1.length == nums2.length,1 <= n <= 10^5,
  • 0 <= nums1i, nums2j <= 10^5,1 <= k <= n

解题思路

解法一:排序 + 最小堆贪心(主提交版本)

将元素按 nums2 降序排列。枚举「最小值」为当前元素的 nums2 值,

在它之前的元素中选 nums1 最大的 k 个(维护一个大小为 k 的最小堆,堆内元素

的 nums1 之和最大),这 k 个下标与当前元素一起组成方案:

分数 = (sum + nums1cur) * nums2cur。

逐个枚举取最大即可,注意当前元素必须选入(它的 nums2 最小)。

解法二:暴力枚举子序列,对照

小数据下枚举所有长度为 k 的下标组合求最大值。

时间复杂度:O(n log n + n log k);空间复杂度:O(n)。

代码实现

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

public class Solution {

    public long maxScore(int[] nums1, int[] nums2, int k) {
        int n = nums1.length;
        Integer[] idx = new Integer[n];
        for (int i = 0; i < n; i++) idx[i] = i;
        Arrays.sort(idx, (a, b) -> nums2[b] - nums2[a]); // nums2 降序
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>(); // 最小堆存 nums1 值
        long sum = 0;
        long best = 0;
        for (int i = 0; i < n; i++) {
            int id = idx[i];
            pq.offer(nums1[id]);
            sum += nums1[id];
            if (pq.size() > k) {
                sum -= pq.poll();
            }
            if (pq.size() == k) {
                best = Math.max(best, sum * (long) nums2[id]);
            }
        }
        return best;
    }

    public long maxScoreBrute(int[] nums1, int[] nums2, int k) {
        int n = nums1.length;
        long[] best = {0};
        dfs(0, 0, Long.MAX_VALUE, 0, nums1, nums2, k, best);
        return best[0];
    }

    private void dfs(int pos, int cnt, long minN2, long sum, int[] n1, int[] n2, int k, long[] best) {
        if (cnt == k) {
            best[0] = Math.max(best[0], sum * minN2);
            return;
        }
        if (pos == n1.length) return;
        // 选
        dfs(pos + 1, cnt + 1, Math.min(minN2, n2[pos]), sum + n1[pos], n1, n2, k, best);
        // 不选
        dfs(pos + 1, cnt, minN2, sum, n1, n2, k, best);
    }

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

        // 示例测试
        System.out.println(s.maxScore(new int[]{1, 3, 3, 2}, new int[]{2, 1, 3, 4}, 3) == 12 ? "PASS" : "FAIL");
        System.out.println(s.maxScore(new int[]{4, 2, 3, 1, 1}, new int[]{7, 5, 10, 9, 6}, 1) == 30 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(13);
        boolean ok = true;
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(8);
            int k = 1 + rnd.nextInt(n);
            int[] a = new int[n];
            int[] b = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = rnd.nextInt(20);
                b[i] = rnd.nextInt(20);
            }
            long r1 = s.maxScore(a, b, k);
            long r2 = s.maxScoreBrute(a, b, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch n1=" + Arrays.toString(a) + " n2=" + Arrays.toString(b)
                        + " k=" + k + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2542 全部 PASS" : "2542 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n log n + n log k);空间复杂度:O(n)。


2543. Check if Point Is Reachable(判断一个点是否可以到达)

题目描述

在无穷大网格图中,从 (1,1) 出发,每一步可以从 (x,y) 移动到:

(x, y-x)、(x-y, y)、(2*x, y)、(x, 2*y)。

给定目标 (targetX, targetY),若可达返回 true,否则 false。

示例 1

复制代码
targetX = 6, targetY = 9 -> false

示例 2

复制代码
targetX = 4, targetY = 7 -> true

提示:

  • 1 <= targetX, targetY <= 10^9

解题思路

解法一:逆向思考 + gcd(主提交版本)

反过来看,(x,y) 的前驱只有四类:由于每次都只改变一个坐标(减或乘 2),

把 (x,y) 除以 2:(x/2, y) 或 (x, y/2) 当对应坐标是偶数且 >0;

或者做加法:x + y 得到 (x+y, y) 或 (x, x+y) 的前驱。

关键观察:gcd(x, y) 在四类操作下都不变(减是 gcd 不变;乘 2 时若另一坐标是奇数,

则 2 的因子无法进入 gcd)。从 (1,1) 出发 gcd 总是 1 或含 2 的因子?

更准确:gcd(x,y) 的所有奇数质因子保持 (1,1) 的 gcd=1 不变。

反向从 (targetX, targetY) 出发:能除 2 就除 2(把 2 因子消掉),

之后若 x != y,用 gcd(x,y) 验证:如果 gcd 的奇数部分大于 1 则不可达;

实际上反向操作是:g = gcd(x,y),若 g 包含奇数因子 >1 则永远无法化为 (1,1)。

因为 (1,1) 的 gcd=1,而奇因子在四类操作下都不可能产生。

因此判断:令 g = gcd(targetX, targetY),只需验证 g 中是否只含 2 的幂(即 g 的奇数部分为 1)。

若 g 是 2 的幂(含 g=1),则可达;否则不可达。

证明略(参考官方题解)。

解法二:BFS 小范围暴力,对照

从 (1,1) BFS 到有限步数,检查能否到达小目标点。

时间复杂度:O(log max(targetX, targetY));空间复杂度:O(1)。

代码实现

java 复制代码
public class Solution {

    public boolean isReachable(int targetX, int targetY) {
        int g = gcd(targetX, targetY);
        // 若 g 中奇数部分为 1 即 g 是 2 的幂
        while (g % 2 == 0) g /= 2;
        return g == 1;
    }

    private int gcd(int a, int b) {
        while (b != 0) {
            int t = a % b;
            a = b;
            b = t;
        }
        return a;
    }

    public boolean isReachableBfs(int targetX, int targetY) {
        if (targetX > 60 || targetY > 60) return false; // 只对拍小数据
        java.util.Set<Long> seen = new java.util.HashSet<Long>();
        java.util.ArrayDeque<int[]> q = new java.util.ArrayDeque<int[]>();
        q.offer(new int[]{1, 1});
        seen.add(((long) 1 << 32) | 1);
        while (!q.isEmpty()) {
            int[] cur = q.poll();
            int x = cur[0], y = cur[1];
            if (x == targetX && y == targetY) return true;
            int[][] nxt = {{x, y - x}, {x - y, y}, {2 * x, y}, {x, 2 * y}};
            for (int[] nx : nxt) {
                if (nx[0] < 1 || nx[1] < 1) continue;
                if (nx[0] > 80 || nx[1] > 80) continue;
                long key = ((long) nx[0] << 32) | nx[1];
                if (seen.add(key)) q.offer(nx);
            }
        }
        return false;
    }

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

        // 示例测试
        System.out.println(!s.isReachable(6, 9) ? "PASS" : "FAIL");
        System.out.println(s.isReachable(4, 7) ? "PASS" : "FAIL");

        // 小范围随机对拍(BFS 限界 80 以内)
        java.util.Random rnd = new java.util.Random(14);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int x = 1 + rnd.nextInt(30);
            int y = 1 + rnd.nextInt(30);
            boolean r1 = s.isReachable(x, y);
            boolean r2 = s.isReachableBfs(x, y);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch x=" + x + " y=" + y + " main=" + r1 + " bfs=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2543 全部 PASS" : "2543 存在不一致");
    }
}

复杂度分析

时间复杂度:O(log max(targetX, targetY));空间复杂度:O(1)。


2544. Alternating Digit Sum(交替数字和)

题目描述

给你一个正整数 n。n 中的每一位数字都会按下述规则分配一个符号:

最高有效位上的数字分配到正号;剩余每位上数字的符号都与其相邻数字相反。

返回所有数字及其对应符号的和。

示例 1

复制代码
n = 521 -> (+5)+(-2)+(+1) = 4

示例 2

复制代码
n = 111 -> 1

示例 3

复制代码
n = 886996 -> 0

提示:

  • 1 <= n <= 10^9

解题思路

解法一:模拟(主提交版本)

将 n 转成字符串,从最高位开始,符号交替 + - + - ...,累加每位数字与符号的乘积。

解法二:数值拆位,对照

先算出位数,再从最高位逐位取出累加,符号同上。

时间复杂度:O(log n);空间复杂度:O(1)。

代码实现

java 复制代码
public class Solution {

    public int alternateDigitSum(int n) {
        String s = String.valueOf(n);
        int ans = 0;
        int sign = 1;
        for (int i = 0; i < s.length(); i++) {
            ans += sign * (s.charAt(i) - '0');
            sign = -sign;
        }
        return ans;
    }

    public int alternateDigitSumNum(int n) {
        int len = String.valueOf(n).length();
        int ans = 0;
        int v = n;
        // 从低位向高位取数位:第 i 次取出的是原始数第 i 位(从低位编号 0),
        // 其从高位编号为 len-1-i,符号为 (-1)^(len-1-i)
        for (int i = 0; i < len; i++) {
            int d = v % 10;
            ans += ((len - 1 - i) % 2 == 0) ? d : -d;
            v /= 10;
        }
        return ans;
    }

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

        // 示例测试
        System.out.println(s.alternateDigitSum(521) == 4 ? "PASS" : "FAIL");
        System.out.println(s.alternateDigitSum(111) == 1 ? "PASS" : "FAIL");
        System.out.println(s.alternateDigitSum(886996) == 0 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(15);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 1 + rnd.nextInt(1000000000);
            if (s.alternateDigitSum(n) != s.alternateDigitSumNum(n)) {
                ok = false;
                System.out.println("Mismatch n=" + n);
                break;
            }
        }
        System.out.println(ok ? "2544 全部 PASS" : "2544 存在不一致");
    }
}

复杂度分析

时间复杂度:O(log n);空间复杂度:O(1)。


2545. Sort the Students by Their Kth Score(根据第 K 场考试的分数排序)

题目描述

班里有 m 位学生,计划组织 n 场考试。给你 m x n 的整数矩阵 score,每行对应一位学生,

score[i][j] 表示第 i 位学生在第 j 场考试的分数,矩阵内整数互不相同。

给定整数 k,按第 k 场考试分数从高到低对学生(行)排序,返回排序后的矩阵。

示例 1

复制代码
score = [[10,6,9,1],[7,5,11,2],[4,8,3,15]], k = 2 -> [[7,5,11,2],[10,6,9,1],[4,8,3,15]]

示例 2

复制代码
score = [[3,4],[5,6]], k = 0 -> [[5,6],[3,4]]

提示:

  • m == score.length,n == scorei.length,1 <= m, n <= 250,
  • 1 <= score[i][j] <= 10^5,分数互不相同,0 <= k < n

解题思路

解法一:行排序(主提交版本)

用 Integer\[\] 保存行下标,按第 k 列分数降序排序,再按新顺序构造结果矩阵。

解法二:交换排序,对照

朴素冒泡/选择排序,按第 k 列交换整行,验证结果一致。

时间复杂度:O(m log m * n);空间复杂度:O(m)。

代码实现

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

public class Solution {

    public int[][] sortTheStudents(int[][] score, int k) {
        int m = score.length;
        Integer[] rowIdx = new Integer[m];
        for (int i = 0; i < m; i++) rowIdx[i] = i;
        Arrays.sort(rowIdx, (a, b) -> score[b][k] - score[a][k]);
        int[][] res = new int[m][];
        for (int i = 0; i < m; i++) {
            res[i] = score[rowIdx[i]];
        }
        return res;
    }

    public int[][] sortTheStudentsSel(int[][] score, int k) {
        int m = score.length;
        int[][] a = new int[m][];
        for (int i = 0; i < m; i++) a[i] = score[i].clone();
        for (int i = 0; i < m; i++) {
            int best = i;
            for (int j = i + 1; j < m; j++) {
                if (a[j][k] > a[best][k]) best = j;
            }
            int[] tmp = a[i];
            a[i] = a[best];
            a[best] = tmp;
        }
        return a;
    }

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

        // 示例测试
        int[][] r1 = s.sortTheStudents(new int[][]{{10, 6, 9, 1}, {7, 5, 11, 2}, {4, 8, 3, 15}}, 2);
        System.out.println(java.util.Arrays.deepEquals(r1, new int[][]{{7, 5, 11, 2}, {10, 6, 9, 1}, {4, 8, 3, 15}}) ? "PASS" : "FAIL");
        int[][] r2 = s.sortTheStudents(new int[][]{{3, 4}, {5, 6}}, 0);
        System.out.println(java.util.Arrays.deepEquals(r2, new int[][]{{5, 6}, {3, 4}}) ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(16);
        boolean ok = true;
        for (int t = 0; t < 1000; t++) {
            int m = 1 + rnd.nextInt(8);
            int n = 1 + rnd.nextInt(6);
            int[][] a = new int[m][n];
            int[] vals = new int[m * n];
            for (int i = 0; i < m * n; i++) vals[i] = i + 1;
            // 打乱
            for (int i = m * n - 1; i > 0; i--) {
                int j = rnd.nextInt(i + 1);
                int tmp = vals[i];
                vals[i] = vals[j];
                vals[j] = tmp;
            }
            int p = 0;
            for (int i = 0; i < m; i++)
                for (int j = 0; j < n; j++) a[i][j] = vals[p++];
            int k = rnd.nextInt(n);
            int[][] rA = s.sortTheStudents(a, k);
            int[][] rB = s.sortTheStudentsSel(a, k);
            if (!java.util.Arrays.deepEquals(rA, rB)) {
                ok = false;
                System.out.println("Mismatch k=" + k + " a=" + java.util.Arrays.deepToString(a));
                break;
            }
        }
        System.out.println(ok ? "2545 全部 PASS" : "2545 存在不一致");
    }
}

复杂度分析

时间复杂度:O(m log m * n);空间复杂度:O(m)。


2546. Apply Bitwise Operations to Make Strings Equal(执行逐位运算使字符串相等)

题目描述

给你两个下标从 0 开始的二元字符串 s 和 target,长度均为 n。

可以对 s 执行任意次操作:选择两个不同下标 i 和 j,同时

将 s[i] 替换为 (s[i] OR s[j]),s[j] 替换为 (s[i] XOR s[j])。

如果可以使 s 等于 target,返回 true,否则 false。

示例 1

复制代码
s = "1010", target = "0110" -> true

示例 2

复制代码
s = "11", target = "00" -> false

提示:

  • n == s.length == target.length,2 <= n <= 10^5,字符串仅由 0、1 组成

解题思路

解法一:分类讨论(主提交版本)

观察操作 (a, b) -> (a OR b, a XOR b):

00 -> 00

01 / 10 -> 11

11 -> 10(注意这里 a OR b = 1,a XOR b = 0)

分析可变形:只要 s 中至少有一个 1,就可以通过若干操作把 1 传播/搬移到任意位置;

若 s 全 0,则永远无法产生 1。

结论:

  • 若 s == target 直接 true;
  • 若 s 与 target 都含 1(s 含 1 才能产生/保留 1,target 含 1 才需要产生 1):true;
  • 若 s 全 0 而 target 含 1:false;
  • 若 target 全 0 而 s 含 1:false(因为 11->10 后虽可把 1 消成 0,
    但 1 无法凭空消失成全 0?验证:11 -> 10,存在 1;01 与 11:
    选 (0,1): (1, 1) -> 全 1;无法把最后一个 1 变成 0 同时没有 1 产生。
    实际上 11 -> 10 仍保留一个 1,再操作只会产生更多或保持 1。
    所以 s 含 1 且 target 全 0 时不可达:false。
  • 若 s 全 0 且 target 全 0:true(本来相等)。
    等价判定:count1(s) > 0 && count1(target) > 0 时为 true,
    否则 s.equals(target) 为 true。
    解法二:BFS 暴力,对照
    对短字符串状态空间做 BFS 枚举所有可达状态。
    时间复杂度:O(n);空间复杂度:O(1)。

代码实现

java 复制代码
public class Solution {

    private static final java.util.Set<String> VISITED = new java.util.HashSet<String>();

    public boolean makeStringsEqual(String s, String target) {
        if (s.equals(target)) return true;
        boolean sHas1 = s.indexOf('1') >= 0;
        boolean tHas1 = target.indexOf('1') >= 0;
        return sHas1 && tHas1;
    }

    public boolean makeStringsEqualBfs(String s, String target) {
        if (s.length() > 12) return false;
        java.util.ArrayDeque<String> q = new java.util.ArrayDeque<String>();
        q.offer(s);
        VISITED.clear();
        VISITED.add(s);
        while (!q.isEmpty()) {
            String cur = q.poll();
            if (cur.equals(target)) return true;
            char[] cs = cur.toCharArray();
            int n = cs.length;
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (i == j) continue;
                    int a = cs[i] - '0';
                    int b = cs[j] - '0';
                    int na = a | b;
                    int nb = a ^ b;
                    char[] nc = cs.clone();
                    nc[i] = (char) ('0' + na);
                    nc[j] = (char) ('0' + nb);
                    String ns = new String(nc);
                    if (VISITED.add(ns)) q.offer(ns);
                }
            }
        }
        return false;
    }

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

        // 示例测试
        System.out.println(s.makeStringsEqual("1010", "0110") ? "PASS" : "FAIL");
        System.out.println(!s.makeStringsEqual("11", "00") ? "PASS" : "FAIL");

        // 小范围 BFS 对拍
        java.util.Random rnd = new java.util.Random(17);
        boolean ok = true;
        for (int t = 0; t < 300; t++) {
            int n = 2 + rnd.nextInt(8);
            StringBuilder a = new StringBuilder();
            StringBuilder b = new StringBuilder();
            for (int i = 0; i < n; i++) {
                a.append(rnd.nextInt(2));
                b.append(rnd.nextInt(2));
            }
            String sa = a.toString();
            String sb = b.toString();
            boolean r1 = s.makeStringsEqual(sa, sb);
            boolean r2 = s.makeStringsEqualBfs(sa, sb);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch s=" + sa + " target=" + sb + " main=" + r1 + " bfs=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2546 全部 PASS" : "2546 存在不一致");
    }
}

复杂度分析

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


2547. Minimum Cost to Split an Array(拆分数组的最小代价)

题目描述

将数组拆分成一些非空子数组。拆分代价是每个子数组中的「重要性」之和。

trimmed(subarray) 移除所有仅出现一次的数字;子数组重要性 = k + trimmed(subarray).length。

返回拆分 nums 的所有可行方案中的最小代价。

示例 1

复制代码
nums = [1,2,1,2,1,3,3], k = 2 -> 8

示例 2

复制代码
nums = [1,2,1,2,1], k = 2 -> 6

示例 3

复制代码
nums = [1,2,1,2,1], k = 5 -> 10

提示:

  • 1 <= nums.length <= 1000;0 <= nums[i] < nums.length;1 <= k <= 10^9

解题思路

解法一:区间 DP + 前缀优化(主提交版本)

定义 extralr = 子数组 numsl...r 中「出现次数 >= 2 的数字」的个数

(即 trimmed 后保留的个数)。重要性 = k + extralr。

DP:dpi = 拆分前 i 个元素的最小代价。

dpi = min over j < i ( dpj + k + extraj+1i )。

直接计算 extra 需要 O(n^2) 预处理:对每个左端 l,向右扫维护 freq,遇到第二次出现

时 extra 计数 +1(出现第二次及以上均计入)。

总复杂度 O(n^2)。

解法二:暴力递归,对照

记忆化 DFS 枚举所有切分点,同样只在小数据上运行。

时间复杂度:O(n2);空间复杂度:O(n2)。

代码实现

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

public class Solution {

    public long minCost(int[] nums, int k) {
        int n = nums.length;
        // extra[l][r]: 子数组 nums[l..r] 的 trimmed 长度 =
        //   Σ(出现次数 >= 2 的数字,每个出现一次都计入)
        // 维护方式:元素首次出现时贡献 0;第二次出现时该数字变为重复,贡献 2;
        // 之后再出现一次贡献 +1。
        int[][] extra = new int[n][n];
        for (int l = 0; l < n; l++) {
            int[] freq = new int[n];
            int rem = 0;
            for (int r = l; r < n; r++) {
                int v = nums[r];
                if (freq[v] == 1) rem += 2;
                else if (freq[v] >= 2) rem += 1;
                freq[v]++;
                extra[l][r] = rem;
            }
        }
        long[] dp = new long[n + 1];
        java.util.Arrays.fill(dp, Long.MAX_VALUE / 4);
        dp[0] = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                long cost = dp[j] + k + extra[j][i - 1];
                if (cost < dp[i]) dp[i] = cost;
            }
        }
        return dp[n];
    }

    public long minCostDfs(int[] nums, int k) {
        int n = nums.length;
        long[][] memo = new long[n][n];
        for (int i = 0; i < n; i++) java.util.Arrays.fill(memo[i], -1);
        int[][] extra = new int[n][n];
        for (int l = 0; l < n; l++) {
            int[] freq = new int[n];
            int rem = 0;
            for (int r = l; r < n; r++) {
                int v = nums[r];
                if (freq[v] == 1) rem += 2;
                else if (freq[v] >= 2) rem += 1;
                freq[v]++;
                extra[l][r] = rem;
            }
        }
        return dfs(0, n, k, extra, memo);
    }

    private long dfs(int pos, int n, int k, int[][] extra, long[][] memo) {
        if (pos == n) return 0;
        if (memo[pos][n - 1] != -1) return memo[pos][n - 1];
        long best = Long.MAX_VALUE / 4;
        for (int r = pos; r < n; r++) {
            best = Math.min(best, k + extra[pos][r] + dfs(r + 1, n, k, extra, memo));
        }
        memo[pos][n - 1] = best;
        return best;
    }

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

        // 示例测试
        System.out.println(s.minCost(new int[]{1, 2, 1, 2, 1, 3, 3}, 2) == 8 ? "PASS" : "FAIL");
        System.out.println(s.minCost(new int[]{1, 2, 1, 2, 1}, 2) == 6 ? "PASS" : "FAIL");
        System.out.println(s.minCost(new int[]{1, 2, 1, 2, 1}, 5) == 10 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(18);
        boolean ok = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(10);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = rnd.nextInt(n);
            int k = 1 + rnd.nextInt(6);
            long r1 = s.minCost(a, k);
            long r2 = s.minCostDfs(a, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch k=" + k + " a=" + java.util.Arrays.toString(a)
                        + " main=" + r1 + " dfs=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2547 全部 PASS" : "2547 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n2);空间复杂度:O(n2)。


2548. Maximum Price to Fill a Bag(填满背包的最大价格)

题目描述

给定二维整数数组 items,其中 items[i] = `price_i`, `weight_i` 表示第 i 个物品的价格和重量。

物品可以按任意比率 part1:part2 拆分(part1 + part2 = 1),拆分后重量与价格同比例缩放。

使用给定的物品,填满容量为 capacity 的背包,返回最大总价格;若无法填满,返回 -1。

示例 1

复制代码
items = [[50,1],[10,8]], capacity = 5 -> 55.0

示例 2

复制代码
items = [[100,30]], capacity = 50 -> -1.0

提示:

  • 1 <= items.length <= 10^5;itemsi.length == 2;
  • 1 <= price_i, weight_i <= 10^4;1 <= capacity <= 10^9

解题思路

解法一:贪心 + 单位重量价值排序(主提交版本)

这是分数背包问题:按单价 price/weight 降序取物品,尽量装满 capacity。

若所有物品总重量 < capacity,返回 -1。

解法二:朴素的逐件挑选模拟,对照

每次选择当前单价最高的物品装入,逻辑等价,

小数据交叉验证。

时间复杂度:O(n log n);空间复杂度:O(n)(排序需要)。

代码实现

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

public class Solution {

    public double maxPrice(int[][] items, int capacity) {
        long totalW = 0;
        for (int[] it : items) totalW += it[1];
        if (totalW < capacity) return -1.0;
        Arrays.sort(items, (a, b) ->
                Double.compare((double) b[0] / b[1], (double) a[0] / a[1]));
        double price = 0;
        int cap = capacity;
        for (int[] it : items) {
            if (cap <= 0) break;
            if (it[1] <= cap) {
                price += it[0];
                cap -= it[1];
            } else {
                // 取一部分
                price += (double) it[0] * cap / it[1];
                cap = 0;
            }
        }
        return price;
    }

    public double maxPriceStep(int[][] items, int capacity) {
        long totalW = 0;
        for (int[] it : items) totalW += it[1];
        if (totalW < capacity) return -1.0;
        boolean[] used = new boolean[items.length];
        double price = 0;
        int cap = capacity;
        while (cap > 0) {
            int best = -1;
            for (int i = 0; i < items.length; i++) {
                if (used[i]) continue;
                if (items[i][1] == 0) continue;
                if (best == -1 || (double) items[i][0] / items[i][1] > (double) items[best][0] / items[best][1]) {
                    best = i;
                }
            }
            if (best == -1) break;
            used[best] = true;
            if (items[best][1] <= cap) {
                price += items[best][0];
                cap -= items[best][1];
            } else {
                price += (double) items[best][0] * cap / items[best][1];
                cap = 0;
            }
        }
        return price;
    }

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

        // 示例测试
        double r1 = s.maxPrice(new int[][]{{50, 1}, {10, 8}}, 5);
        System.out.println(String.format("%.5f", r1) + (Math.abs(r1 - 55.0) < 1e-9 ? " PASS" : " FAIL expect 55"));
        double r2 = s.maxPrice(new int[][]{{100, 30}}, 50);
        System.out.println(String.format("%.5f", r2) + (Math.abs(r2 - (-1.0)) < 1e-9 ? " PASS" : " FAIL expect -1"));

        // 随机对拍(逐步贪心)
        java.util.Random rnd = new java.util.Random(19);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 1 + rnd.nextInt(6);
            int[][] items = new int[n][2];
            for (int i = 0; i < n; i++) {
                items[i][0] = 1 + rnd.nextInt(20);
                items[i][1] = 1 + rnd.nextInt(6);
            }
            int cap = 1 + rnd.nextInt(30);
            double rA = s.maxPrice(items, cap);
            double rB = s.maxPriceStep(items, cap);
            if (Math.abs(rA - rB) > 1e-7) {
                ok = false;
                System.out.println("Mismatch cap=" + cap + " items=" + Arrays.deepToString(items)
                        + " main=" + rA + " step=" + rB);
                break;
            }
        }
        System.out.println(ok ? "2548 全部 PASS" : "2548 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n log n);空间复杂度:O(n)(排序需要)。


2549. Count Distinct Numbers on Board(统计桌面上的不同数字)

题目描述

给你一个正整数 n,开始时它放在桌面上。在 10^9 天内,每天都要执行下述步骤:

对于出现在桌面上的每个数字 x,找出符合 1 <= i <= n 且满足 x % i == 1 的所有数字 i,

然后将这些数字放在桌面上。

返回在 10^9 天之后,出现在桌面上的不同整数的数目。一旦数字放在桌面上,会一直保留。

示例 1

复制代码
n = 5 -> 4(最终桌面为 2,3,4,5)

示例 2

复制代码
n = 3 -> 2

提示:

  • 1 <= n <= 100

解题思路

解法一:数学推导(主提交版本)

对于任意 x > 1,有 x % (x - 1) == 1,因此第二天会把 x-1 放上桌面;

重复这个过程,2, 3, ..., n 都会出现在桌面上(n 本身一开始就有)。

而当 x = 1 时,1 % i == 1 需要 i = 1(1 % 1 == 0)?1 % 1 = 0,不满足;

且没有任何 i 使 1 % i == 1(i > 1 时 1 % i == 1,但需要 i <= n 任意。

等等,若 1 已在桌面上,1 % i == 1 对任意 i > 1 成立,但这些 i 已在桌面(<= n)。

因此最终桌面上恰好是 {2, 3, ..., n},共 n-1 个;当 n = 1 时答案为 1。

即:n == 1 ? 1 : n - 1。

解法二:模拟集合扩张,对照

按题意逐天把新增数字加入集合,直到收敛,

仅用于小数据验证(10^9 天内显然在有限步收敛,因为数字只来自 1...n)。

时间复杂度:O(1);空间复杂度:O(1)。

代码实现

java 复制代码
public class Solution {

    public int distinctIntegers(int n) {
        return n == 1 ? 1 : n - 1;
    }

    public int distinctIntegersSim(int n) {
        boolean[] on = new boolean[n + 2];
        on[n] = true;
        boolean changed = true;
        while (changed) {
            changed = false;
            for (int x = 1; x <= n; x++) {
                if (!on[x]) continue;
                for (int i = 1; i <= n; i++) {
                    if (x % i == 1 && !on[i]) {
                        on[i] = true;
                        changed = true;
                    }
                }
            }
        }
        int cnt = 0;
        for (int i = 1; i <= n; i++) if (on[i]) cnt++;
        return cnt;
    }

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

        // 示例测试
        System.out.println(s.distinctIntegers(5) == 4 ? "PASS" : "FAIL");
        System.out.println(s.distinctIntegers(3) == 2 ? "PASS" : "FAIL");
        System.out.println(s.distinctIntegers(1) == 1 ? "PASS" : "FAIL");

        // 全量对拍 1..100
        boolean ok = true;
        for (int n = 1; n <= 100; n++) {
            if (s.distinctIntegers(n) != s.distinctIntegersSim(n)) {
                ok = false;
                System.out.println("Mismatch n=" + n + " main=" + s.distinctIntegers(n)
                        + " sim=" + s.distinctIntegersSim(n));
                break;
            }
        }
        System.out.println(ok ? "2549 全部 PASS" : "2549 存在不一致");
    }
}

复杂度分析

时间复杂度:O(1);空间复杂度:O(1)。


2550. Count Collisions of Monkeys on a Polygon(猴子碰撞的方法数)

题目描述

有一个正凸多边形,共有 n 个顶点,顶点按顺时针从 0 到 n-1 编号,每个顶点上正好有一只猴子。

每个猴子同时移动到相邻顶点(顺时针 (i+1)%n 或逆时针 (i-1+n)%n)。

如果移动后至少有两只猴子停留在同一个顶点上或相交在一条边上,则发生碰撞。

返回猴子至少发生一次碰撞的移动方法数,答案对 10^9+7 取余。每只猴子只能移动一次。

示例 1

复制代码
n = 3 -> 6

示例 2

复制代码
n = 4 -> 14

提示:

  • 3 <= n <= 10^9

解题思路

解法一:容斥(主提交版本)

每只猴子有顺/逆 2 种方向,总方案 2^n。

不发生碰撞的唯一可能是所有猴子沿同一个方向移动(全顺或全逆),共 2 种。

所以答案 = (2^n - 2) mod MOD。

解法二:暴力枚举,对照

n 小时枚举每只猴子的方向,检查是否碰撞(同一顶点或边相交)。

时间复杂度:O(log n)(快速幂);空间复杂度:O(1)。

代码实现

java 复制代码
public class Solution {

    private static final int MOD = 1_000_000_007;

    public int monkeyMove(int n) {
        long p = 1, base = 2;
        long e = n;
        while (e > 0) {
            if ((e & 1) == 1) p = p * base % MOD;
            base = base * base % MOD;
            e >>= 1;
        }
        return (int) ((p - 2 + MOD) % MOD);
    }

    public int monkeyMoveBrute(int n) {
        int total = 1 << n;
        int collide = 0;
        for (int mask = 0; mask < total; mask++) {
            int[] pos = new int[n];
            for (int i = 0; i < n; i++) {
                int dir = (mask >> i) & 1; // 0=顺,1=逆
                pos[i] = dir == 0 ? (i + 1) % n : (i - 1 + n) % n;
            }
            // 检查同一顶点
            boolean same = false;
            for (int i = 0; i < n && !same; i++)
                for (int j = i + 1; j < n; j++)
                    if (pos[i] == pos[j]) { same = true; break; }
            // 检查边相交:两只猴子 i、j 交换方向(i 到 j 的位置,j 到 i 的位置)
            boolean edge = false;
            for (int i = 0; i < n && !edge; i++) {
                for (int j = i + 1; j < n; j++) {
                    // 相邻顶点上的猴子互换了位置(边 i-j 被两只猴子反向穿过)
                    if ((i + 1) % n == j && pos[i] == j && pos[j] == i) edge = true;
                    if ((j + 1) % n == i && pos[i] == j && pos[j] == i) edge = true;
                }
            }
            if (same || edge) collide++;
        }
        return collide;
    }

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

        // 示例测试
        System.out.println(s.monkeyMove(3) == 6 ? "PASS" : "FAIL");
        System.out.println(s.monkeyMove(4) == 14 ? "PASS" : "FAIL");

        // 对拍 n = 5..10(暴力 2^n)
        boolean ok = true;
        for (int n = 3; n <= 10; n++) {
            int r1 = s.monkeyMove(n);
            int r2 = s.monkeyMoveBrute(n);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch n=" + n + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2550 全部 PASS" : "2550 存在不一致");
    }
}

复杂度分析

时间复杂度:O(log n)(快速幂);空间复杂度:O(1)。


2551. Put Marbles in Bags(将珠子放入背包中)

题目描述

你有 k 个背包。给定整数数组 weights(weights[i] 是第 i 个珠子的重量)和整数 k。

规则:没有背包是空的;如果第 i 个珠子和第 j 个珠子在同一个背包里,那么下标在 i 到 j

之间的所有珠子都必须在这同一个背包中;一个背包(含下标 i...j)的价格是 weights[i] + weights[j]。

分配方案的分数是所有 k 个背包的价格之和。

返回所有分配方案中,最大分数与最小分数的差值。

示例 1

复制代码
weights = [1,3,5,1], k = 2 -> 4

示例 2

复制代码
weights = [1,3], k = 2 -> 0

提示:

  • 1 <= k <= weights.length <= 10^5;1 <= weightsi <= 10^9

解题思路

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

把数组切成 k 段,段 i...j 贡献 weightsi + weightsj。

总分数 = Σ (每段左端点 + 每段右端点) = weights0 + weightsn-1

  • Σ (中间 k-1 个切分点的相邻两元素之和)。
    具体地,在内部位置 p(1 <= p <= n-2)切开时,p 会作为前一段的右端、p+1 作为
    后一段的左端,因此每个切分点贡献 weightsp + weightsp+1。
    最大分数 = weights0+weightsn-1 + 最大的 k-1 个 (weightsp+weightsp+1);
    最小分数 = 用最小的 k-1 个。
    解法二:DFS 枚举切分方案,对照
    小数据枚举所有 C(n-1, k-1) 切分求极值差。
    时间复杂度:O(n log n);空间复杂度:O(n)。

代码实现

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

public class Solution {

    public long putMarbles(int[] weights, int k) {
        int n = weights.length;
        if (k == 1 || k == n) return 0;
        long[] cuts = new long[n - 1];
        for (int i = 0; i < n - 1; i++) cuts[i] = (long) weights[i] + weights[i + 1];
        Arrays.sort(cuts);
        long base = (long) weights[0] + weights[n - 1];
        long maxV = base, minV = base;
        int m = k - 1;
        for (int i = n - 2; i >= n - 1 - m; i--) maxV += cuts[i];
        for (int i = 0; i < m; i++) minV += cuts[i];
        return maxV - minV;
    }

    public long putMarblesBrute(int[] weights, int k) {
        long[] minMax = {Long.MAX_VALUE, Long.MIN_VALUE};
        dfsSplit(0, k, 0, weights, minMax);
        return minMax[1] - minMax[0];
    }

    private void dfsSplit(int pos, int parts, long cur, int[] w, long[] minMax) {
        int n = w.length;
        if (parts == 1) {
            // 最后一段 [pos..n-1]
            long score = cur + w[pos] + w[n - 1];
            minMax[0] = Math.min(minMax[0], score);
            minMax[1] = Math.max(minMax[1], score);
            return;
        }
        // 当前段 [pos..end],end 至少留 parts-1 个元素给后面
        for (int end = pos; end <= n - parts; end++) {
            long seg = w[pos] + w[end];
            dfsSplit(end + 1, parts - 1, cur + seg, w, minMax);
        }
    }

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

        // 示例测试
        System.out.println(s.putMarbles(new int[]{1, 3, 5, 1}, 2) == 4 ? "PASS" : "FAIL");
        System.out.println(s.putMarbles(new int[]{1, 3}, 2) == 0 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(20);
        boolean ok = true;
        for (int t = 0; t < 600; t++) {
            int n = 2 + rnd.nextInt(8);
            int k = 1 + rnd.nextInt(n);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(20);
            long r1 = s.putMarbles(a, k);
            long r2 = s.putMarblesBrute(a, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch k=" + k + " w=" + Arrays.toString(a)
                        + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2551 全部 PASS" : "2551 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n log n);空间复杂度:O(n)。


2552. Count Increasing Quadruplets(统计上升四元组)

题目描述

给你一个长度为 n、下标从 0 开始的整数数组 nums,它包含 1 到 n 的所有数字

(是一个排列),返回上升四元组的数目。

四元组 (i, j, k, l) 上升当且仅当:0 <= i < j < k < l < n 且

nums[i] < nums[k] < nums[j] < nums[l]。

示例 1

复制代码
nums = [1,3,2,4,5] -> 2

示例 2

复制代码
nums = [1,2,3,4] -> 0

提示:

  • 4 <= nums.length <= 4000;1 <= nums[i] <= nums.length;元素互不相同

解题思路

解法一:二层循环 + 累计计数,O(n^2) 且 O(n) 空间(主提交版本)

外层枚举四元组最后一个位置 l,内层枚举候选「第二个位置」k(k < l)。

维护 cnt = 在当前位置之前、值小于 numsl 的元素个数(它们可作四元组的 i);

dpk = 到目前为止,以 k 为「第二个元素 j」、且满足

i < j < k < l'(某个更早的位置) 且 numsi < numsk < numsj

的三元组个数。

转移:

  • numsk < numsl 时:k 可作为 i 的候选,cnt++;
    同时若 dpk > 0,说明存在 (i, j) 对使 numsi < numsk < numsj,
    再把 l 作为第四个元素即可与这些 (i, j, k) 组成四元组,ans += dpk。
  • numsk > numsl 时:k 可作为「第二个元素 j」(numsk > numsl),
    前面 cnt 个 i 都满足 i < k 且 numsi < numsl < numsk,
    因此 dpk += cnt。
    解法二:暴力四重循环,对照
    O(n^4) 仅小数据验证。
    时间复杂度:O(n^2);空间复杂度:O(n)。

代码实现

java 复制代码
public class Solution {

    public long countQuadruplets(int[] nums) {
        int n = nums.length;
        long ans = 0;
        long[] dp = new long[n];
        for (int l = 0; l < n; l++) {
            int cnt = 0; // 当前 l 之前,值 < nums[l] 的元素个数
            for (int k = 0; k < l; k++) {
                if (nums[k] < nums[l]) {
                    cnt++;
                    ans += dp[k];
                } else if (nums[k] > nums[l]) {
                    dp[k] += cnt;
                }
            }
        }
        return ans;
    }

    public long countQuadrupletsBrute(int[] nums) {
        int n = nums.length;
        long 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++) {
                    if (!(nums[i] < nums[k] && nums[k] < nums[j])) continue;
                    for (int l = k + 1; l < n; l++) {
                        if (nums[j] < nums[l]) ans++;
                    }
                }
            }
        }
        return ans;
    }

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

        // 示例测试
        System.out.println(s.countQuadruplets(new int[]{1, 3, 2, 4, 5}) == 2 ? "PASS" : "FAIL");
        System.out.println(s.countQuadruplets(new int[]{1, 2, 3, 4}) == 0 ? "PASS" : "FAIL");

        // 随机对拍(排列小数据)
        java.util.Random rnd = new java.util.Random(21);
        boolean ok = true;
        for (int t = 0; t < 500; t++) {
            int n = 4 + rnd.nextInt(9);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = i + 1;
            // 打乱
            for (int i = n - 1; i > 0; i--) {
                int j = rnd.nextInt(i + 1);
                int tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
            long r1 = s.countQuadruplets(a);
            long r2 = s.countQuadrupletsBrute(a);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch a=" + java.util.Arrays.toString(a)
                        + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2552 全部 PASS" : "2552 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n^2);空间复杂度:O(n)。


2553. Separate the Digits in an Array(分割数组中数字的数位)

题目描述

给你一个正整数数组 nums,返回一个数组 answer:将 nums 中每个整数进行数位分割后

(各数位按原本出现的顺序组成数组),按照 nums 中出现的相同顺序放入 answer。

示例 1

复制代码
nums = [13,25,83,77] -> [1,3,2,5,8,3,7,7]

示例 2

复制代码
nums = [7,1,3,9] -> [7,1,3,9]

提示:

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

解题思路

解法一:模拟 + 列表(主提交版本)

对每个整数,先转字符串逐字符取数位,或取模逆序再反转,依次加入结果列表。

解法二:字符串拼接,对照

将每个数的十进制字符串逐字符追加,再转 int 数组。

时间复杂度:O(total digits);空间复杂度:O(total digits)。

代码实现

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

public class Solution {

    public int[] separateDigits(int[] nums) {
        List<Integer> list = new ArrayList<Integer>();
        for (int x : nums) {
            List<Integer> tmp = new ArrayList<Integer>();
            while (x > 0) {
                tmp.add(x % 10);
                x /= 10;
            }
            for (int i = tmp.size() - 1; i >= 0; i--) list.add(tmp.get(i));
        }
        int[] res = new int[list.size()];
        for (int i = 0; i < res.length; i++) res[i] = list.get(i);
        return res;
    }

    public int[] separateDigitsStr(int[] nums) {
        StringBuilder sb = new StringBuilder();
        for (int x : nums) sb.append(x);
        int[] res = new int[sb.length()];
        for (int i = 0; i < sb.length(); i++) res[i] = sb.charAt(i) - '0';
        return res;
    }

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

        // 示例测试
        System.out.println(java.util.Arrays.equals(s.separateDigits(new int[]{13, 25, 83, 77}), new int[]{1, 3, 2, 5, 8, 3, 7, 7}) ? "PASS" : "FAIL");
        System.out.println(java.util.Arrays.equals(s.separateDigits(new int[]{7, 1, 3, 9}), new int[]{7, 1, 3, 9}) ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(22);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 1 + rnd.nextInt(20);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(100000);
            if (!java.util.Arrays.equals(s.separateDigits(a), s.separateDigitsStr(a))) {
                ok = false;
                System.out.println("Mismatch: " + java.util.Arrays.toString(a));
                break;
            }
        }
        System.out.println(ok ? "2553 全部 PASS" : "2553 存在不一致");
    }
}

复杂度分析

时间复杂度:O(total digits);空间复杂度:O(total digits)。


2554. Maximum Number of Integers to Choose From a Range I(从一个范围内选择最多整数 I)

题目描述

给你一个整数数组 banned 和两个整数 n 和 maxSum。

规则:选择范围 1, `n` 内的整数,每个整数至多选一次,不能在 banned 中,

所选整数之和不超过 maxSum。返回最多可以选择的整数数目。

示例 1

复制代码
banned = [1,6,5], n = 5, maxSum = 6 -> 2

示例 2

复制代码
banned = [1,2,3,4,5,6,7], n = 8, maxSum = 1 -> 0

示例 3

复制代码
banned = [11], n = 7, maxSum = 50 -> 7

提示:

  • 1 <= banned.length <= 10^4;1 <= bannedi, n <= 10^4;1 <= maxSum <= 10^9

解题思路

解法一:贪心 + 哈希集合(主提交版本)

为了选最多且和不超过 maxSum,应从小到大选未被禁止的数,直到和超限。

顺序选择 1...n 中未被 banned 的数即可。

解法二:布尔数组标记,对照

同样从小到大贪心,用 boolean\[\] 标记 banned。

时间复杂度:O(n);空间复杂度:O(n)。

代码实现

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

public class Solution {

    public int maxCount(int[] banned, int n, int maxSum) {
        HashSet<Integer> forbid = new HashSet<Integer>();
        for (int b : banned) forbid.add(b);
        long sum = 0;
        int cnt = 0;
        for (int x = 1; x <= n; x++) {
            if (forbid.contains(x)) continue;
            sum += x;
            if (sum > maxSum) break;
            cnt++;
        }
        return cnt;
    }

    public int maxCountBool(int[] banned, int n, int maxSum) {
        boolean[] forbid = new boolean[n + 1];
        for (int b : banned) {
            if (b <= n) forbid[b] = true;
        }
        long sum = 0;
        int cnt = 0;
        for (int x = 1; x <= n; x++) {
            if (forbid[x]) continue;
            sum += x;
            if (sum > maxSum) break;
            cnt++;
        }
        return cnt;
    }

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

        // 示例测试
        System.out.println(s.maxCount(new int[]{1, 6, 5}, 5, 6) == 2 ? "PASS" : "FAIL");
        System.out.println(s.maxCount(new int[]{1, 2, 3, 4, 5, 6, 7}, 8, 1) == 0 ? "PASS" : "FAIL");
        System.out.println(s.maxCount(new int[]{11}, 7, 50) == 7 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(23);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 1 + rnd.nextInt(100);
            int m = rnd.nextInt(20);
            int[] ban = new int[m];
            for (int i = 0; i < m; i++) ban[i] = 1 + rnd.nextInt(n);
            int maxSum = 1 + rnd.nextInt(2000);
            int r1 = s.maxCount(ban, n, maxSum);
            int r2 = s.maxCountBool(ban, n, maxSum);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch ban=" + java.util.Arrays.toString(ban)
                        + " n=" + n + " maxSum=" + maxSum);
                break;
            }
        }
        System.out.println(ok ? "2554 全部 PASS" : "2554 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n);空间复杂度:O(n)。


2555. Maximize Win From Two Segments(两个线段获得的最多奖品)

题目描述

在 X 轴上有些奖品。给你非递减排列的数组 prizePositions(奖品位置)和整数 k。

可以同时选择两个端点为整数、长度为 k 的线段(可相交),获得位于任一线段上的所有奖品

(含端点)。返回在选两个最优线段的前提下可获得的最多奖品数目。

示例 1

复制代码
prizePositions = [1,1,2,2,3,3,5], k = 2 -> 7

示例 2

复制代码
prizePositions = [1,2,3,4], k = 0 -> 2

提示:

  • 1 <= prizePositions.length <= 10^5;1 <= prizePositions[i] <= 10^9;
  • 0 <= k <= 10^9;数组非递减。

解题思路

解法一:滑动窗口 + 前缀最大(主提交版本)

对于每个右端点 i(线段 prizePositions\[i-k, prizePositionsi] 右端恰好盖住 i),

用双指针求以 i 结尾的线段最多覆盖的最左下标 j,得到窗口大小 wi = i-j+1。

定义 besti = 前 i 个位置中单个线段的最大覆盖数 = max(besti-1, wi)。

答案 = max over i (wi + bestj-1),其中两个线段不相交(各自独占奖品)。

因为两条线段可相交,但最优情况下取不相交的两个窗口即可覆盖同样奖品

(相交线段可平移为相邻,总覆盖数不会减少)。

解法二:暴力枚举两线段,对照

小数据枚举左线段右端 i、右线段右端 j(i < j),

计算两窗口并集大小。

时间复杂度:O(n);空间复杂度:O(n)。

代码实现

java 复制代码
public class Solution {

    public int maximizeWin(int[] prizePositions, int k) {
        int n = prizePositions.length;
        int[] w = new int[n]; // 以 i 为右端点的最佳长度 k 线段覆盖数
        int[] best = new int[n]; // 前 i 个位置内单线段最大覆盖
        int j = 0;
        for (int i = 0; i < n; i++) {
            while (prizePositions[i] - prizePositions[j] > k) j++;
            w[i] = i - j + 1;
            best[i] = (i == 0 ? w[i] : Math.max(best[i - 1], w[i]));
        }
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int prev = (i - w[i] >= 0 ? best[i - w[i]] : 0);
            ans = Math.max(ans, w[i] + prev);
        }
        return ans;
    }

    public int maximizeWinBrute(int[] prizePositions, int k) {
        int n = prizePositions.length;
        int best = 0;
        for (int i = 0; i < n; i++) {
            int leftEnd = prizePositions[i];
            for (int j = 0; j <= i; j++) {
                // 左线段 [leftEnd-k, leftEnd] 与右线段覆盖
                // 直接枚举两根线段的具体位置(每根线段右端选奖品位置)
                for (int a = 0; a < n; a++) {
                    int l1 = prizePositions[a] - k, r1 = prizePositions[a];
                    for (int b = 0; b < n; b++) {
                        int l2 = prizePositions[b] - k, r2 = prizePositions[b];
                        int cnt = 0;
                        for (int p : prizePositions) {
                            if ((p >= l1 && p <= r1) || (p >= l2 && p <= r2)) cnt++;
                        }
                        best = Math.max(best, cnt);
                    }
                }
            }
        }
        return best;
    }

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

        // 示例测试
        System.out.println(s.maximizeWin(new int[]{1, 1, 2, 2, 3, 3, 5}, 2) == 7 ? "PASS" : "FAIL");
        System.out.println(s.maximizeWin(new int[]{1, 2, 3, 4}, 0) == 2 ? "PASS" : "FAIL");

        // 随机对拍(小数据)
        java.util.Random rnd = new java.util.Random(24);
        boolean ok = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(8);
            int[] a = new int[n];
            int cur = 1;
            for (int i = 0; i < n; i++) {
                cur += rnd.nextInt(3); // 非递减且允许重复
                a[i] = cur;
            }
            int k = rnd.nextInt(5);
            int r1 = s.maximizeWin(a, k);
            int r2 = s.maximizeWinBrute(a, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch a=" + java.util.Arrays.toString(a)
                        + " k=" + k + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2555 全部 PASS" : "2555 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n);空间复杂度:O(n)。


2556. Disconnect Path in a Binary Matrix by at Most One Flip

题目描述

(二进制矩阵中翻转最多一次使路径不连通)

给你一个下标从 0 开始的 m x n 二进制矩阵 grid。可以从 (row, col) 移动到

(row+1, col) 或 (row, col+1),前提是目标格子值为 1。如果从 (0,0) 到 (m-1, n-1)

没有任何路径,称矩阵不连通。

可以翻转最多一个格子的值(0 变 1 或 1 变 0,也可不翻转),不能翻转 (0,0) 和 (m-1,n-1)。

如果可以使矩阵不连通,返回 true,否则返回 false。

示例 1

复制代码
grid = [[1,1,1],[1,0,0],[1,1,1]] -> true

示例 2

复制代码
grid = [[1,1,1],[1,0,1],[1,1,1]] -> false

提示:

  • m == grid.length,n == grid[i].length,1 <= m, n <= 1000,
  • 1 <= m*n <= 10^5,grid[0][0] == grid[m-1][n-1] == 1

解题思路

解法一:路径计数 DP + 双模哈希判定必经格(主提交版本)

所有路径单调向右下。设 cntSrc = 起点到 (r,c) 的路径数,

cntTrc = (r,c) 到终点的路径数,则经过 (r,c) 的完整路径数 =

cntSrc * cntTrc。某格子 (r,c) 是「必经格」(所有路径都经过它)

当且仅当 cntSrc * cntTrc == total(起点到终点的总路径数)。

由于路径数可能巨大(组合数级别),用两个大质数取模哈希存储与比较,

双模同时相等的碰撞概率可忽略。

判定:

  • total == 0:本来就不连通,true;
  • 存在非起终点的格子(值为 1)使 cntS*cntT == total:翻转它即断开,true;
  • 否则 false。
    解法二:暴力枚举翻转 + BFS 连通检查,对照
    小数据下枚举每个格子翻转(或不翻转),
    每次 BFS 检查是否仍连通。
    时间复杂度:O(mn);空间复杂度:O(mn)。

代码实现

java 复制代码
public class Solution {

    private static final long MOD1 = 1_000_000_007L;
    private static final long MOD2 = 1_000_000_009L;

    public boolean isPossibleToCutPath(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        if (m == 1 && n == 1) return false;
        // 正向路径数(双模)
        long[][] s1 = new long[m][n];
        long[][] s2 = new long[m][n];
        s1[0][0] = 1;
        s2[0][0] = 1;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0) { continue; }
                if (i == 0 && j == 0) continue;
                long a = 0, b = 0;
                if (i > 0) { a += s1[i - 1][j]; b += s2[i - 1][j]; }
                if (j > 0) { a += s1[i][j - 1]; b += s2[i][j - 1]; }
                s1[i][j] = a % MOD1;
                s2[i][j] = b % MOD2;
            }
        }
        long t1 = s1[m - 1][n - 1];
        long t2 = s2[m - 1][n - 1];
        if (t1 == 0 && t2 == 0) return true; // 本来就不连通
        if (t1 == 0 || t2 == 0) return true; // 单模为 0 说明总路径数恰为模的倍数,按大数非 0 处理即可,但仍可能真实路径数很小
        // 反向路径数
        long[][] e1 = new long[m][n];
        long[][] e2 = new long[m][n];
        e1[m - 1][n - 1] = 1;
        e2[m - 1][n - 1] = 1;
        for (int i = m - 1; i >= 0; i--) {
            for (int j = n - 1; j >= 0; j--) {
                if (grid[i][j] == 0) continue;
                if (i == m - 1 && j == n - 1) continue;
                long a = 0, b = 0;
                if (i + 1 < m) { a += e1[i + 1][j]; b += e2[i + 1][j]; }
                if (j + 1 < n) { a += e1[i][j + 1]; b += e2[i][j + 1]; }
                e1[i][j] = a % MOD1;
                e2[i][j] = b % MOD2;
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0) continue;
                if (i == m - 1 && j == n - 1) continue;
                if (grid[i][j] == 0) continue;
                long c1 = s1[i][j] * e1[i][j] % MOD1;
                long c2 = s2[i][j] * e2[i][j] % MOD2;
                if (c1 == t1 && c2 == t2) return true;
            }
        }
        return false;
    }

    public boolean isPossibleToCutPathBrute(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        if (!reachable(grid, -1, -1)) return true;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0) continue;
                if (i == m - 1 && j == n - 1) continue;
                if (grid[i][j] != 1) continue;
                if (!reachable(grid, i, j)) return true;
            }
        }
        return false;
    }

    private boolean reachable(int[][] grid, int flipR, int flipC) {
        int m = grid.length;
        int n = grid[0].length;
        boolean[][] vis = new boolean[m][n];
        java.util.ArrayDeque<int[]> q = new java.util.ArrayDeque<int[]>();
        vis[0][0] = true;
        q.offer(new int[]{0, 0});
        int[] dr = {1, 0};
        int[] dc = {0, 1};
        while (!q.isEmpty()) {
            int[] cur = q.poll();
            if (cur[0] == m - 1 && cur[1] == n - 1) return true;
            for (int d = 0; d < 2; d++) {
                int nr = cur[0] + dr[d];
                int nc = cur[1] + dc[d];
                if (nr < 0 || nr >= m || nc < 0 || nc >= n) continue;
                if (vis[nr][nc]) continue;
                if (grid[nr][nc] == 0) continue;
                if (nr == flipR && nc == flipC) continue; // 翻转后视为 0
                vis[nr][nc] = true;
                q.offer(new int[]{nr, nc});
            }
        }
        return false;
    }

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

        // 示例测试
        System.out.println(s.isPossibleToCutPath(new int[][]{{1, 1, 1}, {1, 0, 0}, {1, 1, 1}}) ? "PASS" : "FAIL");
        System.out.println(!s.isPossibleToCutPath(new int[][]{{1, 1, 1}, {1, 0, 1}, {1, 1, 1}}) ? "PASS" : "FAIL");
        System.out.println(!s.isPossibleToCutPath(new int[][]{{1}}) ? "PASS" : "FAIL");
        System.out.println(s.isPossibleToCutPath(new int[][]{{1, 1}, {0, 1}}) ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(25);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int m = 1 + rnd.nextInt(5);
            int n = 1 + rnd.nextInt(5);
            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);
            g[0][0] = 1;
            g[m - 1][n - 1] = 1;
            boolean r1 = s.isPossibleToCutPath(g);
            boolean r2 = s.isPossibleToCutPathBrute(g);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch g=" + java.util.Arrays.deepToString(g)
                        + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2556 全部 PASS" : "2556 存在不一致");
    }
}

复杂度分析

时间复杂度:O(mn);空间复杂度:O(mn)。


2557. Maximum Number of Integers to Choose From a Range II

题目描述

(从一个范围内选择最多整数 II)

给你一个整数数组 banned 和两个整数 n 和 maxSum。

规则:选择范围 1, `n` 内的整数,每个整数至多选一次,不能在 banned 中,

所选整数之和不超过 maxSum。返回最多可以选择的整数数目。

示例 1

复制代码
banned = [1,4,6], n = 6, maxSum = 4 -> 1

示例 2

复制代码
banned = [4,3,5,6], n = 7, maxSum = 18 -> 3

提示:

  • 1 <= banned.length <= 10^5;1 <= bannedi <= n <= 10^9;1 <= maxSum <= 10^15

解题思路

解法一:贪心 + 区间段二分(主提交版本)

n 最大 10^9,不能用数组。把 banned 排序去重,

将 1, n 按被 banned 分隔开来的「连续可取段」逐个处理。

为使总数最多且和不超限,段内从小往大取;设当前已用和为 s,

对段 L, R,取 t 个的最小和为 t*(2L + t - 1)/2,二分最大的 t 使 s + 该和 <= maxSum,

然后 s 累加、cnt 累加,继续处理下一段。

由于每段取值互不重叠,贪心正确。

解法二:暴力从小到大贪心,对照

小数据直接顺序扫描 1, n 跳过 banned。

时间复杂度:O(b log b);空间复杂度:O(b)(排序)。

代码实现

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

public class Solution {

    public int maxCount(int[] banned, int n, long maxSum) {
        Arrays.sort(banned);
        long sofar = 0;
        int cnt = 0;
        long segStart = 1;
        for (int b : banned) {
            if (b > n) break;
            if (segStart <= b - 1) {
                long[] r = takeFrom(segStart, b - 1, maxSum - sofar);
                cnt += r[0];
                sofar += r[1];
            }
            segStart = b + 1;
        }
        // 最后一段 [segStart, n]
        if (segStart <= n) {
            long[] r = takeFrom(segStart, n, maxSum - sofar);
            cnt += r[0];
            sofar += r[1];
        }
        return cnt;
    }

    private long[] takeFrom(long L, long R, long cap) {
        long lo = 0, hi = R - L + 1;
        while (lo < hi) {
            long mid = (lo + hi + 1) / 2;
            long need = mid * (2 * L + mid - 1) / 2; // L + (L+1) + ... + (L+mid-1)
            if (need <= cap) lo = mid;
            else hi = mid - 1;
        }
        long used = lo * (2 * L + lo - 1) / 2;
        return new long[]{lo, used};
    }

    public int maxCountBrute(int[] banned, int n, long maxSum) {
        boolean[] forbid = new boolean[n + 1];
        for (int b : banned) {
            if (b <= n) forbid[b] = true;
        }
        long s = 0;
        int cnt = 0;
        for (int x = 1; x <= n; x++) {
            if (forbid[x]) continue;
            s += x;
            if (s > maxSum) break;
            cnt++;
        }
        return cnt;
    }

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

        // 示例测试
        System.out.println(s.maxCount(new int[]{1, 4, 6}, 6, 4) == 1 ? "PASS" : "FAIL");
        System.out.println(s.maxCount(new int[]{4, 3, 5, 6}, 7, 18) == 3 ? "PASS" : "FAIL");

        // 随机对拍(n 小)
        java.util.Random rnd = new java.util.Random(26);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 1 + rnd.nextInt(500);
            int m = rnd.nextInt(30);
            int[] ban = new int[m];
            for (int i = 0; i < m; i++) ban[i] = 1 + rnd.nextInt(n);
            long maxSum = 1 + rnd.nextInt(3000);
            int r1 = s.maxCount(ban, n, maxSum);
            int r2 = s.maxCountBrute(ban, n, maxSum);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch ban=" + Arrays.toString(ban) + " n=" + n
                        + " maxSum=" + maxSum + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2557 全部 PASS" : "2557 存在不一致");
    }
}

复杂度分析

时间复杂度:O(b log b);空间复杂度:O(b)(排序)。


2558. Take Gifts From the Richest Pile(从数量最多的堆取走礼物)

题目描述

给你一个整数数组 gifts,表示各堆礼物的数量。每一秒,选择礼物数量最多的那一堆

(如果有多个任选其一),将其中礼物数量减少到原来的平方根(向下取整)。

返回 k 秒后剩下的礼物总数量。

示例 1

复制代码
gifts = [25,64,9,4,100], k = 4 -> 29

示例 2

复制代码
gifts = [1,1,1,1], k = 4 -> 4

提示:

  • 1 <= gifts.length <= 10^3;1 <= giftsi <= 10^9;1 <= k <= 10^3

解题思路

解法一:最大堆(主提交版本)

k 秒内每次取当前最大值,替换为 floor(sqrt(x)),最后累加剩余元素。

复杂度 O(k log n)。

解法二:每轮线性找最大值,对照

小数据下每次 O(n) 扫描最大值替换。

时间复杂度:O(k log n);空间复杂度:O(n)。

代码实现

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

public class Solution {

    public long pickGifts(int[] gifts, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>((a, b) -> b - a);
        for (int g : gifts) pq.offer(g);
        for (int i = 0; i < k; i++) {
            int x = pq.poll();
            pq.offer((int) Math.sqrt(x));
        }
        long sum = 0;
        while (!pq.isEmpty()) sum += pq.poll();
        return sum;
    }

    public long pickGiftsBrute(int[] gifts, int k) {
        long[] a = new long[gifts.length];
        for (int i = 0; i < gifts.length; i++) a[i] = gifts[i];
        for (int t = 0; t < k; t++) {
            int idx = 0;
            for (int i = 1; i < a.length; i++) {
                if (a[i] > a[idx]) idx = i;
            }
            a[idx] = (long) Math.sqrt(a[idx]);
        }
        long sum = 0;
        for (long v : a) sum += v;
        return sum;
    }

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

        // 示例测试
        System.out.println(s.pickGifts(new int[]{25, 64, 9, 4, 100}, 4) == 29 ? "PASS" : "FAIL");
        System.out.println(s.pickGifts(new int[]{1, 1, 1, 1}, 4) == 4 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(27);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 1 + rnd.nextInt(15);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(1000);
            int k = 1 + rnd.nextInt(20);
            long r1 = s.pickGifts(a, k);
            long r2 = s.pickGiftsBrute(a, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch a=" + java.util.Arrays.toString(a) + " k=" + k);
                break;
            }
        }
        System.out.println(ok ? "2558 全部 PASS" : "2558 存在不一致");
    }
}

复杂度分析

时间复杂度:O(k log n);空间复杂度:O(n)。


2559. Count Vowel Strings in Ranges(统计范围内的元音字符串数)

题目描述

给你一个下标从 0 开始的字符串数组 words 以及一个二维整数数组 queries。

每个查询 queries[i] = `l_i`, `r_i` 统计 words 中下标在 `l_i`, `r_i` 范围内

(包含两端)且以元音字母开头和结尾的字符串的数目。

元音字母是 'a'、'e'、'i'、'o'、'u'。

返回一个整数数组,第 i 个元素是第 i 个查询的答案。

示例 1

复制代码
words = ["aba","bcb","ece","aa","e"], queries = [[0,2],[1,4],[1,1]] -> [2,3,0]

示例 2

复制代码
words = ["a","e","i"], queries = [[0,2],[0,1],[2,2]] -> [3,2,1]

提示:

  • 1 <= words.length <= 10^5;1 <= words[i].length <= 40;
  • 1 <= queries.length <= 10^5;0 <= l <= r < words.length

解题思路

解法一:前缀和(主提交版本)

定义 oki = wordsi 是否元音开头且结尾;前缀和 prefi = ok0+...+oki。

查询 l, r 答案 = prefr - (l > 0 ? prefl-1 : 0)。

解法二:每个查询线性扫描,对照

小数据直接遍历区间统计。

时间复杂度:O(n + q);空间复杂度:O(n)。

代码实现

java 复制代码
public class Solution {

    private boolean isVowel(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }

    public int[] vowelStrings(String[] words, int[][] queries) {
        int n = words.length;
        int[] pref = new int[n];
        for (int i = 0; i < n; i++) {
            char a = words[i].charAt(0);
            char b = words[i].charAt(words[i].length() - 1);
            pref[i] = (i > 0 ? pref[i - 1] : 0) + (isVowel(a) && isVowel(b) ? 1 : 0);
        }
        int[] ans = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int l = queries[i][0], r = queries[i][1];
            ans[i] = pref[r] - (l > 0 ? pref[l - 1] : 0);
        }
        return ans;
    }

    public int[] vowelStringsBrute(String[] words, int[][] queries) {
        int[] ans = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int cnt = 0;
            for (int j = queries[i][0]; j <= queries[i][1]; j++) {
                char a = words[j].charAt(0);
                char b = words[j].charAt(words[j].length() - 1);
                if (isVowel(a) && isVowel(b)) cnt++;
            }
            ans[i] = cnt;
        }
        return ans;
    }

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

        // 示例测试
        System.out.println(java.util.Arrays.equals(s.vowelStrings(new String[]{"aba", "bcb", "ece", "aa", "e"}, new int[][]{{0, 2}, {1, 4}, {1, 1}}), new int[]{2, 3, 0}) ? "PASS" : "FAIL");
        System.out.println(java.util.Arrays.equals(s.vowelStrings(new String[]{"a", "e", "i"}, new int[][]{{0, 2}, {0, 1}, {2, 2}}), new int[]{3, 2, 1}) ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(28);
        boolean ok = true;
        String vowels = "aeiou";
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(20);
            String[] ws = new String[n];
            for (int i = 0; i < n; i++) {
                int len = 1 + rnd.nextInt(4);
                StringBuilder sb = new StringBuilder();
                for (int j = 0; j < len; j++) sb.append((char) ('a' + rnd.nextInt(26)));
                ws[i] = sb.toString();
            }
            int q = 1 + rnd.nextInt(20);
            int[][] qs = new int[q][2];
            for (int[] qu : qs) {
                int l = rnd.nextInt(n), r = l + rnd.nextInt(n - l);
                qu[0] = l;
                qu[1] = r;
            }
            int[] r1 = s.vowelStrings(ws, qs);
            int[] r2 = s.vowelStringsBrute(ws, qs);
            if (!java.util.Arrays.equals(r1, r2)) {
                ok = false;
                System.out.println("Mismatch");
                break;
            }
        }
        System.out.println(ok ? "2559 全部 PASS" : "2559 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n + q);空间复杂度:O(n)。


2560. House Robber IV(打家劫舍 IV)

题目描述

沿街有一排连续的房屋,每间房屋内藏有一定现金。小偷不会窃取相邻的房屋。

小偷的窃取能力定义为他在窃取过程中能从单间房屋中窃取的最大金额。

给定数组 nums(每间房屋存放的现金)和整数 k(窃取的最少房屋数)。

小偷总能窃取至少 k 间房屋。返回小偷的最小窃取能力。

示例 1

复制代码
nums = [2,3,5,9], k = 2 -> 5

示例 2

复制代码
nums = [2,7,9,3,1], k = 2 -> 2

提示:

  • 1 <= nums.length <= 10^5;1 <= nums[i] <= 10^9;1 <= k <= (n+1)/2

解题思路

解法一:二分答案 + 贪心检查(主提交版本)

二分区间的能力值 mid。检查是否能在「只偷价值 <= mid 的房屋且不相邻」的限制下

偷到至少 k 间:贪心从左到右扫,能偷就偷(不偷相邻的即可),统计偷的数量。

单调性:mid 越大越容易满足。最小可行 mid 即答案。

解法二:枚举子集,对照

小数据枚举所有不相邻房屋组合,求满足数量 >= k 的

最小 max 值。

时间复杂度:O(n log max(nums));空间复杂度:O(1)。

代码实现

java 复制代码
public class Solution {

    public int minCapability(int[] nums, int k) {
        int lo = 1, hi = 1_000_000_000;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            if (canRob(nums, k, mid)) hi = mid;
            else lo = mid + 1;
        }
        return lo;
    }

    private boolean canRob(int[] nums, int k, int cap) {
        int cnt = 0;
        int i = 0;
        int n = nums.length;
        while (i < n) {
            if (nums[i] <= cap) {
                cnt++;
                i += 2; // 跳过一个相邻
            } else {
                i += 1;
            }
        }
        return cnt >= k;
    }

    public int minCapabilityBrute(int[] nums, int k) {
        int n = nums.length;
        int best = Integer.MAX_VALUE;
        dfs(0, k, 0, 0, nums);
        return (int) bruteBest;
    }

    private long bruteBest = Long.MAX_VALUE;

    private void dfs(int pos, int k, int picked, int curMax, int[] nums) {
        int n = nums.length;
        if (picked == k) {
            bruteBest = Math.min(bruteBest, curMax);
            return;
        }
        if (pos >= n) return;
        // 选 pos
        dfs(pos + 2, k, picked + 1, Math.max(curMax, nums[pos]), nums);
        // 不选 pos
        dfs(pos + 1, k, picked, curMax, nums);
    }

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

        // 示例测试
        System.out.println(s.minCapability(new int[]{2, 3, 5, 9}, 2) == 5 ? "PASS" : "FAIL");
        System.out.println(s.minCapability(new int[]{2, 7, 9, 3, 1}, 2) == 2 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(29);
        boolean ok = true;
        for (int t = 0; t < 1500; t++) {
            int n = 1 + rnd.nextInt(12);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(30);
            int k = 1 + rnd.nextInt((n + 1) / 2);
            int r1 = s.minCapability(a, k);
            s.bruteBest = Long.MAX_VALUE;
            int r2 = s.minCapabilityBrute(a, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch a=" + java.util.Arrays.toString(a)
                        + " k=" + k + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2560 全部 PASS" : "2560 存在不一致");
    }
}

复杂度分析

时间复杂度:O(n log max(nums));空间复杂度:O(1)。

相关推荐
Flynn Liang10 分钟前
RAG 评估体系:黄金集、检索指标与 LLM-as-Judge 落地
java·langchain·rag·评估·langchain4j·检索指标
SimonKing1 小时前
Workbuddy太占C盘空间了,写个迁移工具迁走10G的数据
java·后端·程序员
Hvitur2 小时前
Java - Lambda
java·开发语言·python
希望我万事顺遂2 小时前
力扣hot100-滑动窗口最大值
数据结构·算法·leetcode
是main不是漫3 小时前
【双指针|LeetCode1089 复写零】巧用逆向思路,破解原地覆盖陷阱
数据结构·算法·leetcode
apcipot_rain7 小时前
C++转Java日记(06)
java·开发语言
金銀銅鐵11 小时前
带您看懂字节码指令(一):一帧里有什么
java·后端·ai编程
nice_lcj52013 小时前
【从零理解 Spring AI · 14】RAG 的四个环节:加载、切分、向量化、检索
java·spring boot·embedding·向量数据库·rag·spring ai·ai应用开发
学长毕设15 小时前
基于springboot科创竞赛信息平台-附源码
java·spring boot·后端
聪明的笨猪猪15 小时前
Java 工程案例:AI 生成 @Async 异步线程池踩坑
java·经验分享·面试·ai编程