LeetCode2861~2880题目解析

LeetCode 2861-2880. 题解合集

文章目录


2861. Maximum Number of Alloys(最大合金数)

题目描述

假设你是一家合金制造公司的老板,你的公司使用多种金属来制造合金。

现在共有 n 种不同类型的金属,并且你可以使用 k 台机器来制造合金。

每台机器都需要特定数量的每种金属来创建合金。

对于第 i 台机器,创建合金需要 composition[i][j] 份 j 类型金属。

最初你拥有 stock[x] 份 x 类型金属,而每购入一份 x 类型金属需要花费 cost[x] 金钱。

给你整数 n、k、budget,下标从 1 开始的二维数组 composition,

两个下标从 1 开始的数组 stock 和 cost,请在预算不超过 budget 的前提下,

最大化公司制造合金的数量。所有合金都需要由同一台机器制造。

返回公司可以制造的最大合金数。

示例 1

  • 输入 :n = 3, k = 2, budget = 15, composition = \[1,1,1,1,1,10], stock = 0,0,0, cost = 1,2,3
  • 输出:2

示例 2

  • 输入 :n = 3, k = 2, budget = 15, composition = \[1,1,1,1,1,10], stock = 0,0,100, cost = 1,2,3
  • 输出:5

示例 3

  • 输入 :n = 2, k = 3, budget = 10, composition = \[2,1,1,2,1,1], stock = 1,1, cost = 5,5
  • 输出 :2
    提示:
  • 1 <= n, k <= 100
  • 0 <= budget <= 10^8
  • 1 <= composition[i][j] <= 100
  • 0 <= stock[i] <= 10^8
  • 1 <= cost[i] <= 100

解题思路

解法一:对答案二分

对每种机器独立二分最大合金数 x:制造 x 份的成本为

Σ max(0, compositionij*x - stockj) * costj,

若成本 <= budget 则可行。答案取各机器最大值。

二分上界:设 budget 全部买最便宜的金属,单个合金最大消耗 100 份,

上界 = budget + max(stock)(保险取 1e8 + 1e8 + 100)。

时间复杂度 O(k * n * log(2e8)),空间复杂度 O(1)。

解法二:暴力递增模拟

小规模逐份累加制造直到预算不足(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:二分答案(主提交版本) ----------
    public int maxNumberOfAlloys(int n, int k, int budget, List<List<Integer>> composition,
                                 List<Integer> stock, List<Integer> cost) {
        long[] s = new long[n], c = new long[n];
        long maxStock = 0;
        for (int i = 0; i < n; i++) { s[i] = stock.get(i); c[i] = cost.get(i); maxStock = Math.max(maxStock, s[i]); }
        long hiBound = maxStock + budget + 200; // 安全上界
        long best = 0;
        for (int m = 0; m < k; m++) {
            int[] comp = new int[n];
            for (int j = 0; j < n; j++) comp[j] = composition.get(m).get(j);
            long lo = 0, hi = hiBound;
            while (lo <= hi) {
                long mid = (lo + hi) >>> 1;
                if (feasible(comp, s, c, budget, mid)) {
                    best = Math.max(best, mid);
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
        }
        return (int) best;
    }

    private boolean feasible(int[] comp, long[] s, long[] c, int budget, long x) {
        long total = 0;
        for (int j = 0; j < s.length; j++) {
            long need = comp[j] * x - s[j];
            if (need > 0) total += need * c[j];
            if (total > budget) return false;
        }
        return total <= budget;
    }

    // ---------- 解法二:逐份模拟(对照,小规模) ----------
    public int maxNumberOfAlloysBrute(int n, int k, int budget, List<List<Integer>> composition,
                                      List<Integer> stock, List<Integer> cost) {
        int best = 0;
        for (int m = 0; m < k; m++) {
            int[] comp = new int[n];
            for (int j = 0; j < n; j++) comp[j] = composition.get(m).get(j);
            long[] s = new long[n];
            for (int j = 0; j < n; j++) s[j] = stock.get(j);
            int b = budget; // 每台机器独立预算
            int cnt = 0;
            while (true) {
                long costNow = 0;
                for (int j = 0; j < n; j++) {
                    long need = comp[j] - s[j];
                    if (need > 0) costNow += need * cost.get(j);
                }
                if (costNow > b) break;
                b -= (int) costNow;
                for (int j = 0; j < n; j++) s[j] = Math.max(0, s[j] - comp[j]);
                cnt++;
            }
            best = Math.max(best, cnt);
        }
        return best;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        List<List<Integer>> comp1 = new ArrayList<>();
        comp1.add(new ArrayList<>(java.util.Arrays.asList(1, 1, 1)));
        comp1.add(new ArrayList<>(java.util.Arrays.asList(1, 1, 10)));
        check(s.maxNumberOfAlloys(3, 2, 15, comp1, new ArrayList<>(java.util.Arrays.asList(0, 0, 0)), new ArrayList<>(java.util.Arrays.asList(1, 2, 3))) == 2, "2861 例1");
        check(s.maxNumberOfAlloys(3, 2, 15, comp1, new ArrayList<>(java.util.Arrays.asList(0, 0, 100)), new ArrayList<>(java.util.Arrays.asList(1, 2, 3))) == 5, "2861 例2");
        List<List<Integer>> comp2 = new ArrayList<>();
        comp2.add(new ArrayList<>(java.util.Arrays.asList(2, 1)));
        comp2.add(new ArrayList<>(java.util.Arrays.asList(1, 2)));
        comp2.add(new ArrayList<>(java.util.Arrays.asList(1, 1)));
        check(s.maxNumberOfAlloys(2, 3, 10, comp2, new ArrayList<>(java.util.Arrays.asList(1, 1)), new ArrayList<>(java.util.Arrays.asList(5, 5))) == 2, "2861 例3");

        java.util.Random rnd = new java.util.Random(79);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int n = 1 + rnd.nextInt(3);
            int k = 1 + rnd.nextInt(3);
            int budget = rnd.nextInt(60);
            List<List<Integer>> comp = new ArrayList<>();
            List<Integer> stock = new ArrayList<>(), cost = new ArrayList<>();
            for (int m = 0; m < k; m++) {
                List<Integer> row = new ArrayList<>();
                for (int j = 0; j < n; j++) row.add(1 + rnd.nextInt(5));
                comp.add(row);
            }
            for (int j = 0; j < n; j++) { stock.add(rnd.nextInt(10)); cost.add(1 + rnd.nextInt(5)); }
            int a = s.maxNumberOfAlloys(n, k, budget, comp, stock, cost);
            int b = s.maxNumberOfAlloysBrute(n, k, budget, comp, stock, cost);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 n=" + n + " k=" + k + " budget=" + budget + " comp=" + comp + " stock=" + stock + " cost=" + cost + " 二分=" + a + " 模拟=" + b);
                break;
            }
        }
        check(allOk, "2861 随机对拍");
        System.out.println("2861 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(k * n * log(2e8)),空间复杂度 O(1)。


2862. Maximum Element-Sum of a Complete Subset of Indices(完全子集的最大元素和)

题目描述

给你一个下标从 1 开始、由 n 个整数组成的数组。你需要从 nums 选择一个完全集,

其中每对元素下标的乘积都是一个完全平方数,例如选择 ai 和 aj,i * j 一定是完全平方数。

返回完全子集所能取到的最大元素和。

示例 1

  • 输入 :nums = 8,7,3,5,7,2,4,9
  • 输出:16

示例 2

  • 输入 :nums = 8,10,3,8,1,13,7,9,4
  • 输出 :20
    提示:
  • 1 <= n == nums.length <= 10^4
  • 1 <= nums[i] <= 10^9

解题思路

解法一:下标去平方因子分组

i * j 是完全平方数 ⇔ i 与 j 去掉各自平方因子后的「无平方因子的数」(square-free part)相等。

对每个下标 i,求 sqfi = i 去除所有平方因子后的值(如 8 -> 2, 12 -> 3, 18 -> 2)。

用哈希表按 sqf 分组求和,取最大组和。

时间复杂度 O(n sqrt n)(或筛法优化),空间复杂度 O(n)。

解法二:暴力两两校验

小规模枚举所有下标组合验证 i*j 是否为完全平方数(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:平方因子分组(主提交版本) ----------
    public long maximumSum(List<Integer> nums) {
        int n = nums.size();
        Map<Integer, Long> group = new HashMap<>();
        for (int idx = 1; idx <= n; idx++) {
            int sqf = squareFree(idx);
            group.merge(sqf, (long) nums.get(idx - 1), Long::sum);
        }
        long ans = 0;
        for (long v : group.values()) ans = Math.max(ans, v);
        return ans;
    }

    private int squareFree(int x) {
        for (int d = 2; d * d <= x; d++) {
            while (x % (d * d) == 0) x /= (d * d);
        }
        return x;
    }

    // ---------- 解法二:暴力(对照,小规模) ----------
    public long maximumSumBrute(List<Integer> nums) {
        int n = nums.size();
        long ans = 0;
        for (int mask = 1; mask < (1 << n); mask++) {
            List<Integer> picked = new ArrayList<>();
            for (int i = 0; i < n; i++) if (((mask >> i) & 1) != 0) picked.add(i + 1);
            boolean ok = true;
            for (int a = 0; a < picked.size() && ok; a++) {
                for (int b = a + 1; b < picked.size(); b++) {
                    long prod = (long) picked.get(a) * picked.get(b);
                    long r = (long) Math.sqrt(prod);
                    if (r * r != prod) { ok = false; break; }
                }
            }
            if (ok) {
                long sum = 0;
                for (int i : picked) sum += nums.get(i - 1);
                ans = Math.max(ans, sum);
            }
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maximumSum(new ArrayList<>(java.util.Arrays.asList(8, 7, 3, 5, 7, 2, 4, 9))) == 16, "2862 例1");
        check(s.maximumSum(new ArrayList<>(java.util.Arrays.asList(8, 10, 3, 8, 1, 13, 7, 9, 4))) == 20, "2862 例2");

        java.util.Random rnd = new java.util.Random(80);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int n = 1 + rnd.nextInt(8);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(1 + rnd.nextInt(50));
            long x = s.maximumSum(a);
            long y = s.maximumSumBrute(a);
            if (x != y) {
                allOk = false;
                System.out.println("对拍失败 nums=" + a + " 分组=" + x + " 暴力=" + y);
                break;
            }
        }
        check(allOk, "2862 随机对拍");
        System.out.println("2862 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n sqrt n)(或筛法优化),空间复杂度 O(n)。


2863. Maximum Length of Semi-Decreasing Subarrays(最长半递减子数组的长度)

题目描述

给定一个整数数组 nums。

返回 nums 的最长半递减子数组的长度,如果没有这样的子数组则返回 0。

  • 子数组是数组内的连续非空元素序列。
  • 一个非空数组是半递减的,如果它的第一个元素严格大于它的最后一个元素。

示例 1

  • 输入 :nums = 7,6,5,4,3,2,1,6,10,11
  • 输出: 8

示例 2

  • 输入 :nums = 57,55,50,60,61,58,63,59,64,60,63
  • 输出: 6

示例 3

  • 输入 :nums = 1,2,3,4
  • 输出 : 0
    提示:
  • 1 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9

解题思路

解法一:维护前缀最小值下标

一个子数组 i...j 半递减需要 numsj < numsi。

对每个 j,希望起点 i 尽量靠左且 numsi > numsj。

预处理:leftMinj = 满足 numsi <= numsj 且(i 之后)... 其实反了------

正确做法:从右往左扫,维护一个双端队列/单调栈存「比当前 numsj 更小的最左位置」。

更简单:对每个位置 j,最近一个满足 numsi > numsj 的最左 i。

线性做法:用单调栈从右往左记录「递减候选序列」,对每个 j 二分找最小 i。

另一种更简洁:预处理 preLessIdx 或使用排序。

采用:把下标按 nums 值降序排序(值相同下标靠前的优先),逐个把下标加入「可用起点」,

判断时机:对每个 j 作为终点,所有 numsi > numsj 的点都是可用起点,取最小下标即可。

具体:从大到小排序 (value, index),同时从大到小扫描 j:

当值 > numsj 的点全部已加入 TreeSet,取 TreeSet 最小值;若 < j 则长度 = j - min + 1。

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

解法二:暴力双重循环

小规模枚举所有子数组(对照)。

代码实现

java 复制代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;

public class Solution {
    // ---------- 解法一:排序 + TreeSet(主提交版本) ----------
    public int maxSubarrayLength(int[] nums) {
        int n = nums.length;
        List<int[]> sorted = new ArrayList<>(); // [value, index]
        for (int i = 0; i < n; i++) sorted.add(new int[]{nums[i], i});
        Collections.sort(sorted, (a, b) ->
                a[0] != b[0] ? Integer.compare(b[0], a[0]) : Integer.compare(a[1], b[1]));
        TreeSet<Integer> starts = new TreeSet<>();
        int ptr = 0; // 指向 sorted 中值最大且尚未加入的下标
        int ans = 0;
        for (int j = n - 1; j >= 0; j--) {
            // 加入所有值 > nums[j] 的起点(严格大于)
            while (ptr < sorted.size() && sorted.get(ptr)[0] > nums[j]) {
                starts.add(sorted.get(ptr)[1]);
                ptr++;
            }
            if (starts.isEmpty()) continue;
            int minIdx = starts.first();
            if (minIdx < j) ans = Math.max(ans, j - minIdx + 1);
        }
        return ans;
    }

    // ---------- 解法二:暴力(对照) ----------
    public int maxSubarrayLengthBrute(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                if (nums[i] > nums[j]) ans = Math.max(ans, j - i + 1);
            }
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maxSubarrayLength(new int[]{7, 6, 5, 4, 3, 2, 1, 6, 10, 11}) == 8, "2863 例1");
        check(s.maxSubarrayLength(new int[]{57, 55, 50, 60, 61, 58, 63, 59, 64, 60, 63}) == 6, "2863 例2");
        check(s.maxSubarrayLength(new int[]{1, 2, 3, 4}) == 0, "2863 例3");

        java.util.Random rnd = new java.util.Random(81);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(60);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = rnd.nextInt(101) - 50;
            int x = s.maxSubarrayLength(a);
            int y = s.maxSubarrayLengthBrute(a);
            if (x != y) {
                allOk = false;
                System.out.println("对拍失败 nums=" + java.util.Arrays.toString(a) + " 主解=" + x + " 暴力=" + y);
                break;
            }
        }
        check(allOk, "2863 随机对拍");
        System.out.println("2863 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

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


2864. Maximum Odd Binary Number(最大二进制奇数)

题目描述

给你一个二进制字符串 s,其中至少包含一个 '1'。

你必须按某种方式重新排列字符串中的位,使得到的二进制数字是

可以由该组合生成的最大二进制奇数。返回该字符串。

注意:返回的结果字符串可以含前导零。

示例 1

  • 输入 :s = "010"
  • 输出:"001"

示例 2

  • 输入 :s = "0101"
  • 输出 :"1001"
    提示:
  • 1 <= s.length <= 100
  • s 仅由 '0' 和 '1' 组成
  • s 中至少包含一个 '1'

解题思路

解法一:贪心

二进制奇数要求最低位为 1。剩下的 1 尽量放高位:

结果 = (cnt1 - 1) 个 '1' + cnt0 个 '0' + 最后补一个 '1'。

时间复杂度 O(n),空间复杂度 O(n)。

解法二:全排列找最大

小规模枚举所有排列取字典序最大且为奇数的(对照)。

代码实现

java 复制代码
public class Solution {
    // ---------- 解法一:贪心(主提交版本) ----------
    public String maximumOddBinaryNumber(String s) {
        int cnt1 = 0, cnt0 = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '1') cnt1++;
            else cnt0++;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < cnt1 - 1; i++) sb.append('1');
        for (int i = 0; i < cnt0; i++) sb.append('0');
        sb.append('1');
        return sb.toString();
    }

    // ---------- 解法二:DFS 排列(对照,小规模) ----------
    public String maximumOddBinaryNumberBrute(String s) {
        char[] cs = s.toCharArray();
        java.util.Arrays.sort(cs);
        // 从大到小生成排列,取第一个奇数的
        String best = "";
        return dfs(cs, new boolean[cs.length], new StringBuilder(), best);
    }

    private String dfs(char[] cs, boolean[] used, StringBuilder cur, String best) {
        if (cur.length() == cs.length) {
            String str = cur.toString();
            if (str.charAt(str.length() - 1) == '1') {
                if (best.isEmpty()) return str;
                // 字符串长度相同,字典序大的即数值大
                return str.compareTo(best) > 0 ? str : best;
            }
            return best;
        }
        // 降序选择,保证先构造出最大排列
        for (int i = cs.length - 1; i >= 0; i--) {
            if (used[i]) continue;
            if (i > 0 && cs[i] == cs[i - 1] && !used[i - 1]) continue; // 去重
            used[i] = true;
            cur.append(cs[i]);
            best = dfs(cs, used, cur, best);
            cur.deleteCharAt(cur.length() - 1);
            used[i] = false;
        }
        return best;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maximumOddBinaryNumber("010").equals("001"), "2864 例1");
        check(s.maximumOddBinaryNumber("0101").equals("1001"), "2864 例2");

        java.util.Random rnd = new java.util.Random(82);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 2 + rnd.nextInt(6);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) sb.append(rnd.nextBoolean() ? '1' : '0');
            if (sb.indexOf("1") < 0) sb.setCharAt(0, '1');
            String sstr = sb.toString();
            String a = s.maximumOddBinaryNumber(sstr);
            String b = s.maximumOddBinaryNumberBrute(sstr);
            if (!a.equals(b)) {
                allOk = false;
                System.out.println("对拍失败 s=" + sstr + " 贪心=" + a + " DFS=" + b);
                break;
            }
        }
        check(allOk, "2864 随机对拍");
        System.out.println("2864 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。


2865. Beautiful Towers I(美丽塔 I)

题目描述

给定一个包含 n 个整数的数组 heights 表示 n 座连续的塔中砖块的数量。

你的任务是移除一些砖块来形成一个山脉状的塔排列。在这种布置中,

塔高度先是非递减,有一个或多个连续塔达到最大峰值,然后非递增排列。

返回满足山脉状塔排列的方案中,高度和的最大值。

示例 1

  • 输入 :maxHeights = 5,3,4,1,1
  • 输出:13

示例 2

  • 输入 :maxHeights = 6,5,3,9,2,7
  • 输出:22

示例 3

  • 输入 :maxHeights = 3,2,5,5,2,3
  • 输出 :18
    提示:
  • 1 <= n == heights.length <= 10^3
  • 1 <= heights[i] <= 10^9

解题思路

解法一:枚举峰值 + 单调栈前缀后缀

对每个位置 i 作为峰值,左半段 non-decreasing ≤ maxHeightsi,

左段最大和 = 单调栈维护,「以 i 结尾的上升段高度和」。

右半段同理。答案 = max(lefti + righti - maxHeightsi)。

由于 n <= 10^3 也可 O(n^2) 直接扩展,但这里统一用 O(n) 单调栈(同 II)。

时间复杂度 O(n),空间复杂度 O(n)。

解法二:暴力扩展

对每个峰值向两侧扩展并取 min(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:单调栈 DP(主提交版本) ----------
    public long maximumSumOfHeights(List<Integer> maxHeights) {
        int n = maxHeights.size();
        long[] left = new long[n];
        long[] right = new long[n];
        // 左:以 i 为峰值的左半段(含 i)和
        int[] stack = new int[n];
        int top = -1;
        long[] lsum = new long[n]; // lsum[i] = 以 i 结尾的非递减段和
        for (int i = 0; i < n; i++) {
            long h = maxHeights.get(i);
            while (top >= 0 && maxHeights.get(stack[top]) >= h) top--;
            if (top < 0) {
                lsum[i] = h * (i + 1);
            } else {
                int p = stack[top];
                lsum[i] = lsum[p] + h * (i - p);
            }
            stack[++top] = i;
            left[i] = lsum[i];
        }
        // 右:镜像
        top = -1;
        long[] rsum = new long[n];
        for (int i = n - 1; i >= 0; i--) {
            long h = maxHeights.get(i);
            while (top >= 0 && maxHeights.get(stack[top]) >= h) top--;
            if (top < 0) {
                rsum[i] = h * (n - i);
            } else {
                int p = stack[top];
                rsum[i] = rsum[p] + h * (p - i);
            }
            stack[++top] = i;
            right[i] = rsum[i];
        }
        long ans = 0;
        for (int i = 0; i < n; i++) {
            ans = Math.max(ans, left[i] + right[i] - maxHeights.get(i));
        }
        return ans;
    }

    // ---------- 解法二:暴力扩展(对照) ----------
    public long maximumSumOfHeightsBrute(List<Integer> maxHeights) {
        int n = maxHeights.size();
        long ans = 0;
        for (int peak = 0; peak < n; peak++) {
            long sum = 0;
            long cur = maxHeights.get(peak);
            // 向左:逐级取 min(必须保证非递减)
            long limit = cur;
            for (int i = peak; i >= 0; i--) {
                limit = Math.min(limit, maxHeights.get(i));
                sum += limit;
            }
            limit = cur;
            for (int i = peak; i < n; i++) {
                limit = Math.min(limit, maxHeights.get(i));
                sum += limit;
            }
            sum -= cur; // 峰值算了两次
            ans = Math.max(ans, sum);
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maximumSumOfHeights(new ArrayList<>(java.util.Arrays.asList(5, 3, 4, 1, 1))) == 13, "2865 例1");
        check(s.maximumSumOfHeights(new ArrayList<>(java.util.Arrays.asList(6, 5, 3, 9, 2, 7))) == 22, "2865 例2");
        check(s.maximumSumOfHeights(new ArrayList<>(java.util.Arrays.asList(3, 2, 5, 5, 2, 3))) == 18, "2865 例3");

        java.util.Random rnd = new java.util.Random(83);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(50);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(1 + rnd.nextInt(30));
            long x = s.maximumSumOfHeights(a);
            long y = s.maximumSumOfHeightsBrute(a);
            if (x != y) {
                allOk = false;
                System.out.println("对拍失败 maxHeights=" + a + " 单调栈=" + x + " 暴力=" + y);
                break;
            }
        }
        check(allOk, "2865 随机对拍");
        System.out.println("2865 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。


2866. Beautiful Towers II(美丽塔 II)

题目描述

给你一个长度为 n 下标从 0 开始的整数数组 maxHeights。

你的任务是在坐标轴上建 n 座塔。第 i 座塔的下标为 i,高度为 heights[i]。

如果满足以下条件,我们称这些塔是美丽的:

  • 1 <= heights[i] <= maxHeights[i]
  • heights 是一个山脉数组。
    如果存在下标 i 满足:对于所有 0 < j <= i 有 heights[j-1] <= heights[j],
    且对于所有 i <= k < n-1 有 heights[k+1] <= heights[k],那么 heights 是山脉数组。
    返回满足美丽塔要求的方案中,高度和的最大值。

示例 1

  • 输入 :maxHeights = 5,3,4,1,1
  • 输出:13

示例 2

  • 输入 :maxHeights = 6,5,3,9,2,7
  • 输出:22

示例 3

  • 输入 :maxHeights = 3,2,5,5,2,3
  • 输出 :18
    提示:
  • 1 <= n == maxHeights.length <= 10^5
  • 1 <= maxHeights[i] <= 10^9

解题思路

解法一:单调栈前缀/后缀 DP

lefti = 保证 0...i 非递减时,以 i 为峰顶的左半段最大和;

righti = 保证 i...n-1 非递增时,以 i 为峰顶的右半段最大和。

用单调栈存储"前一个更矮的塔"位置 p:

lefti = leftp + maxHeightsi * (i - p)(p 之前高度不超过 hi 已由 p 封顶)。

同理 right。答案 = max(lefti + righti - maxHeightsi)。

时间复杂度 O(n),空间复杂度 O(n)。

解法二:暴力扩展

小规模对每个峰值向两侧扩展取 min(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:单调栈 DP(主提交版本) ----------
    public long maximumSumOfHeights(List<Integer> maxHeights) {
        int n = maxHeights.size();
        long[] left = new long[n];
        long[] right = new long[n];
        int[] st = new int[n];
        int top = -1;
        for (int i = 0; i < n; i++) {
            long h = maxHeights.get(i);
            while (top >= 0 && maxHeights.get(st[top]) >= h) top--;
            if (top < 0) {
                left[i] = h * (i + 1);
            } else {
                int p = st[top];
                left[i] = left[p] + h * (i - p);
            }
            st[++top] = i;
        }
        top = -1;
        for (int i = n - 1; i >= 0; i--) {
            long h = maxHeights.get(i);
            while (top >= 0 && maxHeights.get(st[top]) >= h) top--;
            if (top < 0) {
                right[i] = h * (n - i);
            } else {
                int p = st[top];
                right[i] = right[p] + h * (p - i);
            }
            st[++top] = i;
        }
        long ans = 0;
        for (int i = 0; i < n; i++) {
            ans = Math.max(ans, left[i] + right[i] - maxHeights.get(i));
        }
        return ans;
    }

    // ---------- 解法二:暴力扩展(对照) ----------
    public long maximumSumOfHeightsBrute(List<Integer> maxHeights) {
        int n = maxHeights.size();
        long ans = 0;
        for (int peak = 0; peak < n; peak++) {
            long sum = 0;
            long limit = maxHeights.get(peak);
            for (int i = peak; i >= 0; i--) {
                limit = Math.min(limit, maxHeights.get(i));
                sum += limit;
            }
            limit = maxHeights.get(peak);
            for (int i = peak; i < n; i++) {
                limit = Math.min(limit, maxHeights.get(i));
                sum += limit;
            }
            sum -= maxHeights.get(peak);
            ans = Math.max(ans, sum);
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maximumSumOfHeights(new ArrayList<>(java.util.Arrays.asList(5, 3, 4, 1, 1))) == 13, "2866 例1");
        check(s.maximumSumOfHeights(new ArrayList<>(java.util.Arrays.asList(6, 5, 3, 9, 2, 7))) == 22, "2866 例2");
        check(s.maximumSumOfHeights(new ArrayList<>(java.util.Arrays.asList(3, 2, 5, 5, 2, 3))) == 18, "2866 例3");

        java.util.Random rnd = new java.util.Random(84);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(50);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(1 + rnd.nextInt(30));
            long x = s.maximumSumOfHeights(a);
            long y = s.maximumSumOfHeightsBrute(a);
            if (x != y) {
                allOk = false;
                System.out.println("对拍失败 maxHeights=" + a + " 单调栈=" + x + " 暴力=" + y);
                break;
            }
        }
        check(allOk, "2866 随机对拍");
        System.out.println("2866 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。


2867. Count Valid Paths in a Tree(统计树中的合法路径数目)

题目描述

给你一棵 n 个节点的无向树,节点编号为 1 到 n。

给你整数 n 和长度为 n - 1 的二维整数数组 edges,其中 edges[i] = `ui`, `vi`。

返回树中的合法路径数目。

如果在节点 a 到节点 b 之间恰好有一个节点的编号是质数,那么我们称路径 (a, b) 是合法的。

注意:路径 (a, b) 和路径 (b, a) 视为同一条路径,只计一次。

示例 1

  • 输入 :n = 5, edges = \[1,2,1,3,2,4,2,5]
  • 输出:4

示例 2

  • 输入 :n = 6, edges = \[1,2,1,3,2,4,3,5,3,6]
  • 输出 :6
    提示:
  • 1 <= n <= 10^5
  • edges.length == n - 1
  • 1 <= ui, vi <= n
  • 输入保证 edges 形成一棵合法的树。

解题思路

解法一:以质数节点为枢纽计数

把每条合法路径看成「恰好经过一个质数节点」。枚举每个质数节点 p 作为枢纽,

去掉 p 后,p 的每个邻接分量都是「全非质数连通块」。

若各分量大小为 s1, s2, ..., sm:

  • 一端在分量内、一端是 p:贡献 s1 + s2 + ... = (总非质数节点数)
  • 两个端点分别在两个不同分量:贡献 Σ_{i<j} si*sj。
    总和 = a + b,其中 a = Σsi,b = (Σsi * Σsi - Σsi^2) / 2。
    预处理每个非质数连通块大小(BFS/DFS 跳过质数节点),
    复杂度 O(n)。
    解法二:暴力枚举所有点对路径
    小规模对每个点对 BFS 路径数质数个数(对照)。
    复杂度:
  • 时间复杂度 :O(n^2),BFS/DFS 遍历连通块两两计数
  • 空间复杂度 :O(n),连通块大小数组

代码实现

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

public class Solution {
    // ---------- 解法一:质数枢纽计数(主提交版本) ----------
    public long countPaths(int n, int[][] edges) {
        boolean[] prime = new boolean[n + 1];
        Arrays.fill(prime, true);
        prime[0] = prime[1] = false;
        for (int i = 2; i * i <= n; i++) {
            if (prime[i]) {
                for (int j = i * i; j <= n; j += i) prime[j] = false;
            }
        }
        List<Integer>[] g = new List[n + 1];
        for (int i = 0; i <= n; i++) g[i] = new ArrayList<>();
        for (int[] e : edges) {
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        long ans = 0;
        for (int p = 2; p <= n; p++) {
            if (!prime[p]) continue;
            long sum = 0, sumSq = 0;
            for (int nei : g[p]) {
                if (prime[nei]) continue; // 质数邻接属于其它枢纽,只统计非质数块
                long s = dfsBlock(nei, p, g, prime);
                sum += s;
                sumSq += s * s;
            }
            // 一端是 p:sum 条;两端都在非质数块:(sum^2 - sumSq)/2
            ans += sum + (sum * sum - sumSq) / 2;
        }
        return ans;
    }

    // 从 v 出发(跳过父节点 p、跳过质数节点)统计非质数连通块大小
    private long dfsBlock(int v, int parent, List<Integer>[] g, boolean[] prime) {
        long cnt = 1;
        for (int to : g[v]) {
            if (to == parent || prime[to]) continue;
            cnt += dfsBlock(to, v, g, prime);
        }
        return cnt;
    }

    // ---------- 解法二:暴力点对(对照,小规模) ----------
    public long countPathsBrute(int n, int[][] edges) {
        boolean[] prime = new boolean[n + 1];
        Arrays.fill(prime, true);
        prime[0] = prime[1] = false;
        for (int i = 2; i * i <= n; i++) {
            if (prime[i]) for (int j = i * i; j <= n; j += i) prime[j] = false;
        }
        List<Integer>[] g = new List[n + 1];
        for (int i = 0; i <= n; i++) g[i] = new ArrayList<>();
        for (int[] e : edges) {
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        long ans = 0;
        for (int a = 1; a <= n; a++) {
            for (int b = a + 1; b <= n; b++) {
                // BFS 找路径并统计质数个数
                int[] prev = new int[n + 1];
                Arrays.fill(prev, -1);
                java.util.ArrayDeque<Integer> q = new java.util.ArrayDeque<>();
                q.add(a);
                prev[a] = 0;
                while (!q.isEmpty()) {
                    int x = q.poll();
                    if (x == b) break;
                    for (int to : g[x]) {
                        if (prev[to] == -1) { prev[to] = x; q.add(to); }
                    }
                }
                int primeCnt = 0;
                int cur = b;
                while (cur != 0) {
                    if (prime[cur]) primeCnt++;
                    cur = prev[cur];
                }
                if (primeCnt == 1) ans++;
            }
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.countPaths(5, new int[][]{{1,2},{1,3},{2,4},{2,5}}) == 4, "2867 例1");
        check(s.countPaths(6, new int[][]{{1,2},{1,3},{2,4},{3,5},{3,6}}) == 6, "2867 例2");

        java.util.Random rnd = new java.util.Random(85);
        boolean allOk = true;
        for (int t = 0; t < 100; t++) {
            int n = 2 + rnd.nextInt(9);
            List<int[]> es = new ArrayList<>();
            for (int v = 2; v <= n; v++) {
                int p = 1 + rnd.nextInt(v - 1);
                es.add(new int[]{p, v});
            }
            int[][] edges = es.toArray(new int[0][]);
            long a = s.countPaths(n, edges);
            long b = s.countPathsBrute(n, edges);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 n=" + n + " edges=" + Arrays.deepToString(edges) + " 主解=" + a + " 暴力=" + b);
                break;
            }
        }
        check(allOk, "2867 随机对拍");
        System.out.println("2867 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

  • 时间复杂度 :O(n^2),BFS/DFS 遍历连通块两两计数
  • 空间复杂度 :O(n),连通块大小数组

2868. The Wording Game(单词游戏)

题目描述

Alice 和 Bob 分别拥有一个按字典序排序的字符串数组,分别命名为 a 和 b。

他们正在玩一个单词游戏,遵循以下规则:

  • 每一轮,当前玩家应该从他的列表中选择一个单词,并且选择的单词比上一个单词紧邻大;
    然后轮到另一名玩家。
  • 如果一名玩家在自己的回合中无法选择单词,则输掉比赛。
    Alice 通过选择在字典序最小的单词开始游戏。
    给定 a 和 b,已知两名玩家都按最佳策略玩游戏,如果 Alice 可以获胜则返回 true,否则 false。
    单词 w 比 z 紧邻大:w 字典序大于 z,且 w 的首字母等于 z 的首字母或为 z 首字母后相邻的字母。

示例 1

  • 输入 :a = "`avokado`","`dabar`", b = "`brazil`"
  • 输出 :false

示例 2

  • 输入 :a = "`ananas`","`atlas`","`banana`", b = "`albatros`","`cikla`","`nogomet`"
  • 输出 :true

示例 3

  • 输入 :a = "`hrvatska`","`zastava`", b = "`bijeli`","`galeb`"
  • 输出 :true
    提示:
  • 1 <= a.length, b.length <= 10^5
  • a[i] 和 b[i] 仅包含小写英文字母。
  • a 和 b 按字典序排序。
  • a 和 b 中所有的单词都是不同的。
  • 所有单词长度之和不超过 10^6。

解题思路

解法一:贪心模拟

Alice 首轮必须选自己字典序最小的词。之后每轮当前玩家在自己未被用过的词中,

选「字典序最小且能紧邻大接上」的词;若选不出则输。

因为数组有序且每个词只用一次,维护两个指针从前往后扫;当前玩家在自己列表中

线性找第一个满足条件的(其后若已用过用下标跳过)。

更严谨:用一个「已用尾标」------由于每轮都尽量选最小的可用词,两个玩家的

已用词必然是各自数组的某个前缀,所以只需维护 aUsed/bUsed 前缀计数。

时间复杂度 O(a.length + b.length),空间复杂度 O(1)。

解法二:回溯搜索

小规模时递归枚举所有走法,验证 Alice 是否有必胜策略(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:贪心模拟(主提交版本) ----------
    public boolean canAliceWin(String[] a, String[] b) {
        // Alice 先手:必须选自己最小的词
        String last = a[0];
        int aUsed = 1, bUsed = 0;
        boolean aliceTurn = false; // 下一轮 Bob
        while (true) {
            boolean moved = false;
            if (aliceTurn) {
                for (int i = aUsed; i < a.length; i++) {
                    if (adjacentLarger(a[i], last)) {
                        last = a[i];
                        aUsed = i + 1;
                        moved = true;
                        break;
                    }
                }
            } else {
                for (int i = bUsed; i < b.length; i++) {
                    if (adjacentLarger(b[i], last)) {
                        last = b[i];
                        bUsed = i + 1;
                        moved = true;
                        break;
                    }
                }
            }
            if (!moved) return !aliceTurn; // 当前轮玩家无法出牌 => 对方(下一步的上一轮)赢
            aliceTurn = !aliceTurn;
        }
    }

    // w 是否比 z 紧邻大
    private boolean adjacentLarger(String w, String z) {
        if (w.compareTo(z) <= 0) return false;
        char wc = w.charAt(0), zc = z.charAt(0);
        return wc == zc || wc == zc + 1;
    }

    // ---------- 解法二:回溯搜索(对照,小规模) ----------
    public boolean canAliceWinBrute(String[] a, String[] b) {
        // Alice 第一手强制选自己最小的词
        int[] mask = {0, 0};
        mask[0] |= 1 << 0;
        // 第一个 dfs 是"轮到 Bob 走",返回 Bob 能否赢;Bob 不能赢则 Alice 赢
        return !dfsBrute(a, b, a[0], mask, false, new java.util.HashMap<>());
    }

    // cur 为上一单词,turn=true 表示轮到 Alice
    private boolean dfsBrute(String[] a, String[] b, String cur, int[] mask, boolean turn,
                             java.util.Map<String, Boolean> memo) {
        String key = cur + "|" + mask[0] + "|" + mask[1] + "|" + turn;
        Boolean cached = memo.get(key);
        if (cached != null) return cached;
        String[] arr = turn ? a : b;
        boolean canMove = false;
        for (int i = 0; i < arr.length; i++) {
            if (((mask[turn ? 0 : 1] >> i) & 1) != 0) continue;
            if (adjacentLarger(arr[i], cur)) {
                canMove = true;
                mask[turn ? 0 : 1] |= 1 << i;
                boolean oppWin = dfsBrute(a, b, arr[i], mask, !turn, memo);
                mask[turn ? 0 : 1] &= ~(1 << i);
                if (!oppWin) {
                    memo.put(key, true);
                    return true;
                }
            }
        }
        // 无法出牌 => 当前玩家输
        memo.put(key, false);
        return false;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(!s.canAliceWin(new String[]{"avokado", "dabar"}, new String[]{"brazil"}), "2868 例1");
        check(s.canAliceWin(new String[]{"ananas", "atlas", "banana"}, new String[]{"albatros", "cikla", "nogomet"}), "2868 例2");
        check(s.canAliceWin(new String[]{"hrvatska", "zastava"}, new String[]{"bijeli", "galeb"}), "2868 例3");

        // 用对拍:生成小规模随机按字典序排序的词表
        java.util.Random rnd = new java.util.Random(86);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int na = 1 + rnd.nextInt(4), nb = 1 + rnd.nextInt(4);
            java.util.TreeSet<String> aa = new java.util.TreeSet<>();
            java.util.TreeSet<String> bb = new java.util.TreeSet<>();
            while (aa.size() < na) aa.add(randWord(rnd));
            while (bb.size() < nb) bb.add(randWord(rnd));
            String[] a = aa.toArray(new String[0]);
            String[] b = bb.toArray(new String[0]);
            boolean m1 = s.canAliceWin(a, b);
            boolean m2 = s.canAliceWinBrute(a, b);
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 a=" + java.util.Arrays.toString(a) + " b=" + java.util.Arrays.toString(b) + " 贪心=" + m1 + " 搜索=" + m2);
                break;
            }
        }
        check(allOk, "2868 随机对拍");
        System.out.println("2868 all tests done");
    }

    static String randWord(java.util.Random rnd) {
        int len = 1 + rnd.nextInt(4);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; i++) sb.append((char) ('a' + rnd.nextInt(5)));
        return sb.toString();
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(a.length + b.length),空间复杂度 O(1)。


2869. Minimum Operations to Collect Elements(收集元素的最少操作次数)

题目描述

给你一个正整数数组 nums 和一个整数 k。

一次操作中,你可以将数组的最后一个元素删除,将该元素添加到一个集合中。

返回收集元素 1, 2, ..., k 需要的最少操作次数。

示例 1

  • 输入 :nums = 3,1,5,4,2, k = 2
  • 输出:4

示例 2

  • 输入 :nums = 3,1,5,4,2, k = 5
  • 输出:5

示例 3

  • 输入 :nums = 3,2,5,3,1, k = 3
  • 输出 :4
    提示:
  • 1 <= nums.length <= 50
  • 1 <= nums[i] <= nums.length
  • 1 <= k <= nums.length
  • 输入保证你可以收集到元素 1, 2, ..., k。

解题思路

解法一:从后向前扫描

从数组末尾开始往左扫,遇到值 <= k 且未收集过的元素就收集,

直到 1...k 全部收集,返回扫描过的元素个数(操作次数)。

时间复杂度 O(n),空间复杂度 O(k)。

解法二:模拟

直接模拟每次删除末尾(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:逆序扫描(主提交版本) ----------
    public int minOperations(List<Integer> nums, int k) {
        Set<Integer> collected = new HashSet<>();
        int n = nums.size();
        for (int i = n - 1; i >= 0; i--) {
            int v = nums.get(i);
            if (v <= k) collected.add(v);
            if (collected.size() == k) return n - i;
        }
        return n; // 理论不会到这
    }

    // ---------- 解法二:List 模拟(对照) ----------
    public int minOperationsBrute(List<Integer> nums, int k) {
        List<Integer> a = new ArrayList<>(nums);
        Set<Integer> collected = new HashSet<>();
        int ops = 0;
        while (collected.size() < k) {
            int v = a.remove(a.size() - 1);
            if (v <= k) collected.add(v);
            ops++;
        }
        return ops;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.minOperations(new ArrayList<>(java.util.Arrays.asList(3, 1, 5, 4, 2)), 2) == 4, "2869 例1");
        check(s.minOperations(new ArrayList<>(java.util.Arrays.asList(3, 1, 5, 4, 2)), 5) == 5, "2869 例2");
        check(s.minOperations(new ArrayList<>(java.util.Arrays.asList(3, 2, 5, 3, 1)), 3) == 4, "2869 例3");

        java.util.Random rnd = new java.util.Random(87);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(20);
            int k = 1 + rnd.nextInt(Math.min(5, n) + 1);
            List<Integer> a = new ArrayList<>();
            // 先放入 1..k 保证可收集,再补足到 n
            for (int x = 1; x <= k; x++) a.add(x);
            while (a.size() < n) a.add(1 + rnd.nextInt(n));
            int m1 = s.minOperations(new ArrayList<>(a), k);
            int m2 = s.minOperationsBrute(new ArrayList<>(a), k);
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 nums=" + a + " k=" + k + " 主解=" + m1 + " 模拟=" + m2);
                break;
            }
        }
        check(allOk, "2869 随机对拍");
        System.out.println("2869 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(k)。


2870. Minimum Number of Operations to Make Array Empty(使数组为空的最少操作次数)

题目描述

给你一个下标从 0 开始的正整数数组 nums。

你可以对数组执行以下两种操作任意次:

  • 从数组中选择两个值相等的元素,并将它们从数组中删除。
  • 从数组中选择三个值相等的元素,并将它们从数组中删除。
    返回使数组为空的最少操作次数,如果无法达成请返回 -1。

示例 1

  • 输入 :nums = 2,3,3,2,2,4,2,3,4
  • 输出:4

示例 2

  • 输入 :nums = 2,1,2,2,3,3
  • 输出 :-1
    提示:
  • 2 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^6

解题思路

解法一:频次贪心

对每个值统计出现次数 c。每次操作删除 2 个或 3 个同值元素。

若 c == 1 无法清空;c >= 2 时最少次数 = (c + 2) / 3 的变形:

优先用 3,余数 1 时借一个 3 拆成 2+2(c=4 -> 两次 2),余数 2 时补一次 2。

公式:ans = (c / 3) + (c % 3 == 0 ? 0 : 1),但 c=1 时 -1,c=4 特判为 2。

更统一:dp 或直接:

if c == 1 -> -1

else ans += (c + 2) / 3 // ceil(c/3) 对 c>=2 均成立?c=2 -> 1, c=3 -> 1, c=4 -> 2, c=5 -> 2, c=6 -> 2, c=7 -> 3

验证:2=1✓ 3=1✓ 4=2✓ 5=2✓ 6=2✓ 7=3✓ 8=3✓(8=3+3+2)

所以 ans = ceil(c/3) = (c+2)/3。

时间复杂度 O(n),空间复杂度 O(n)。

解法二:BFS/DP 小规模验证

对每个 c(小范围)用 DP 求最少操作数(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:频次 + 贪心(主提交版本) ----------
    public int minOperations(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; ) {
            int j = i;
            while (j < n && nums[j] == nums[i]) j++;
            int c = j - i;
            if (c == 1) return -1;
            ans += (c + 2) / 3;
            i = j;
        }
        return ans;
    }

    // ---------- 解法二:DP 求最少操作(对照) ----------
    public int minOperationsBrute(int[] nums) {
        java.util.Map<Integer, Integer> cnt = new java.util.HashMap<>();
        for (int v : nums) cnt.merge(v, 1, Integer::sum);
        int ans = 0;
        for (int c : cnt.values()) {
            int dp = dpMin(c);
            if (dp < 0) return -1;
            ans += dp;
        }
        return ans;
    }

    // 对频次 c 求最少操作(2 或 3),记忆化
    private int dpMin(int c) {
        int[] dp = new int[c + 1];
        java.util.Arrays.fill(dp, Integer.MAX_VALUE / 2);
        dp[0] = 0;
        for (int i = 2; i <= c; i++) {
            if (i >= 2) dp[i] = Math.min(dp[i], dp[i - 2] + 1);
            if (i >= 3) dp[i] = Math.min(dp[i], dp[i - 3] + 1);
        }
        return dp[c] >= Integer.MAX_VALUE / 4 ? -1 : dp[c];
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.minOperations(new int[]{2, 3, 3, 2, 2, 4, 2, 3, 4}) == 4, "2870 例1");
        check(s.minOperations(new int[]{2, 1, 2, 2, 3, 3}) == -1, "2870 例2");

        java.util.Random rnd = new java.util.Random(88);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            int n = 2 + rnd.nextInt(12);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(6);
            int m1 = s.minOperations(a.clone());
            int m2 = s.minOperationsBrute(a.clone());
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 nums=" + Arrays.toString(a) + " 贪心=" + m1 + " DP=" + m2);
                break;
            }
        }
        check(allOk, "2870 随机对拍");
        System.out.println("2870 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。


2871. Split Array Into Maximum Number of Subarrays(将数组分割成最多数目的子数组)

题目描述

给你一个只包含非负整数的数组 nums。

定义子数组 nums`l`...`r`(l <= r)的分数为 nums[l] AND ... AND nums[r]。

请你将数组分割成一个或者更多子数组,满足:

  • 每个元素都只属于一个子数组。
  • 子数组分数之和尽可能小。
    在满足以上要求的条件下,返回最多可以得到多少个子数组。

示例 1

  • 输入 :nums = 1,0,2,0,1,2
  • 输出:3

示例 2

  • 输入 :nums = 5,7,1,3
  • 输出 :1
    提示:
  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^6

解题思路

解法一:贪心

记 totalAnd = nums0 & ... & numsn-1。

  • 若 totalAnd == 0:可以切成 numZeros 个单位块,使每块 AND 为 0,
    答案 = 值为 0 的元素个数(被 0 分割);更精确:每个 0 单独一块,
    非 0 元素并入相邻块或不并。官方结论:答案为 0 的个数。
    理由:每块分数 >= 0(AND 非负),总分最小为 0;每块为 0 则需要块内含一个 0
    或整段 AND 为 0。把每个 0 单独成块(0 的个数 = cnt0),多余的 1 并入相邻块
    不增加块数且保持 AND=0(非 0 元素与 0 AND 为 0)。可能 cnt0=0 但全数组 AND=0?
    此时 arr 无 0 但 AND 为 0,至少需要 1 块;最多能切多少?只要整块 AND=0
    切成两块各 AND=0 需要每个子块 AND=0...... 不一定。官方解法:
    若整个数组 AND 不为 0,答案为 1;否则答案为 0 的个数(至少 1)。
  • 若 totalAnd != 0:无法分成多块使总和 < totalAnd(每块 AND >= totalAnd ?不一定),
    官方结论:答案为 1。
    实现:统计 0 的个数 cnt0,全数组 AND 为 totalAnd:
    if totalAnd != 0 return 1;
    否则 return max(1, cnt0)。
    时间复杂度 O(n),空间复杂度 O(1)。
    解法二:暴力枚举全部分割
    小规模枚举所有分割方式计算最小总分下的最大块数(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:贪心(主提交版本) ----------
    public int maxSubarrays(int[] nums) {
        int totalAnd = -1; // 全 1 掩码
        for (int v : nums) totalAnd &= v;
        if (totalAnd != 0) return 1;
        // 整数组 AND 为 0:贪心从左到右,累积 AND 一旦为 0 就切一块
        // 若最后一段累积 AND 不为 0,则并入前一块(不影响其 AND=0)
        int ans = 0;
        int cur = -1;
        for (int v : nums) {
            cur &= v;
            if (cur == 0) {
                ans++;
                cur = -1;
            }
        }
        return ans;
    }

    // ---------- 解法二:暴力分割(对照,小规模) ----------
    public int maxSubarraysBrute(int[] nums) {
        int n = nums.length;
        int minScore = Integer.MAX_VALUE;
        // 先求整段 AND 作为最小总和下界参考,正解:总分数最小 = 每块 AND 之和
        // 枚举分割点组合
        for (int mask = 0; mask < (1 << (n - 1)); mask++) {
            List<Integer> ands = new ArrayList<>();
            int cur = nums[0];
            for (int i = 0; i + 1 < n; i++) {
                cur &= nums[i];
                if (((mask >> i) & 1) != 0) {
                    ands.add(cur);
                    cur = nums[i + 1];
                } else {
                    cur &= nums[i + 1];
                }
            }
            ands.add(cur);
            int sum = 0;
            for (int a : ands) sum += a;
            minScore = Math.min(minScore, sum);
        }
        // 再找满足最小总分的最大块数
        int best = 1;
        for (int mask = 0; mask < (1 << (n - 1)); mask++) {
            List<Integer> ands = new ArrayList<>();
            int cur = nums[0];
            for (int i = 0; i + 1 < n; i++) {
                cur &= nums[i];
                if (((mask >> i) & 1) != 0) {
                    ands.add(cur);
                    cur = nums[i + 1];
                } else {
                    cur &= nums[i + 1];
                }
            }
            ands.add(cur);
            int sum = 0;
            for (int a : ands) sum += a;
            if (sum == minScore) best = Math.max(best, ands.size());
        }
        return best;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maxSubarrays(new int[]{1, 0, 2, 0, 1, 2}) == 3, "2871 例1");
        check(s.maxSubarrays(new int[]{5, 7, 1, 3}) == 1, "2871 例2");
        check(s.maxSubarrays(new int[]{0, 0}) == 2, "2871 全0");

        java.util.Random rnd = new java.util.Random(89);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(9);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = rnd.nextInt(8);
            int m1 = s.maxSubarrays(a.clone());
            int m2 = s.maxSubarraysBrute(a.clone());
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 nums=" + java.util.Arrays.toString(a) + " 贪心=" + m1 + " 暴力=" + m2);
                break;
            }
        }
        check(allOk, "2871 随机对拍");
        System.out.println("2871 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

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


2872. Maximum Number of K-Divisible Components(可以被 K 整除连通块的最大数目)

题目描述

给你一棵 n 个节点的无向树,节点编号为 0 到 n - 1。

给你整数 n 和长度为 n - 1 的二维整数数组 edges,其中 edges[i] = `ai`, `bi`。

同时给你下标从 0 开始长度为 n 的整数数组 values,其中 values[i] 是节点 i 的值。再给整数 k。

你可以从树中删除一些边,也可以一条边也不删,得到若干连通块。

一个连通块的值定义为连通块中所有节点值之和。

如果所有连通块的值都可以被 k 整除,那么这是一个合法分割。

返回所有合法分割中,连通块数目的最大值。

示例 1

  • 输入 :n = 5, edges = \[0,2,1,2,1,3,2,4], values = 1,8,1,4,4, k = 6
  • 输出:2

示例 2

  • 输入 :n = 7, edges = \[0,1,0,2,1,3,1,4,2,5,2,6], values = 3,0,6,1,5,2,1, k = 3
  • 输出 :3
    提示:
  • 1 <= n <= 3 * 10^4
  • edges.length == n - 1
  • 0 <= ai, bi < n
  • values.length == n
  • 0 <= values[i] <= 10^9
  • 1 <= k <= 10^9
  • values 之和可以被 k 整除。

解题思路

解法一:DFS 自底向上

从根 DFS,后序处理:(子树和 % k) 传给父节点。

若某子树的子树和 % k == 0,则可以断开该边(子树独立成块),计数 +1;

否则把余数合并给父节点。

根节点处理完后自动成为最后一块(其累加和必为 0 mod k)。

实现细节:dfs 返回子树和 % k(带上本节点);若返回 0 则计数 +1。

注意根也应在计数中:根返回 0 时同样 +1(哨兵处理)。

时间复杂度 O(n),空间复杂度 O(n)。

解法二:暴力枚举删边子集

小规模枚举删除哪些边,检查每个连通块和 % k == 0(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:DFS 自底向上(主提交版本) ----------
    public int maxKDivisibleComponents(int n, int[][] edges, int[] values, int k) {
        List<Integer>[] g = new List[n];
        for (int i = 0; i < n; i++) g[i] = new ArrayList<>();
        for (int[] e : edges) {
            g[e[0]].add(e[1]);
            g[e[1]].add(e[0]);
        }
        int[] cnt = new int[1];
        dfs(0, -1, g, values, k, cnt);
        // 根所在连通块:其和必被 k 整除(题目保证总和不被破坏),需单独计数
        cnt[0]++;
        return cnt[0];
    }

    private long dfs(int v, int p, List<Integer>[] g, int[] values, int k, int[] cnt) {
        long sum = values[v];
        for (int to : g[v]) {
            if (to == p) continue;
            long sub = dfs(to, v, g, values, k, cnt);
            if (sub % k == 0) cnt[0]++; // 子树可独立成块
            else sum += sub;
        }
        return sum % k == 0 ? 0 : sum;
    }

    // ---------- 解法二:暴力删边(对照,小规模) ----------
    public int maxKDivisibleComponentsBrute(int n, int[][] edges, int[] values, int k) {
        int m = edges.length;
        int best = 1;
        for (int mask = 0; mask < (1 << m); mask++) {
            // 并查集构建连通块
            int[] parent = new int[n];
            for (int i = 0; i < n; i++) parent[i] = i;
            for (int i = 0; i < m; i++) {
                if (((mask >> i) & 1) == 0) continue; // 未删除的边
                union(parent, edges[i][0], edges[i][1]);
            }
            long[] sum = new long[n];
            for (int i = 0; i < n; i++) sum[find(parent, i)] += values[i];
            boolean ok = true;
            int blocks = 0;
            for (int i = 0; i < n; i++) {
                if (parent[i] == i) {
                    blocks++;
                    if (sum[i] % k != 0) { ok = false; break; }
                }
            }
            if (ok) best = Math.max(best, blocks);
        }
        return best;
    }

    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 s = new Solution();
        check(s.maxKDivisibleComponents(5, new int[][]{{0,2},{1,2},{1,3},{2,4}}, new int[]{1,8,1,4,4}, 6) == 2, "2872 例1");
        check(s.maxKDivisibleComponents(7, new int[][]{{0,1},{0,2},{1,3},{1,4},{2,5},{2,6}}, new int[]{3,0,6,1,5,2,1}, 3) == 3, "2872 例2");

        java.util.Random rnd = new java.util.Random(90);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int n = 2 + rnd.nextInt(8);
            List<int[]> es = new ArrayList<>();
            for (int v = 1; v < n; v++) es.add(new int[]{rnd.nextInt(v), v});
            int[][] edges = es.toArray(new int[0][]);
            int[] values = new int[n];
            long total = 0;
            for (int i = 0; i < n; i++) { values[i] = rnd.nextInt(10); total += values[i]; }
            int k = 1 + rnd.nextInt(8);
            // 保证总和可被 k 整除(题目约束),否则 DFS 根块不合法
            long need = (k - total % k) % k;
            values[0] += (int) need;
            int a = s.maxKDivisibleComponents(n, edges, values, k);
            int b = s.maxKDivisibleComponentsBrute(n, edges, values, k);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 n=" + n + " edges=" + Arrays.deepToString(edges) + " values=" + Arrays.toString(values) + " k=" + k + " DFS=" + a + " 暴力=" + b);
                break;
            }
        }
        check(allOk, "2872 随机对拍");
        System.out.println("2872 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。


2873. Maximum Value of an Ordered Triplet I(有序三元组中的最大值 I)

题目描述

给你一个下标从 0 开始的整数数组 nums。

从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。

如果所有满足条件的三元组的值都是负数,则返回 0。

下标三元组 (i, j, k) 的值等于 (nums[i] - nums[j]) * nums[k]。

示例 1

  • 输入 :nums = 12,6,1,2,7
  • 输出:77

示例 2

  • 输入 :nums = 1,10,3,4,19
  • 输出:133

示例 3

  • 输入 :nums = 1,2,3
  • 输出 :0
    提示:
  • 3 <= nums.length <= 100
  • 1 <= nums[i] <= 10^6

解题思路

解法一:枚举 j 两端最优

对每个 j,值 = (max(nums0...j-1) - numsj) * max(numsj+1...n-1)。

三者都为正才可能 > 0,负数结果统一为 0。

时间复杂度 O(n),空间复杂度 O(n)。

解法二:暴力三重循环

小规模直接枚举(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:前后缀最值(主提交版本) ----------
    public long maximumTripletValue(int[] nums) {
        int n = nums.length;
        long ans = 0;
        long maxLeft = nums[0];
        for (int j = 1; j < n - 1; j++) {
            maxLeft = Math.max(maxLeft, nums[j - 1]);
            long left = maxLeft - nums[j];
            if (left <= 0) continue;
            for (int kk = j + 1; kk < n; kk++) {
                ans = Math.max(ans, left * nums[kk]);
            }
        }
        return ans;
    }

    // ---------- 解法二:暴力(对照) ----------
    public long maximumTripletValueBrute(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++) {
                    ans = Math.max(ans, (long) (nums[i] - nums[j]) * nums[k]);
                }
            }
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maximumTripletValue(new int[]{12, 6, 1, 2, 7}) == 77, "2873 例1");
        check(s.maximumTripletValue(new int[]{1, 10, 3, 4, 19}) == 133, "2873 例2");
        check(s.maximumTripletValue(new int[]{1, 2, 3}) == 0, "2873 例3");

        java.util.Random rnd = new java.util.Random(91);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 3 + rnd.nextInt(60);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(100);
            long x = s.maximumTripletValue(a);
            long y = s.maximumTripletValueBrute(a);
            if (x != y) {
                allOk = false;
                System.out.println("对拍失败 nums=" + Arrays.toString(a) + " 前后缀=" + x + " 暴力=" + y);
                break;
            }
        }
        check(allOk, "2873 随机对拍");
        System.out.println("2873 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。


2874. Maximum Value of an Ordered Triplet II(有序三元组中的最大值 II)

题目描述

给你一个下标从 0 开始的整数数组 nums。

从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。

如果所有满足条件的三元组的值都是负数,则返回 0。

下标三元组 (i, j, k) 的值等于 (nums[i] - nums[j]) * nums[k]。

示例 1

  • 输入 :nums = 12,6,1,2,7
  • 输出:77

示例 2

  • 输入 :nums = 1,10,3,4,19
  • 输出:133

示例 3

  • 输入 :nums = 1,2,3
  • 输出 :0
    提示:
  • 3 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^6

解题思路

解法一:动态维护两个最值

对每个 k 作为第三项:答案 = max(numsi - numsj) * numsk(i < j < k)。

维护两个变量:

  • maxDiff = 已扫过位置中最大的 numsi - numsj(i < j);
  • maxI = 已扫过的最大的 numsi。
    遍历 k:先更新 ans = max(ans, maxDiff * numsk),
    再用 numsk 更新 maxDiff(作为新的 j 的候选:maxI - numsk)和 maxI。
    注意顺序:必须先算 ans(k 未加入前),再更新状态。
    时间复杂度 O(n),空间复杂度 O(1)。
    解法二:暴力三重循环
    小规模直接枚举(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:动态维护(主提交版本) ----------
    public long maximumTripletValue(int[] nums) {
        int n = nums.length;
        long ans = 0;
        long maxDiff = Long.MIN_VALUE; // 最大的 nums[i] - nums[j]
        long maxI = nums[0];
        for (int k = 1; k < n - 1; k++) {
            maxDiff = Math.max(maxDiff, maxI - nums[k]); // k 作为 j
            ans = Math.max(ans, maxDiff * nums[k + 1] == 0 ? 0 : 0); // 占位
        }
        // 上面写法有误,重写:
        ans = 0;
        maxDiff = Long.MIN_VALUE;
        maxI = nums[0];
        for (int j = 1; j < n; j++) {
            // j 作为中间点:最终答案用到 k > j
            if (j + 1 < n) {
                maxDiff = Math.max(maxDiff, maxI - nums[j]);
                ans = Math.max(ans, maxDiff * nums[j + 1] == 0 ? 0 : maxDiff * nums[j + 1]);
            }
            maxI = Math.max(maxI, nums[j]);
        }
        return ans < 0 ? 0 : ans;
    }

    // ---------- 解法二:暴力(对照) ----------
    public long maximumTripletValueBrute(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++) {
                    ans = Math.max(ans, (long) (nums[i] - nums[j]) * nums[k]);
                }
            }
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.maximumTripletValue(new int[]{12, 6, 1, 2, 7}) == 77, "2874 例1");
        check(s.maximumTripletValue(new int[]{1, 10, 3, 4, 19}) == 133, "2874 例2");
        check(s.maximumTripletValue(new int[]{1, 2, 3}) == 0, "2874 例3");

        java.util.Random rnd = new java.util.Random(92);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 3 + rnd.nextInt(60);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(100);
            long x = s.maximumTripletValue(a);
            long y = s.maximumTripletValueBrute(a);
            if (x != y) {
                allOk = false;
                System.out.println("对拍失败 nums=" + Arrays.toString(a) + " 动态=" + x + " 暴力=" + y);
                break;
            }
        }
        check(allOk, "2874 随机对拍");
        System.out.println("2874 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

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


2875. Minimum Size Subarray in Infinite Array(无限数组的最短子数组)

题目描述

给你一个下标从 0 开始的数组 nums 和一个整数 target。

下标从 0 开始的数组 infinite_nums 是通过无限地将 nums 的元素追加到自己之后生成的。

从 infinite_nums 中找出满足元素和等于 target 的最短子数组,返回其长度。

如果不存在满足条件的子数组,返回 -1。

示例 1

  • 输入 :nums = 1,2,3, target = 5
  • 输出:2

示例 2

  • 输入 :nums = 1,1,1,2,3, target = 4
  • 输出:2

示例 3

  • 输入 :nums = 2,4,6,8, target = 3
  • 输出 :-1
    提示:
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5
  • 1 <= target <= 10^9

解题思路

解法一:周期 + 滑动窗口

设 sum = nums 总和,target 可能需要多个完整周期。

若 target >= 2*sum,则可先减掉 (target/sum - 1) 个完整周期

(每周期贡献 sum,周期数越多数组越长,最优解尽量少用完整周期?

注意:完整周期在中间,头尾是 nums 的子串拼接,所以完整周期数

= target / sum - 1 是最少可能,因为头尾至少各缺一点或多一点。

具体:full = target / sum - 1(若 target / sum >= 1),

剩余 rem = target - full * sum,在 nums+nums 中找和为 rem 的最短子数组,

答案 = full * n + len。

但注意 rem 可能为 0(target 恰好是 sum 的倍数):直接返回 target/sum * n。

边界:rem 必须是 nums+nums 中某段和;用前缀和 + 哈希记录 (pref - rem) 的最早位置。

由于窗口跨越两个数组即可覆盖所有循环起点,用 nums 重复 2 次足够。

复杂度 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 int minSizeSubarray(int[] nums, int target) {
        int n = nums.length;
        long sum = 0;
        for (int v : nums) sum += v;
        long full = target / sum;
        long rem = target - full * sum;
        if (rem == 0) return (int) (full * n);
        // 在 nums+nums 中找和为 rem 的最短子数组(可以有 rem 跨周期的情况)
        // 注意 full 至少为 0;rem 段理论上不会超过 2 个周期。
        long[] pref = new long[2 * n + 1];
        for (int i = 0; i < 2 * n; i++) pref[i + 1] = pref[i] + nums[i % n];
        Map<Long, Integer> firstPos = new HashMap<>();
        int best = Integer.MAX_VALUE;
        for (int i = 0; i <= 2 * n; i++) {
            // 找起点:pref[i] - rem
            long need = pref[i] - rem;
            Integer pos = firstPos.get(need);
            if (pos != null) best = Math.min(best, i - pos);
            firstPos.putIfAbsent(pref[i], i);
        }
        if (best == Integer.MAX_VALUE) return -1;
        long ans = full * n + best;
        return ans > Integer.MAX_VALUE ? -1 : (int) ans;
    }

    // ---------- 解法二:滑动窗口模拟(对照,小规模) ----------
    public int minSizeSubarrayBrute(int[] nums, int target) {
        int n = nums.length;
        int total = 0;
        for (int v : nums) total += v;
        // 生成足够长的数组:最多需要 full+2 个周期
        int needCycles = target / total + 3;
        List<Integer> arr = new ArrayList<>();
        for (int c = 0; c < needCycles; c++) {
            for (int v : nums) arr.add(v);
        }
        int m = arr.size();
        int best = Integer.MAX_VALUE;
        int l = 0;
        long cur = 0;
        for (int r = 0; r < m; r++) {
            cur += arr.get(r);
            while (cur > target && l <= r) {
                cur -= arr.get(l);
                l++;
            }
            if (cur == target) best = Math.min(best, r - l + 1);
            while (cur >= target && l <= r) {
                if (cur == target) best = Math.min(best, r - l + 1);
                cur -= arr.get(l);
                l++;
            }
        }
        return best == Integer.MAX_VALUE ? -1 : best;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.minSizeSubarray(new int[]{1, 2, 3}, 5) == 2, "2875 例1");
        check(s.minSizeSubarray(new int[]{1, 1, 1, 2, 3}, 4) == 2, "2875 例2");
        check(s.minSizeSubarray(new int[]{2, 4, 6, 8}, 3) == -1, "2875 例3");
        check(s.minSizeSubarray(new int[]{1, 2}, 3) == 2, "2875 sum 倍数");

        java.util.Random rnd = new java.util.Random(93);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(8);
            int[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = 1 + rnd.nextInt(10);
            int target = 1 + rnd.nextInt(60);
            int m1 = s.minSizeSubarray(a, target);
            int m2 = s.minSizeSubarrayBrute(a, target);
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 nums=" + java.util.Arrays.toString(a) + " target=" + target + " 周期=" + m1 + " 滑窗=" + m2);
                break;
            }
        }
        check(allOk, "2875 随机对拍");
        System.out.println("2875 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

复杂度 O(n),空间 O(n)。


2876. Count Visited Nodes in a Directed Graph(有向图访问计数)

题目描述

现有一个有向图,其中包含 n 个节点,节点编号从 0 到 n - 1。

该图还包含了 n 条有向边。给你下标从 0 开始的数组 edges,

其中 edges[i] 表示存在一条从节点 i 到节点 edges[i] 的边。

想象在图上发生以下过程:

  • 你从节点 x 开始,通过边访问其他节点,直到你在过程中再次访问到之前已经访问过的节点。
    返回数组 answer,其中 answer[i] 表示如果从节点 i 开始执行该过程,
    你可以访问到的不同节点数。

示例 1

  • 输入 :edges = 1,2,0,0
  • 输出:3,3,3,4

示例 2

  • 输入 :edges = 1,2,3,4,0
  • 输出 :5,5,5,5,5
    提示:
  • n == edges.length
  • 2 <= n <= 10^5
  • 0 <= edges[i] <= n - 1
  • edges[i] != i

解题思路

解法一:基环树分解

每个节点出度为 1,图由若干「基环树」组成(每个连通分量恰有一个环)。

从每个节点出发最终都会进入环。

处理:

  • 用拓扑排序(入度)剔除环外树枝,剩下的就是环上的节点;
  • 对每个环,环上每个节点 answer = 环长;
  • 树枝节点 answer = 1 + answer子节点(自底向上 DP)。
    实现:先拓扑剥枝记录 order,反向(从环向叶子)DP。
    时间复杂度 O(n),空间复杂度 O(n)。
    解法二:逐点模拟
    小规模每个节点沿边走到重复(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:基环树 + 拓扑剥枝(主提交版本) ----------
    public int[] countVisitedNodes(List<Integer> edges) {
        int n = edges.size();
        int[] out = new int[n];
        int[] indeg = new int[n];
        for (int i = 0; i < n; i++) {
            out[i] = edges.get(i);
            indeg[out[i]]++;
        }
        // 拓扑剥枝:入度为 0 的节点是树枝
        int[] q = new int[n];
        int head = 0, tail = 0;
        for (int i = 0; i < n; i++) if (indeg[i] == 0) q[tail++] = i;
        boolean[] removed = new boolean[n];
        while (head < tail) {
            int v = q[head++];
            removed[v] = true;
            int to = out[v];
            if (--indeg[to] == 0) q[tail++] = to;
        }
        int[] ans = new int[n];
        boolean[] onCycle = new boolean[n];
        for (int i = 0; i < n; i++) if (!removed[i]) onCycle[i] = true;
        // 处理每个环
        for (int i = 0; i < n; i++) {
            if (onCycle[i] && ans[i] == 0) {
                // 找环长
                int cur = i;
                List<Integer> cycle = new ArrayList<>();
                while (!onCycle[cur] || cycle.isEmpty() || cur != i) {
                    if (!onCycle[cur] || cycle.contains(cur)) break;
                    cycle.add(cur);
                    cur = out[cur];
                }
                // 上面循环复杂化:直接用 visited 数组
                cycle.clear();
                int x = i;
                while (true) {
                    if (ans[x] != 0) break;
                    cycle.add(x);
                    x = out[x];
                    if (cycle.size() > 1 && x == i) break;
                }
                int len = cycle.size();
                for (int v : cycle) ans[v] = len;
            }
        }
        // 树枝节点:从环向外逆拓扑填充
        // 逆序处理拓扑序列(从叶子往回)
        for (int idx = tail - 1; idx >= 0; idx--) {
            int v = q[idx]; // 树枝节点
            ans[v] = ans[out[v]] + 1;
        }
        return ans;
    }

    // ---------- 解法二:逐点模拟(对照,小规模) ----------
    public int[] countVisitedNodesBrute(List<Integer> edges) {
        int n = edges.size();
        int[] ans = new int[n];
        for (int s = 0; s < n; s++) {
            boolean[] vis = new boolean[n];
            int cur = s;
            while (!vis[cur]) {
                vis[cur] = true;
                cur = edges.get(cur);
            }
            int cnt = 0;
            for (boolean v : vis) if (v) cnt++;
            ans[s] = cnt;
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(java.util.Arrays.equals(s.countVisitedNodes(new ArrayList<>(java.util.Arrays.asList(1, 2, 0, 0))), new int[]{3, 3, 3, 4}), "2876 例1");
        check(java.util.Arrays.equals(s.countVisitedNodes(new ArrayList<>(java.util.Arrays.asList(1, 2, 3, 4, 0))), new int[]{5, 5, 5, 5, 5}), "2876 例2");

        java.util.Random rnd = new java.util.Random(94);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 2 + rnd.nextInt(10);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                int to = rnd.nextInt(n);
                while (to == i) to = rnd.nextInt(n);
                a.add(to);
            }
            int[] m1 = s.countVisitedNodes(new ArrayList<>(a));
            int[] m2 = s.countVisitedNodesBrute(new ArrayList<>(a));
            if (!java.util.Arrays.equals(m1, m2)) {
                allOk = false;
                System.out.println("对拍失败 edges=" + a + " 基环=" + java.util.Arrays.toString(m1) + " 模拟=" + java.util.Arrays.toString(m2));
                break;
            }
        }
        check(allOk, "2876 随机对拍");
        System.out.println("2876 all tests done");
    }

    static void check(boolean ok, String name) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
    }
}

复杂度分析

时间复杂度 O(n),空间复杂度 O(n)。


2877. Create a DataFrame from List(从表中创建 DataFrame)

题目类型:Python

题目描述

编写一个解决方案,基于名为 student_data 的二维列表 **创建 **一个 DataFrame 。这个二维列表包含一些学生的 ID 和年龄信息。

DataFrame 应该有两列, student_id 和 age,并且与原始二维列表的顺序相同。

返回结果格式如下示例所示。

示例 1:**

复制代码
**输入:
**student_data:**
**`[
  [1, 15],
  [2, 11],
  [3, 11],
  [4, 20]
]`
输出:
+------------+-----+
| student_id | age |
+------------+-----+
| 1          | 15  |
| 2          | 11  |
| 3          | 11  |
| 4          | 20  |
+------------+-----+
解释:
基于 student_data 创建了一个 DataFrame,包含 student_id 和 age 两列。

解题思路

解法一:pandas.DataFrame 直接构造(主提交版本)

  • pandas.DataFrame(数据, columns=[列名...]) 可直接把二维列表转为 DataFrame;
  • 列顺序与 columns 参数一致,即 student_id、age,与原始列表行序相同。

解法二:先建字典再转 DataFrame(对照)

  • 先按列提取 student_id 与 age 两个列表,再传字典构造,结果等价但代码更长。

代码实现

python 复制代码
import pandas as pd

def createDataframe(student_data: List[List[int]]) -> pd.DataFrame:
    return pd.DataFrame(student_data, columns=['student_id', 'age'])

复杂度分析

  • 时间复杂度 :O(n),n 为 DataFrame 行数,单次内建操作。
  • 空间复杂度 :O(n),返回结果表需要存储数据。

2878. Get the Size of a DataFrame(获取 DataFrame 的大小)

题目类型:Python

题目描述

复制代码
DataFrame `players:`
+-------------+--------+
| Column Name | Type   |
+-------------+--------+
| player_id   | int    |
| name        | object |
| age         | int    |
| position    | object |
| ...         | ...    |
+-------------+--------+

编写一个解决方案,计算并显示 players 的 行数和列数。

将结果返回为一个数组:

[number of rows, number of columns]

返回结果格式如下示例所示。

示例 1:

复制代码
**输入:
**+-----------+----------+-----+-------------+--------------------+
| player_id | name     | age | position    | team               |
+-----------+----------+-----+-------------+--------------------+
| 846       | Mason    | 21  | Forward     | RealMadrid         |
| 749       | Riley    | 30  | Winger      | Barcelona          |
| 155       | Bob      | 28  | Striker     | ManchesterUnited   |
| 583       | Isabella | 32  | Goalkeeper  | Liverpool          |
| 388       | Zachary  | 24  | Midfielder  | BayernMunich       |
| 883       | Ava      | 23  | Defender    | Chelsea            |
| 355       | Violet   | 18  | Striker     | Juventus           |
| 247       | Thomas   | 27  | Striker     | ParisSaint-Germain |
| 761       | Jack     | 33  | Midfielder  | ManchesterCity     |
| 642       | Charlie  | 36  | Center-back | Arsenal            |
+-----------+----------+-----+-------------+--------------------+**
输出:
**[10, 5]
解释:
这个 DataFrame 包含 10 行和 5 列。

解题思路

解法一:shape 属性(主提交版本)

  • DataFrame.shape 返回 (行数, 列数) 元组,直接转列表即答案。

解法二:len 与 len(columns)(对照)

  • 用 len(df) 取行数、len(df.columns) 取列数再组列表,效果一致。

代码实现

python 复制代码
import pandas as pd

def getDataframeSize(players: pd.DataFrame) -> List[int]:
    return list(players.shape)

复杂度分析

  • 时间复杂度 :O(n),n 为 DataFrame 行数,单次内建操作。
  • 空间复杂度 :O(n),返回结果表需要存储数据。

2879. Display the First Three Rows(显示前三行)

题目类型:Python

题目描述

复制代码
DataFrame: `employees`
+-------------+--------+
| Column Name | Type   |
+-------------+--------+
| employee_id | int    |
| name        | object |
| department  | object |
| salary      | int    |
+-------------+--------+

编写一个解决方案,显示这个 DataFrame 的** 前 3 **行。

示例 1:

复制代码
**输入:
**DataFrame employees
+-------------+-----------+-----------------------+--------+
| employee_id | name      | department            | salary |
+-------------+-----------+-----------------------+--------+
| 3           | Bob       | Operations            | 48675  |
| 90          | Alice     | Sales                 | 11096  |
| 9           | Tatiana   | Engineering           | 33805  |
| 60          | Annabelle | InformationTechnology | 37678  |
| 49          | Jonathan  | HumanResources        | 23793  |
| 43          | Khaled    | Administration        | 40454  |
+-------------+-----------+-----------------------+--------+
输出:
+-------------+---------+-------------+--------+
| employee_id | name    | department  | salary |
+-------------+---------+-------------+--------+
| 3           | Bob     | Operations  | 48675  |
| 90          | Alice   | Sales       | 11096  |
| 9           | Tatiana | Engineering | 33805  |
+-------------+---------+-------------+--------+
解释:
只有前 3 行被显示。

解题思路

解法一:head(3)(主提交版本)

  • DataFrame.head(n) 返回前 n 行;本题要求前三行,调用 head(3)。

解法二:iloc 切片(对照)

  • df.iloc[:3] 按位置切片同样取到前三行。

代码实现

python 复制代码
import pandas as pd

def selectFirstRows(employees: pd.DataFrame) -> pd.DataFrame:
    return employees.head(3)

复杂度分析

  • 时间复杂度 :O(n),n 为 DataFrame 行数,单次内建操作。
  • 空间复杂度 :O(n),返回结果表需要存储数据。

2880. Select Data(数据选取)

题目类型:Python

题目描述

复制代码
DataFrame students
+-------------+--------+
| Column Name | Type   |
+-------------+--------+
| student_id  | int    |
| name        | object |
| age         | int    |
+-------------+--------+

编写一个解决方案,选择 student_id = 101 的学生的 name 和 age 并输出。

返回结果格式如下示例所示。

示例 1:

复制代码
输入:
+------------+---------+-----+
| student_id | name    | age |
+------------+---------+-----+
| 101        | Ulysses | 13  |
| 53         | William | 10  |
| 128        | Henry   | 6   |
| 3          | Henry   | 11  |
+------------+---------+-----+
输出:
+---------+-----+
| name    | age |
+---------+-----+
| Ulysses | 13  |
+---------+-----+
**解释:
**学生 Ulysses 的 student_id = 101,所以我们输出了他的 name 和 age。

解题思路

解法一:布尔索引 + 列选择(主提交版本)

  • students['student_id'] == 101 生成布尔掩码,筛选学号为 101 的行;
  • 再用双括号 [['name','age']] 选取需要的两列。

解法二:loc 定位(对照)

  • df.loc[df['student_id'] == 101, ['name','age']] 一步完成行列选取。

代码实现

python 复制代码
import pandas as pd

def selectData(students: pd.DataFrame) -> pd.DataFrame:
    return students[students['student_id'] == 101][['name', 'age']]

复杂度分析

  • 时间复杂度 :O(n),n 为 DataFrame 行数,单次内建操作。
  • 空间复杂度 :O(n),返回结果表需要存储数据。
相关推荐
装不满的克莱因瓶20 分钟前
设计模式之策略模式核心概念和应用场景
java·后端·设计模式·架构·系统架构·策略模式
晴天的雨.99226 分钟前
【C++】继承篇
java·开发语言·c++
SomeOtherTime31 分钟前
第 1 讲:内存布局与所有权解剖——栈帧、胖指针与“移动/拷贝”在汇编层面的真实面目
java·开发语言·汇编
qq_4069810336 分钟前
c++ lamda(三)Lambda表达式
java·前端·c++
头发还在的女程序员37 分钟前
Java在线租房系统源码解析:Spring Boot+Vue 3+UniApp房源管理与预约看房方案
java·vue.js·spring boot·租房小程序·租房系统·租房软件
FYKJ_201044 分钟前
springboot同人周边订单管理系统48955-计算机毕业设计、课程设计
java·javascript·vue.js·spring boot·django·php·课程设计
Flynn Liang1 小时前
LLM 应用安全:Prompt 注入、越权与输入输出护栏
java·人工智能·大模型·langchain4j·llm 安全·prompt 注入·guardrail
特立独行的猫A7 小时前
用仓颉语言 cj-tauri 框架,五分钟给娃做了个鸿蒙形近字游戏
java·后端·python
budingxiaomoli14 小时前
Spring统一功能处理
java·后端·spring