LeetCode2761~2780题目解析

LeetCode 2761-2780. 题解合集

文章目录


2761. Prime Pairs With Target Sum(和等于目标值的质数对)

题目描述

给你一个整数 n。如果两个整数 x 和 y 满足下述条件,则认为二者形成一个质数对:

1. 1 <= x <= y <= n;

2. x + y == n;

  1. x 和 y 都是质数。

请你以二维有序列表的形式返回符合要求的所有 `xi`, `yi`,列表需要按 xi 的

非递减顺序排序。如果不存在符合要求的质数对,则返回空数组。

示例 1

复制代码
n = 10 -> [[3,7],[5,5]]

示例 2

复制代码
n = 2 -> []

提示:

  • 1 <= n <= 1e6。

解题思路

解法一:埃氏筛 + 双指针,主提交版本

先线性/埃氏筛出 1...n 的所有质数标记。然后对质数列表用双指针:

左指针从最小质数开始、右指针从最大质数开始,若 pL + pR == n 且 pL <= pR

则是一对答案(左移右移);若小于 n 则 pL 右移,大于 n 则 pR 左移。

由于质数列表天然递增,双指针自然按 xi 非递减输出。

解法二:素数判定逐个枚举,对照

对每个 x 从 2 到 n/2,判断 x 与 n-x 是否均为

质数(朴素试除,小数据可用)。

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

代码实现

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

public class Solution {

    public List<List<Integer>> findPrimePairs(int n) {
        // 埃氏筛
        boolean[] notPrime = new boolean[n + 1];
        notPrime[0] = notPrime[1] = true;
        for (int i = 2; i * i <= n; i++) {
            if (!notPrime[i]) {
                for (int j = i * i; j <= n; j += i) notPrime[j] = true;
            }
        }
        List<Integer> primes = new ArrayList<Integer>();
        for (int i = 2; i <= n; i++) {
            if (!notPrime[i]) primes.add(i);
        }
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        int l = 0, r = primes.size() - 1;
        while (l <= r) {
            int sum = primes.get(l) + primes.get(r);
            if (sum == n) {
                ans.add(Arrays.asList(primes.get(l), primes.get(r)));
                l++;
                r--;
            } else if (sum < n) {
                l++;
            } else {
                r--;
            }
        }
        return ans;
    }

    public List<List<Integer>> findPrimePairsBrute(int n) {
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        for (int x = 2; x <= n / 2; x++) {
            int y = n - x;
            if (isPrime(x) && isPrime(y) && x <= y) {
                ans.add(Arrays.asList(x, y));
            }
        }
        return ans;
    }

    private boolean isPrime(int v) {
        if (v < 2) return false;
        for (int i = 2; i * i <= v; i++) {
            if (v % i == 0) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.findPrimePairs(10).equals(Arrays.asList(Arrays.asList(3, 7), Arrays.asList(5, 5))) ? "PASS" : "FAIL");
        System.out.println(sol.findPrimePairs(2).equals(new ArrayList<List<Integer>>()) ? "PASS" : "FAIL");
        System.out.println(sol.findPrimePairs(4).equals(Arrays.asList(Arrays.asList(2, 2))) ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2761);
        boolean ok = true;
        for (int t = 0; t < 500; t++) {
            int n = 2 + rnd.nextInt(100);
            List<List<Integer>> r1 = sol.findPrimePairs(n);
            List<List<Integer>> r2 = sol.findPrimePairsBrute(n);
            if (!r1.equals(r2)) {
                ok = false;
                System.out.println("Mismatch n=" + n + " sieve=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2761 全部 PASS" : "2761 存在不一致");
    }
}

复杂度分析

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


2762. Continuous Subarrays(不间断子数组)

题目描述

给你一个下标从 0 开始的整数数组 nums。nums 的一个子数组如果满足以下条件,

那么它是不间断的:对于子数组内任意下标对 (i1, i2),都有 |nums[i1] - nums[i2]| <= 2。

请你返回不间断子数组的总数目。

示例 1

复制代码
nums = [5,4,2,4] -> 8
大小为 1 的有 4 个,大小为 2 的有 3 个,大小为 3 的有 1 个,共 8 个。

示例 2

复制代码
nums = [1,2,3] -> 6(1+2+3)

提示:

  • 1 <= nums.length <= 1e5;1 <= nums[i] <= 1e9。

解题思路

解法一:滑动窗口 + 单调队列,主提交版本

条件「任意两元素差 <= 2」等价于「窗口内最大值 - 最小值 <= 2」。

维护窗口 l, r,用两个单调队列(一个存最小值候选、一个存最大值候选)

维护窗口极值。当 max - min > 2 时收缩左边界,直到满足。

以 r 结尾的不间断子数组个数 = r - l + 1(每个左端点 l' in l, r 都可行,

因为收缩到合法后 l 左侧再收缩也只会更小)。

累加即答案。注意用 long 避免溢出。

解法二:暴力枚举,对照

枚举所有子数组,检查是否任意两元素差 <= 2

(即取 min/max 差 <= 2),仅用于小规模对拍。

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

代码实现

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

public class Solution {

    public long continuousSubarrays(int[] nums) {
        int n = nums.length;
        long ans = 0;
        int l = 0;
        // 单调队列存下标:minQ 头部最小,maxQ 头部最大
        java.util.ArrayDeque<Integer> minQ = new java.util.ArrayDeque<Integer>();
        java.util.ArrayDeque<Integer> maxQ = new java.util.ArrayDeque<Integer>();
        for (int r = 0; r < n; r++) {
            // 维护最小队列
            while (!minQ.isEmpty() && nums[minQ.peekLast()] >= nums[r]) minQ.pollLast();
            minQ.offerLast(r);
            // 维护最大队列
            while (!maxQ.isEmpty() && nums[maxQ.peekLast()] <= nums[r]) maxQ.pollLast();
            maxQ.offerLast(r);
            // 收缩左边界直到满足 |max-min| <= 2
            while (nums[maxQ.peekFirst()] - nums[minQ.peekFirst()] > 2) {
                l++;
                if (minQ.peekFirst() < l) minQ.pollFirst();
                if (maxQ.peekFirst() < l) maxQ.pollFirst();
            }
            ans += (long) (r - l + 1);
        }
        return ans;
    }

    public long continuousSubarraysBrute(int[] nums) {
        int n = nums.length;
        long ans = 0;
        for (int i = 0; i < n; i++) {
            int mn = nums[i], mx = nums[i];
            for (int j = i; j < n; j++) {
                mn = Math.min(mn, nums[j]);
                mx = Math.max(mx, nums[j]);
                if (mx - mn <= 2) ans++;
                else break; // 单调不再可能满足
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.continuousSubarrays(new int[]{5, 4, 2, 4}) == 8 ? "PASS" : "FAIL");
        System.out.println(sol.continuousSubarrays(new int[]{1, 2, 3}) == 6 ? "PASS" : "FAIL");
        System.out.println(sol.continuousSubarrays(new int[]{1}) == 1 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2762);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 1 + rnd.nextInt(12);
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(10);
            long r1 = sol.continuousSubarrays(nums);
            long r2 = sol.continuousSubarraysBrute(nums);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2762 全部 PASS" : "2762 存在不一致");
    }
}

复杂度分析

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


2763. Sum of Imbalance Numbers of All Subarrays(所有子数组中不平衡数字之和)

题目描述

一个长度为 n 下标从 0 开始的整数数组 arr 的不平衡数字定义为:在 sarr = sorted(arr)

数组中,满足 sarr[i+1] - sarr[i] > 1 的下标 i 的数目(0 <= i < n-1)。

给你一个下标从 0 开始的整数数组 nums,请你返回它所有子数组的不平衡数字之和。

示例 1

复制代码
nums = [2,3,1,4] -> 3
非 0 不平衡数字的子数组:[3,1]=1、[3,1,4]=1、[1,4]=1。

示例 2

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

提示:

  • 1 <= nums.length <= 1000;1 <= nums[i] <= nums.length。

解题思路

解法一:连通块递推 O(n^2),主提交版本

对每个起点 i,向右扩展终点 j,增量维护当前子数组的不平衡数字。

设去重排序后序列中「连续值段」(值相差 1 的相邻元素连成一段)的段数为 b,

则排序序列中相邻差 > 1 的位置数恰好为 b - 1,即 im = b - 1。

新加入值 x(首次出现)时更新 b:

  • x-1 与 x+1 均已出现:x 把左右两段连成一段,b--;
  • 仅一边出现:段数不变;
  • 两边都没出现:x 自成一新段,b++。
    x 重复出现时不改变 b。每次把 im = b - 1 累加入答案。
    解法二:直接排序暴力,对照
    对每个子数组复制、排序、扫一遍算差值 >1 的个数。
    时间复杂度:O(n^2);空间复杂度:O(n)。

代码实现

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

public class Solution {

    public int sumImbalanceNumbers(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            boolean[] seen = new boolean[n + 2]; // 值域 [1, n]
            int blocks = 0; // 连通块(连续值段)数
            for (int j = i; j < n; j++) {
                int x = nums[j];
                if (!seen[x]) {
                    seen[x] = true;
                    boolean hasL = x > 1 && seen[x - 1];
                    boolean hasR = seen[x + 1];
                    if (hasL && hasR) blocks--;       // 连接左右两段
                    else if (!hasL && !hasR) blocks++; // 自成一新段
                    // 仅一边出现:段数不变
                }
                // 不平衡数 = 段数 - 1(段间相邻差 > 1)
                ans += (blocks - 1);
            }
        }
        return ans;
    }

    public int sumImbalanceNumbersBrute(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                int len = j - i + 1;
                int[] arr = new int[len];
                for (int k = i; k <= j; k++) arr[k - i] = nums[k];
                java.util.Arrays.sort(arr);
                int im = 0;
                for (int k = 0; k < len - 1; k++) {
                    if (arr[k + 1] - arr[k] > 1) im++;
                }
                ans += im;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.sumImbalanceNumbers(new int[]{2, 3, 1, 4}) == 3 ? "PASS" : "FAIL");
        System.out.println(sol.sumImbalanceNumbers(new int[]{1, 3, 3, 3, 5}) == 8 ? "PASS" : "FAIL");

        // 随机对拍(值域受限于 [1, n])
        java.util.Random rnd = new java.util.Random(2763);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 1 + rnd.nextInt(9);
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(n);
            int r1 = sol.sumImbalanceNumbers(nums);
            int r2 = sol.sumImbalanceNumbersBrute(nums);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2763 全部 PASS" : "2763 存在不一致");
    }
}

复杂度分析

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


2764. Is Array a Preorder of Some Binary Tree(数组是否表示某二叉树的前序遍历)

题目描述

给定一个以 0 为起始索引的整数二维数组 nodes,你的任务是确定给定的数组是否表示

某个二叉树的前序遍历。对于每个索引 i,nodes[i] = `id`, `parentId`,其中 id 是

索引 i 处节点的 id,parentId 是其在树中的父节点 id(根节点 parentId = -1)。

如果给定的数组表示某个树的前序遍历,则返回 true,否则返回 false。

示例 1

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

示例 2

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

提示:

  • 1 <= nodes.length <= 1e5;nodes[i].length == 2;0 <= id <= 1e5;
  • -1 <= parentId <= 1e5;输入保证 nodes 可以组成二叉树。

解题思路

解法一:栈模拟前序遍历合法性,主提交版本

前序遍历的性质:访问顺序按「节点 + 左子树 + 右子树」。用一个栈维护「当前

沿着左链走下来的祖先链」:

  • 根节点(parentId == -1)必须出现在下标 0;
  • 对于后续每个节点 (id, pid):它的父节点必须是栈顶。若是,入栈(继续沿左链);
    若否,则说明要回到某个祖先并转入其右子树------此时把栈顶元素出栈直到栈顶
    == pid(出栈意味着该节点左子树遍历完毕,转入右兄弟)。
  • 若把栈弹出完都没找到 pid,说明顺序非法,返回 false。
    注意:同一节点的右子树前序顺序是连续的,出栈后 pid 必为栈顶。
    用 Set 顺便校验节点 id 唯一。
    解法二:构造树后模拟递归前序,对照
    按 nodes 建树(哈希表 id -> 子节点列表),
    从根做前序遍历,把遍历顺序与输入顺序逐项对比。小规模对拍。
    时间复杂度:O(n);空间复杂度:O(n)。

代码实现

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

public class Solution {

    public boolean isPreorder(List<List<Integer>> nodes) {
        int n = nodes.size();
        // 根必须在下标 0
        if (nodes.get(0).get(1) != -1) return false;
        Set<Integer> seen = new HashSet<Integer>();
        seen.add(nodes.get(0).get(0));
        java.util.ArrayDeque<Integer> stack = new java.util.ArrayDeque<Integer>();
        stack.push(nodes.get(0).get(0));
        for (int i = 1; i < n; i++) {
            int id = nodes.get(i).get(0);
            int pid = nodes.get(i).get(1);
            if (!seen.add(id)) return false; // id 重复非法
            // 弹出左链直到栈顶是父节点
            while (!stack.isEmpty() && stack.peek() != pid) {
                stack.pop();
            }
            if (stack.isEmpty()) return false; // 父节点不在祖先链上
            stack.push(id);
        }
        return true;
    }

    public boolean isPreorderBuild(List<List<Integer>> nodes) {
        int n = nodes.size();
        // 邻接表:父 -> 子列表(保持输入顺序 ------ 输入顺序就是前序顺序的候选)
        java.util.Map<Integer, java.util.List<Integer>> children
                = new java.util.HashMap<Integer, java.util.List<Integer>>();
        int root = -1;
        for (List<Integer> nd : nodes) {
            int id = nd.get(0), pid = nd.get(1);
            if (pid == -1) root = id;
            else {
                if (!children.containsKey(pid)) children.put(pid, new java.util.ArrayList<Integer>());
                children.get(pid).add(id);
            }
        }
        if (root != nodes.get(0).get(0)) return false;
        final int[] idx = {0};
        return preorderCompare(nodes, children, root, idx) && idx[0] == n;
    }

    private boolean preorderCompare(List<List<Integer>> nodes,
                                    java.util.Map<Integer, java.util.List<Integer>> children,
                                    int id, int[] idx) {
        if (idx[0] >= nodes.size() || nodes.get(idx[0]).get(0) != id) return false;
        idx[0]++;
        java.util.List<Integer> ch = children.get(id);
        if (ch == null) return true;
        for (int c : ch) {
            if (!preorderCompare(nodes, children, c, idx)) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.isPreorder(java.util.Arrays.asList(
                java.util.Arrays.asList(0, -1), java.util.Arrays.asList(1, 0),
                java.util.Arrays.asList(2, 0), java.util.Arrays.asList(3, 2),
                java.util.Arrays.asList(4, 2))) ? "PASS" : "FAIL");
        System.out.println(!sol.isPreorder(java.util.Arrays.asList(
                java.util.Arrays.asList(0, -1), java.util.Arrays.asList(1, 0),
                java.util.Arrays.asList(2, 0), java.util.Arrays.asList(3, 1),
                java.util.Arrays.asList(4, 1))) ? "PASS" : "FAIL");
        // 单节点
        System.out.println(sol.isPreorder(java.util.Arrays.asList(java.util.Arrays.asList(5, -1))) ? "PASS" : "FAIL");

        // 随机对拍:随机生成一棵树,产生前序序列(真用例),再做随机打乱(大概率假)
        java.util.Random rnd = new java.util.Random(2764);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 1 + rnd.nextInt(10);
            // 随机建树:id 0..n-1,父亲取 [0, i-1] 或 -1(根唯一)
            java.util.List<java.util.List<Integer>> tree = buildRandomTree(n, rnd);
            if (tree == null) continue;
            // 生成前序列表,作为正例
            java.util.List<java.util.List<Integer>> pre = preorderOf(tree);
            boolean r1 = sol.isPreorder(pre);
            boolean r2 = sol.isPreorderBuild(pre);
            if (!r1 || !r2) {
                ok = false;
                System.out.println("Mismatch positive pre=" + pre + " main=" + r1 + " build=" + r2);
                break;
            }
            // 随机交换两个节点的顺序铸成坏例(保证仍以 0,-1 开头则可能仍合法......直接随机打乱非根部分,判断双解法应一致)
            java.util.List<java.util.List<Integer>> shuffled = new java.util.ArrayList<java.util.List<Integer>>(pre);
            if (shuffled.size() > 2) {
                int a = 1 + rnd.nextInt(shuffled.size() - 1);
                int b = 1 + rnd.nextInt(shuffled.size() - 1);
                java.util.Collections.swap(shuffled, a, b);
            }
            boolean s1 = sol.isPreorder(shuffled);
            boolean s2 = sol.isPreorderBuild(shuffled);
            if (s1 != s2) {
                ok = false;
                System.out.println("Mismatch shuffled=" + shuffled + " main=" + s1 + " build=" + s2);
                break;
            }
        }
        System.out.println(ok ? "2764 全部 PASS" : "2764 存在不一致");
    }

    private static java.util.List<java.util.List<Integer>> buildRandomTree(int n, java.util.Random rnd) {
        int[] parent = new int[n];
        java.util.Arrays.fill(parent, -2);
        parent[0] = -1; // 根
        java.util.List<java.util.List<Integer>> result
                = new java.util.ArrayList<java.util.List<Integer>>();
        result.add(java.util.Arrays.asList(0, -1));
        for (int i = 1; i < n; i++) {
            int p = rnd.nextInt(i); // 父亲一定有 id < i,保证树连通
            parent[i] = p;
            result.add(java.util.Arrays.asList(i, p));
        }
        // 合法性由「父亲 id < 子 id」保证可构成树(无环、除根外都有父亲)
        return result;
    }

    private static java.util.List<java.util.List<Integer>> preorderOf(java.util.List<java.util.List<Integer>> nodes) {
        int n = nodes.size();
        java.util.Map<Integer, java.util.List<Integer>> children
                = new java.util.HashMap<Integer, java.util.List<Integer>>();
        int root = -1;
        for (java.util.List<Integer> nd : nodes) {
            int id = nd.get(0), pid = nd.get(1);
            if (pid == -1) root = id;
            else {
                if (!children.containsKey(pid)) children.put(pid, new java.util.ArrayList<Integer>());
                children.get(pid).add(id);
            }
        }
        java.util.List<java.util.List<Integer>> pre = new java.util.ArrayList<java.util.List<Integer>>();
        dfsPre(nodes, children, root, pre);
        return pre;
    }

    private static void dfsPre(java.util.List<java.util.List<Integer>> nodes,
                               java.util.Map<Integer, java.util.List<Integer>> children,
                               int id, java.util.List<java.util.List<Integer>> pre) {
        int pid = -2;
        for (java.util.List<Integer> nd : nodes) {
            if (nd.get(0) == id) { pid = nd.get(1); break; }
        }
        pre.add(java.util.Arrays.asList(id, pid));
        java.util.List<Integer> ch = children.get(id);
        if (ch == null) return;
        for (int c : ch) dfsPre(nodes, children, c, pre);
    }
}

复杂度分析

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


2765. Longest Alternating Subarray(最长交替子数组)

题目描述

给你一个下标从 0 开始的整数数组 nums。如果 nums 中长度为 m 的子数组 s 满足以下

条件,我们称它是一个交替子数组:

  • m > 1;
  • s1 = s0 + 1;
  • 数组 s 与 `s0`, `s1`, `s0`, `s1`, ... 一样,即 s1 - s0 = 1,s2 - s1 = -1,
    s3 - s2 = 1,s4 - s3 = -1,以此类推。
    请你返回 nums 中所有交替子数组中最长的长度;若不存在交替子数组,返回 -1。

示例 1

复制代码
nums = [2,3,4,3,4] -> 4([3,4,3,4])

示例 2

复制代码
nums = [4,5,6] -> 2([4,5] 或 [5,6])

提示:

  • 2 <= nums.length <= 100;1 <= nums[i] <= 1e4。

解题思路

解法一:枚举起点扩展,主提交版本

枚举每个起点 i,要求 numsi+1 - numsi == 1(交替起点必须是 +1 上升),

之后差值在 +1 / -1 之间切换,一直扩展直到不满足,记录长度。

注意交替子数组起点处必须是 s1 = s0 + 1。

解法二:暴力枚举所有子数组,对照

枚举每个 (l, r) 完整检查交替条件。

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

代码实现

java 复制代码
public class Solution {

    public int alternatingSubarray(int[] nums) {
        int n = nums.length;
        int ans = -1;
        for (int i = 0; i < n - 1; i++) {
            // 起点必须是 +1 上升
            if (nums[i + 1] - nums[i] != 1) continue;
            int len = 2;
            int diff = -1; // 下一个期望差值
            int j = i + 1;
            while (j + 1 < n && nums[j + 1] - nums[j] == diff) {
                len++;
                diff = -diff;
                j++;
            }
            ans = Math.max(ans, len);
        }
        return ans;
    }

    public int alternatingSubarrayBrute(int[] nums) {
        int n = nums.length;
        int ans = -1;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                // 检查 [i, j] 是否交替:s1=s0+1,之后 ±1 交替
                boolean ok = true;
                if (nums[i + 1] - nums[i] != 1) continue;
                long diff = 1;
                for (int k = i + 1; k <= j && ok; k++) {
                    if (nums[k] - nums[k - 1] != diff) ok = false;
                    diff = -diff;
                }
                if (ok) ans = Math.max(ans, j - i + 1);
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.alternatingSubarray(new int[]{2, 3, 4, 3, 4}) == 4 ? "PASS" : "FAIL");
        System.out.println(sol.alternatingSubarray(new int[]{4, 5, 6}) == 2 ? "PASS" : "FAIL");
        System.out.println(sol.alternatingSubarray(new int[]{1, 1, 1}) == -1 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2765);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 2 + rnd.nextInt(14);
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(8);
            int r1 = sol.alternatingSubarray(nums);
            int r2 = sol.alternatingSubarrayBrute(nums);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " main=" + r1 + " brute=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2765 全部 PASS" : "2765 存在不一致");
    }
}

复杂度分析

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


2766. Relocate Marbles(重新放置石块)

题目类型:JavaScript

题目描述

给你一个下标从 0 开始的整数数组 nums ,表示一些石块的初始位置。再给你两个长度** 相等** 下标从 0 开始的整数数组 moveFrom 和 moveTo 。

在 moveFrom.length 次操作内,你将改变石块的位置。在第 i 次操作中,你将位置在 moveFrom[i] 的所有石块移到位置 moveTo[i] 。

完成这些操作后,请你按升序返回所有 有 石块的位置。

注意:

  • 如果一个位置至少有一个石块,我们称这个位置 有 石块。

  • 一个位置可能会有多个石块。

示例 1:

复制代码
输入:nums = [1,6,7,8], moveFrom = [1,7,2], moveTo = [2,9,5]
输出:[5,6,8,9]
解释:一开始,石块在位置 1,6,7,8 。
第 i = 0 步操作中,我们将位置 1 处的石块移到位置 2 处,位置 2,6,7,8 有石块。
第 i = 1 步操作中,我们将位置 7 处的石块移到位置 9 处,位置 2,6,8,9 有石块。
第 i = 2 步操作中,我们将位置 2 处的石块移到位置 5 处,位置 5,6,8,9 有石块。
最后,至少有一个石块的位置为 [5,6,8,9] 。

示例 2:

复制代码
输入: nums = [1,1,3,3], moveFrom = [1,3], moveTo = [2,2]
输出:[2]
解释:一开始,石块在位置 [1,1,3,3] 。
第 i = 0 步操作中,我们将位置 1 处的石块移到位置 2 处,有石块的位置为 [2,2,3,3] 。
第 i = 1 步操作中,我们将位置 3 处的石块移到位置 2 处,有石块的位置为 [2,2,2,2] 。
由于 2 是唯一有石块的位置,我们返回 [2] 。

提示:

  • 1 <= nums.length <= 105

  • 1 <= moveFrom.length <= 105

  • moveFrom.length == moveTo.length

  • 1 <= nums[i], moveFrom[i], moveTo[i] <= 109

  • 测试数据保证在进行第 i 步操作时,moveFrom[i] 处至少有一个石块。

解题思路

每次把某一位置上的全部石子移到另一位置,求最终有石子的位置。同一位置可堆叠,数组模拟移动需要反复查找。

用集合保存有石子的坐标:移出时删除起点,移入时加入终点。最后排序输出。

代码实现

typescript 复制代码
function relocateMarbles(nums: number[], moveFrom: number[], moveTo: number[]): number[] {
    const pos: Set<number> = new Set(nums);
    for (let i = 0; i < moveFrom.length; i++) {
        pos.delete(moveFrom[i]);
        pos.add(moveTo[i]);
    }
    return [...pos].sort((a, b) => a - b);
}

复杂度分析

  • 时间复杂度 :O(n),n 为操作次数,哈希集合读写均摊常数。
  • 空间复杂度 :O(k),k 为有石块的位置数。

2767. Partition String Into Minimum Beautiful Substrings(将字符串分割为最少的美丽子字符串)

题目描述

给你一个二进制字符串 s,你需要将字符串分割成一个或者多个子字符串,使每个子字符串都是美丽的。

如果一个字符串满足以下条件,则它是美丽的:

  • 它不包含前导 0;
  • 它是 5 的幂的二进制表示。
    返回分割后的子字符串的最少数目;若无法分割,返回 -1。

示例 1

复制代码
s = "1011" -> 2(["101","1"],101=5^1 二进制,1=5^0 二进制)

示例 2

复制代码
s = "111" -> 3(["1","1","1"])

示例 3

复制代码
s = "0" -> -1

提示:

  • 1 <= s.length <= 15;s[i] 为 '0' 或 '1'。

解题思路

解法一:DP 最小分割,主提交版本

5 的幂(二进制长度不超过 15)数量有限,先离线枚举所有 5 的幂的二进制表示,

存成集合 good。用 dpi 表示分割 s0...i-1 所需的最少美丽子串数,

枚举最后一段 j, i-1:若该段无前导 0 且其数值属于 good,则

dpi = min(dpi, dpj + 1)。答案为 dpn(不可达为 -1)。

解法二:DFS 回溯,对照

从位置 pos 出发,枚举所有以 pos 开头的美丽子串并递归,

取最小段数,记忆化。小规模对拍。

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

代码实现

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

public class Solution {

    public int minimumBeautifulSubstrings(String s) {
        int n = s.length();
        // 预计算所有长度 <= n 的 5 的幂的二进制值
        Set<Integer> good = new HashSet<Integer>();
        long p = 1;
        while (true) {
            String bin = Long.toBinaryString(p);
            if (bin.length() > n) break;
            good.add((int) p);
            p *= 5;
            if (p > (1L << 40)) break;
        }
        int INF = Integer.MAX_VALUE / 2;
        int[] dp = new int[n + 1];
        java.util.Arrays.fill(dp, INF);
        dp[0] = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                if (s.charAt(j) == '0') continue; // 前导 0
                String sub = s.substring(j, i);
                // 数值解析(长度 <= 15,int 安全)
                int val = 0;
                for (int k = j; k < i; k++) val = (val << 1) | (s.charAt(k) - '0');
                if (good.contains(val)) {
                    if (dp[j] + 1 < dp[i]) dp[i] = dp[j] + 1;
                }
            }
        }
        return dp[n] >= INF ? -1 : dp[n];
    }

    public int minimumBeautifulSubstringsDfs(String s) {
        int n = s.length();
        Set<Integer> good = new HashSet<Integer>();
        long p = 1;
        while (true) {
            if (Long.toBinaryString(p).length() > n) break;
            good.add((int) p);
            p *= 5;
        }
        int[] memo = new int[n + 1];
        java.util.Arrays.fill(memo, -2);
        int res = dfs(s, 0, good, memo);
        return res >= Integer.MAX_VALUE / 2 ? -1 : res;
    }

    private int dfs(String s, int pos, Set<Integer> good, int[] memo) {
        if (pos == s.length()) return 0;
        if (memo[pos] != -2) return memo[pos];
        int best = Integer.MAX_VALUE / 2;
        int val = 0;
        for (int i = pos; i < s.length(); i++) {
            if (i == pos && s.charAt(i) == '0') break; // 前导 0 后面也都不行
            val = (val << 1) | (s.charAt(i) - '0');
            if (good.contains(val)) {
                int rest = dfs(s, i + 1, good, memo);
                if (rest + 1 < best) best = rest + 1;
            }
        }
        memo[pos] = best;
        return best;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minimumBeautifulSubstrings("1011") == 2 ? "PASS" : "FAIL");
        System.out.println(sol.minimumBeautifulSubstrings("111") == 3 ? "PASS" : "FAIL");
        System.out.println(sol.minimumBeautifulSubstrings("0") == -1 ? "PASS" : "FAIL");
        System.out.println(sol.minimumBeautifulSubstrings("1") == 1 ? "PASS" : "FAIL");

        // 随机对拍(长度小)
        java.util.Random rnd = new java.util.Random(2767);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int len = 1 + rnd.nextInt(12);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < len; i++) sb.append(rnd.nextBoolean() ? '1' : '0');
            String s = sb.toString();
            int r1 = sol.minimumBeautifulSubstrings(s);
            int r2 = sol.minimumBeautifulSubstringsDfs(s);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch s=" + s + " dp=" + r1 + " dfs=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2767 全部 PASS" : "2767 存在不一致");
    }
}

复杂度分析

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


2768. Number of Black Blocks(黑格子的数目)

题目描述

给你两个整数 m 和 n,表示一个下标从 0 开始的 m x n 的网格图。

给你一个下标从 0 开始的二维整数矩阵 coordinates,其中 coordinates[i] = `x`, `y`

表示坐标为 `x`, `y` 的格子是黑色的,其他格子为白色。

一个块定义为网格图中 2 x 2 的一个子矩阵:左上角格子 `x`, `y` 的块覆盖

`x`,`y`、`x`+1,`y`、`x`,`y`+1、`x`+1,`y`+1 四个格子(0 <= x < m-1,0 <= y < n-1)。

返回长度为 5 的数组 arr,arr[i] 表示恰好包含 i 个黑色格子的块的数目。

示例 1

复制代码
m=3, n=3, coordinates=[[0,0]] -> [3,1,0,0,0]

示例 2

复制代码
m=3, n=3, coordinates=[[0,0],[1,1],[0,2]] -> [0,2,2,0,0]

提示:

  • 2 <= m,n <= 1e5;0 <= coordinates.length <= 1e4;坐标两两不同。

解题思路

解法一:黑点定位 + 覆盖块统计,主提交版本

黑格数量最多 1e4,而块数量可能达到 (m-1)(n-1) 巨大,因此只统计受黑格影响的块。
每个黑格 (x, y) 最多属于 4 个块,块左上角为 (x-1,y-1)、(x-1,y)、(x,y-1)、(x,y),
对每个在网格内的左上角,用 Map 记录该块被多少个黑格覆盖。
总块数 total = (m-1)
(n-1)。没有被任何黑格覆盖的块黑格数为 0,

arr0 = total - 有黑格的块数。

对每个被覆盖的块,黑格数即覆盖它的黑格数量,对应 arrcount++。

解法二:直接建网格,对照

m, n 小时直接 boolean 网格 + 扫描所有块。

时间复杂度:O(coordinates.length * 4 + |map|);空间复杂度:O(coordinates.length)。

代码实现

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

public class Solution {

    public long[] countBlackBlocks(int m, int n, int[][] coordinates) {
        Map<Long, Integer> cover = new HashMap<Long, Integer>();
        for (int[] c : coordinates) {
            int x = c[0], y = c[1];
            // 左上角候选
            for (int dx = 0; dx <= 1; dx++) {
                for (int dy = 0; dy <= 1; dy++) {
                    int tx = x - dx, ty = y - dy;
                    if (tx >= 0 && tx <= m - 2 && ty >= 0 && ty <= n - 2) {
                        long key = (long) tx * n + ty;
                        cover.put(key, cover.getOrDefault(key, 0) + 1);
                    }
                }
            }
        }
        long[] ans = new long[5];
        long coveredBlocks = 0;
        for (int cnt : cover.values()) {
            ans[cnt]++;
            coveredBlocks++;
        }
        long total = (long) (m - 1) * (n - 1);
        ans[0] = total - coveredBlocks;
        return ans;
    }

    public long[] countBlackBlocksSim(int m, int n, int[][] coordinates) {
        boolean[][] black = new boolean[m][n];
        for (int[] c : coordinates) black[c[0]][c[1]] = true;
        long[] ans = new long[5];
        for (int i = 0; i < m - 1; i++) {
            for (int j = 0; j < n - 1; j++) {
                int cnt = 0;
                if (black[i][j]) cnt++;
                if (black[i + 1][j]) cnt++;
                if (black[i][j + 1]) cnt++;
                if (black[i + 1][j + 1]) cnt++;
                ans[cnt]++;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(java.util.Arrays.equals(
                sol.countBlackBlocks(3, 3, new int[][]{{0, 0}}),
                new long[]{3, 1, 0, 0, 0}) ? "PASS" : "FAIL");
        System.out.println(java.util.Arrays.equals(
                sol.countBlackBlocks(3, 3, new int[][]{{0, 0}, {1, 1}, {0, 2}}),
                new long[]{0, 2, 2, 0, 0}) ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2768);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int m = 2 + rnd.nextInt(5);
            int n = 2 + rnd.nextInt(5);
            java.util.Set<Long> used = new HashSet<Long>();
            int k = rnd.nextInt(m * n + 1);
            int[][] coords = new int[k][2];
            for (int i = 0; i < k; i++) {
                int x, y;
                do {
                    x = rnd.nextInt(m);
                    y = rnd.nextInt(n);
                } while (!used.add((long) x * n + y));
                coords[i][0] = x;
                coords[i][1] = y;
            }
            long[] r1 = sol.countBlackBlocks(m, n, coords);
            long[] r2 = sol.countBlackBlocksSim(m, n, coords);
            if (!java.util.Arrays.equals(r1, r2)) {
                ok = false;
                System.out.println("Mismatch m=" + m + " n=" + n + " coords="
                        + java.util.Arrays.deepToString(coords) + " main=" + java.util.Arrays.toString(r1)
                        + " sim=" + java.util.Arrays.toString(r2));
                break;
            }
        }
        System.out.println(ok ? "2768 全部 PASS" : "2768 存在不一致");
    }
}

复杂度分析

时间复杂度:O(coordinates.length * 4 + |map|);空间复杂度:O(coordinates.length)。


2769. Find the Maximum Achievable Number(找出最大的可达成数字)

题目描述

给你两个整数 num 和 t。如果整数 x 可以在执行下述操作不超过 t 次的情况下变为与

num 相等,则称其为可达成数字:每次操作将 x 的值增加或减少 1,同时可以选择将

num 的值增加或减少 1。

返回所有可达成数字中的最大值 x。

示例 1

复制代码
num = 4, t = 1 -> 6(x 减 1 的同时 num 加 1,一次后相等)

示例 2

复制代码
num = 3, t = 2 -> 7(两次:x 减 2、num 加 2)

提示:

  • 1 <= num, t <= 50。

解题思路

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

每次操作 x 与 num 的相对距离最多缩小 2(x 往 num 靠近 1、num 也往 x 靠近 1)。

因此 t 次操作最多让初始差 d = x - num 从任意大缩小到 0,即 t 次操作能消除的

最大距离为 2t,x 最大可取 num + 2t。

解法二:模拟,对照

从一个大值开始做 t 次操作逼近 num,或直接二分查找最大的 x

使得 t 次内可达 num(检查 x - num <= 2t 且同奇偶性放宽到不等式即可)。

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

代码实现

java 复制代码
public class Solution {

    public int theMaximumAchievableX(int num, int t) {
        return num + 2 * t;
    }

    public int theMaximumAchievableXSim(int num, int t) {
        int x = num + 2 * t; // 这就是最大值;模拟验证:
        // 用二分验证:最大 x 满足 (x - num) <= 2*t(x > num 时每次相对距离 -2)
        int lo = num, hi = num + 2 * t;
        while (lo < hi) {
            int mid = (lo + hi + 1) / 2;
            if (mid - num <= 2 * t) lo = mid;
            else hi = mid - 1;
        }
        return lo;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.theMaximumAchievableX(4, 1) == 6 ? "PASS" : "FAIL");
        System.out.println(sol.theMaximumAchievableX(3, 2) == 7 ? "PASS" : "FAIL");
        System.out.println(sol.theMaximumAchievableX(1, 50) == 101 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2769);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int num = 1 + rnd.nextInt(50);
            int tt = 1 + rnd.nextInt(50);
            int r1 = sol.theMaximumAchievableX(num, tt);
            int r2 = sol.theMaximumAchievableXSim(num, tt);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch num=" + num + " t=" + tt + " main=" + r1 + " sim=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2769 全部 PASS" : "2769 存在不一致");
    }
}

复杂度分析

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


2770. Maximum Number of Jumps to Reach the Last Index(达到末尾下标所需的最大跳跃次数)

题目描述

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

你的初始位置在下标 0。在一步操作中,你可以从下标 i 跳跃到任意满足下述条件的下标 j:

  • 0 <= i < j < n;
  • -target <= nums[j] - nums[i] <= target。
    返回到达下标 n-1 处所需的最大跳跃次数;若无法到达,返回 -1。

示例 1

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

示例 2

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

示例 3

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

提示:

  • 2 <= nums.length <= 1000;-1e9 <= nums[i] <= 1e9;0 <= target <= 2e9。

解题思路

解法一:DP 最长路,主提交版本

dpi 表示从 0 到 i 的最大跳跃次数。初始化 dp0 = 0,其余为 -inf。

对每个 i,枚举 j > i:若 |numsj-numsi| <= target 则 dpj = max(dpj, dpi+1)。

答案 dpn-1(-inf 则 -1)。

注意用 long 计算差值避免溢出(nums 差可达 2e9,target <= 2e9)。

解法二:记忆化搜索,对照

dfs(i) 表示从 0 到 i 的最大跳跃次数,

递归枚举前驱。小规模对拍。

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

代码实现

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

public class Solution {

    public int maximumJumps(int[] nums, int target) {
        int n = nums.length;
        int NEG = Integer.MIN_VALUE / 2;
        int[] dp = new int[n];
        Arrays.fill(dp, NEG);
        dp[0] = 0;
        for (int i = 0; i < n; i++) {
            if (dp[i] == NEG) continue;
            for (int j = i + 1; j < n; j++) {
                long diff = (long) nums[j] - nums[i];
                if (diff >= -target && diff <= target) {
                    if (dp[i] + 1 > dp[j]) dp[j] = dp[i] + 1;
                }
            }
        }
        return dp[n - 1] == NEG ? -1 : dp[n - 1];
    }

    public int maximumJumpsMemo(int[] nums, int target) {
        int n = nums.length;
        int[] memo = new int[n];
        Arrays.fill(memo, -2); // -2 未计算
        int res = dfs(nums, target, n - 1, memo);
        return res < 0 ? -1 : res; // 不可达统一返回 -1
    }

    private int dfs(int[] nums, int target, int i, int[] memo) {
        if (i == 0) return 0;
        if (memo[i] != -2) return memo[i];
        int best = -1; // 不可达
        for (int p = 0; p < i; p++) {
            long diff = (long) nums[i] - nums[p];
            if (diff >= -target && diff <= target) {
                int prev = dfs(nums, target, p, memo);
                if (prev >= 0 && prev + 1 > best) best = prev + 1;
            }
        }
        memo[i] = best;
        return best;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maximumJumps(new int[]{1, 3, 6, 4, 1, 2}, 2) == 3 ? "PASS" : "FAIL");
        System.out.println(sol.maximumJumps(new int[]{1, 3, 6, 4, 1, 2}, 3) == 5 ? "PASS" : "FAIL");
        System.out.println(sol.maximumJumps(new int[]{1, 3, 6, 4, 1, 2}, 0) == -1 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2770);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 2 + rnd.nextInt(9);
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) nums[i] = rnd.nextInt(21) - 10;
            int target = rnd.nextInt(8);
            int r1 = sol.maximumJumps(nums, target);
            int r2 = sol.maximumJumpsMemo(nums, target);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch nums=" + Arrays.toString(nums) + " target=" + target
                        + " dp=" + r1 + " memo=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2770 全部 PASS" : "2770 存在不一致");
    }
}

复杂度分析

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


2771. Longest Non-decreasing Subarray From Two Arrays(构造最长非递减子数组)

题目描述

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

定义另一个长度为 n 的数组 nums3:对于每个下标 i,你可以将 nums1[i] 或 nums2[i]

的值赋给 nums3[i]。你的任务是使用最优策略为 nums3 赋值,以最大化 nums3 中

最长非递减子数组的长度。

示例 1

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

示例 2

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

示例 3

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

提示:

  • 1 <= n <= 1e5;1 <= nums1i, nums2i <= 1e9。

解题思路

解法一:DP 双状态,主提交版本

dp0i:以 nums3i = nums1i 结尾的最长非递减子数组长度;

dp1i:以 nums3i = nums2i 结尾的最长非递减子数组长度。

转移时枚举上一位置选的源 (a, b) in {(0,0),(0,1),(1,0),(1,1)}:

若 numsSrci >= numsPrevprei 则可延续。初始 dp00 = dp10 = 1。

答案 = max over i (dp0i, dp1i)。

解法二:DFS 记忆化,对照

dfs(i, src) 返回以位置 i 选源 src 结尾的最长长度,

递归选择 i+1 时满足非递减。小规模对拍。

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

代码实现

java 复制代码
public class Solution {

    public int maxNonDecreasingLength(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int[] dp0 = new int[n]; // 以 nums1[i] 结尾
        int[] dp1 = new int[n]; // 以 nums2[i] 结尾
        dp0[0] = dp1[0] = 1;
        int ans = 1;
        for (int i = 1; i < n; i++) {
            dp0[i] = dp1[i] = 1;
            if (nums1[i] >= nums1[i - 1]) dp0[i] = Math.max(dp0[i], dp0[i - 1] + 1);
            if (nums1[i] >= nums2[i - 1]) dp0[i] = Math.max(dp0[i], dp1[i - 1] + 1);
            if (nums2[i] >= nums1[i - 1]) dp1[i] = Math.max(dp1[i], dp0[i - 1] + 1);
            if (nums2[i] >= nums2[i - 1]) dp1[i] = Math.max(dp1[i], dp1[i - 1] + 1);
            ans = Math.max(ans, Math.max(dp0[i], dp1[i]));
        }
        return ans;
    }

    public int maxNonDecreasingLengthMemo(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int[][] memo = new int[n][2];
        for (int[] row : memo) java.util.Arrays.fill(row, -1);
        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans = Math.max(ans, dfs(nums1, nums2, i, 0, memo));
            ans = Math.max(ans, dfs(nums1, nums2, i, 1, memo));
        }
        return ans;
    }

    private int dfs(int[] a, int[] b, int i, int src, int[][] memo) {
        if (memo[i][src] != -1) return memo[i][src];
        int val = src == 0 ? a[i] : b[i];
        int best = 1;
        if (i + 1 < a.length) {
            if (a[i + 1] >= val) best = Math.max(best, dfs(a, b, i + 1, 0, memo) + 1);
            if (b[i + 1] >= val) best = Math.max(best, dfs(a, b, i + 1, 1, memo) + 1);
        }
        memo[i][src] = best;
        return best;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maxNonDecreasingLength(new int[]{2, 3, 1}, new int[]{1, 2, 1}) == 2 ? "PASS" : "FAIL");
        System.out.println(sol.maxNonDecreasingLength(new int[]{1, 3, 2, 1}, new int[]{2, 2, 3, 4}) == 4 ? "PASS" : "FAIL");
        System.out.println(sol.maxNonDecreasingLength(new int[]{1, 1}, new int[]{2, 2}) == 2 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2771);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 1 + rnd.nextInt(9);
            int[] a = new int[n], b = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = 1 + rnd.nextInt(6);
                b[i] = 1 + rnd.nextInt(6);
            }
            int r1 = sol.maxNonDecreasingLength(a, b);
            int r2 = sol.maxNonDecreasingLengthMemo(a, b);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch a=" + java.util.Arrays.toString(a) + " b="
                        + java.util.Arrays.toString(b) + " dp=" + r1 + " memo=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2771 全部 PASS" : "2771 存在不一致");
    }
}

复杂度分析

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


2772. Apply Operations to Make All Array Elements Equal to Zero(使数组中的所有元素都等于零)

题目描述

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

你可以对数组执行下述操作任意次:从数组中选出长度为 k 的任一子数组,并将子数组中

每个元素都减去 1。如果你可以使数组中的所有元素都等于 0,返回 true;否则返回 false。

示例 1

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

示例 2

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

提示:

  • 1 <= k <= nums.length <= 1e5;0 <= nums[i] <= 1e6。

解题思路

解法一:贪心 + 差分、扫描线,主提交版本

从左到右处理:下标 i 的元素只能被「以 i 开头或更早开头、覆盖 i 的长度 k 区间」

的减操作影响。由于区间起点若在 i 之前且尚未结束,其贡献已在途;而起点为 i 的

区间必须在此时决定开几次。贪心:若当前累计被减量 cur < numsi,则需要在

i, i+k-1 内补 numsi - cur 次操作(这是唯一能减到 numsi 的手段,因为

之后的区间起点 >= i+1 不再覆盖 i)。若 cur > numsi 则已过度,非法。

用差分数组 add 记录「区间结束后的减少量」。检查每个位置是否恰好归零,且

最后一个 k 窗口不能越界(若 i + k > n 且还需要减则非法)。

实现:cur 为当前生效中的减量;addi 记录在位置 i 结束的区间贡献(负向)。

解法二:暴力模拟,对照

直接对每个 i 若 numsi > 0 则对 i, i+k-1 整体减

numsi(若越界则失败),最后检查全 0。仅用于小规模对拍。

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

代码实现

java 复制代码
public class Solution {

    public boolean checkArray(int[] nums, int k) {
        int n = nums.length;
        int[] diff = new int[n + 1]; // diff[i] 表示「结束于 i 的区间」带来的减少量
        int cur = 0;                 // 当前位置累计被减量
        for (int i = 0; i < n; i++) {
            cur += diff[i];
            if (cur > nums[i]) return false; // 减多了
            if (cur == nums[i]) continue;
            int need = nums[i] - cur;
            int end = i + k - 1;
            if (end >= n) return false; // 需要减但区间越界,无法归零
            cur += need;                 // 从 i 起多减 need 次
            diff[end + 1] -= need;       // end 之后结束
        }
        return true;
    }

    public boolean checkArraySim(int[] nums, int k) {
        int n = nums.length;
        int[] arr = nums.clone();
        for (int i = 0; i < n; i++) {
            if (arr[i] == 0) continue;
            if (i + k > n) return false;
            int dec = arr[i];
            for (int j = i; j < i + k; j++) {
                arr[j] -= dec;
                if (arr[j] < 0) return false;
            }
        }
        for (int v : arr) {
            if (v != 0) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.checkArray(new int[]{2, 2, 3, 1, 1, 0}, 3) ? "PASS" : "FAIL");
        System.out.println(!sol.checkArray(new int[]{1, 3, 1, 1}, 2) ? "PASS" : "FAIL");
        System.out.println(sol.checkArray(new int[]{0, 0, 0}, 1) ? "PASS" : "FAIL");
        System.out.println(!sol.checkArray(new int[]{1, 1}, 3) ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2772);
        boolean ok = true;
        for (int t = 0; t < 5000; t++) {
            int n = 1 + rnd.nextInt(8);
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) nums[i] = rnd.nextInt(4);
            int k = 1 + rnd.nextInt(n);
            boolean r1 = sol.checkArray(nums, k);
            boolean r2 = sol.checkArraySim(nums, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch nums=" + java.util.Arrays.toString(nums) + " k=" + k
                        + " diff=" + r1 + " sim=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2772 全部 PASS" : "2772 存在不一致");
    }
}

复杂度分析

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


2773. Height of Special Binary Tree(特殊二叉树的高度)

题目描述

给定一棵具有 n 个节点的特殊二叉树的根节点 root。特殊二叉树的节点编号从 1 到 n。

假设这棵树有 k 个叶子,顺序为 b1 < b2 < ... < bk。这棵树的叶子节点有一个特殊属性:

  • 如果 i < k,则 bi 的右子节点为 b(i+1);否则为 b1。
  • 如果 i > 1,则 bi 的左子节点为 b(i-1);否则为 bk。
    返回给定树的高度。二叉树的高度指从根节点到任何其他节点的最长路径的长度。

示例 1

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

示例 2

复制代码
root = [1,2] -> 1

示例 3

复制代码
root = [1,2,3,null,null,4,null,5,6] -> 3

提示:

  • 2 <= n <= 1e4;1 <= node.val <= n;node.val 唯一。

解题思路

解法一:DFS 深度优先 + 防环剪枝,主提交版本

叶子节点成环形链表(bi 左连 b(i-1)、右连 b(i+1))。若直接对二叉树 DFS 会因

叶子的交叉引用无限循环。观察:只有真正的「树边」(父 -> 子)能增加深度,

叶子之间的环引用需跳过:

  • 左子边:若 root.left.right == root,说明 root.left 是 root 的叶子左邻
    b(i-1)(环引用:b(i-1).right == bi == root),跳过不递归;
  • 右子边:若 root.right.left == root,说明 root.right 是 root 的叶子右邻
    b(i+1)(环引用:b(i+1).left == bi == root),跳过不递归。
    其余情况为真树边,深度 +1 继续。
    解法二:迭代 DFS + 显式栈,对照
    显式栈模拟同样剪枝,两者结果应一致。
    时间复杂度:O(n);空间复杂度:O(h)(h 为树高)。

代码实现

java 复制代码
public class Solution {

    private int ans;

    public int heightOfTree(TreeNode root) {
        ans = 0;
        dfs(root, 0);
        return ans;
    }

    private void dfs(TreeNode root, int d) {
        ans = Math.max(ans, d);
        // 左子若为叶子左环引用(root.left.right == root),跳过
        if (root.left != null && root.left.right != root) {
            dfs(root.left, d + 1);
        }
        // 右子若为叶子右环引用(root.right.left == root),跳过
        if (root.right != null && root.right.left != root) {
            dfs(root.right, d + 1);
        }
    }

    public int heightOfTreeIter(TreeNode root) {
        int res = 0;
        java.util.ArrayDeque<TreeNode> stack = new java.util.ArrayDeque<TreeNode>();
        java.util.ArrayDeque<Integer> depths = new java.util.ArrayDeque<Integer>();
        stack.push(root);
        depths.push(0);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            int d = depths.pop();
            res = Math.max(res, d);
            if (node.left != null && node.left.right != node) {
                stack.push(node.left);
                depths.push(d + 1);
            }
            if (node.right != null && node.right.left != node) {
                stack.push(node.right);
                depths.push(d + 1);
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1: [1,2,3,null,null,4,5] -> 叶子 2、4、5
        TreeNode n1 = new TreeNode(1);
        TreeNode n2 = new TreeNode(2);
        TreeNode n3 = new TreeNode(3);
        TreeNode n4 = new TreeNode(4);
        TreeNode n5 = new TreeNode(5);
        n1.left = n2;
        n1.right = n3;
        n3.left = n4;
        n3.right = n5;
        // 叶子顺序 b1=2, b2=4, b3=5
        n2.right = n4;   // b1.右 = b2
        n4.right = n5;   // b2.右 = b3
        n5.right = n2;   // b3.右 = b1
        n2.left = n5;    // b1.左 = bk = b3
        n4.left = n2;    // b2.左 = b1
        n5.left = n4;    // b3.左 = b2
        System.out.println(sol.heightOfTree(n1) == 2 ? "PASS" : "FAIL");
        System.out.println(sol.heightOfTreeIter(n1) == 2 ? "PASS" : "FAIL");

        // 示例 2: [1,2] 单叶子
        TreeNode e1 = new TreeNode(1);
        TreeNode e2 = new TreeNode(2);
        e1.left = e2;
        e2.left = e2;   // 单叶子:左 = bk = 自己
        e2.right = e2;  // 单叶子:右 = b1 = 自己
        System.out.println(sol.heightOfTree(e1) == 1 ? "PASS" : "FAIL");
        System.out.println(sol.heightOfTreeIter(e1) == 1 ? "PASS" : "FAIL");

        // 示例 3: [1,2,3,null,null,4,null,5,6] -> 叶子 2、5、6
        TreeNode r = new TreeNode(1);
        TreeNode l2 = new TreeNode(2);
        TreeNode r3 = new TreeNode(3);
        r.left = l2;
        r.right = r3;
        TreeNode n44 = new TreeNode(4);
        r3.left = n44;
        TreeNode n55 = new TreeNode(5);
        TreeNode n66 = new TreeNode(6);
        n44.left = n55;
        n44.right = n66;
        // 叶子顺序 b1=2, b2=5, b3=6
        l2.right = n55;   // b1.右 = b2
        n55.right = n66;  // b2.右 = b3
        n66.right = l2;   // b3.右 = b1
        l2.left = n66;    // b1.左 = bk = b3
        n55.left = l2;    // b2.左 = b1
        n66.left = n55;   // b3.左 = b2
        System.out.println(sol.heightOfTree(r) == 3 ? "PASS" : "FAIL");
        System.out.println(sol.heightOfTreeIter(r) == 3 ? "PASS" : "FAIL");

        // 随机对拍:随机完全二叉树形状 + 叶子成环,两种写法应一致
        java.util.Random rnd = new java.util.Random(2773);
        boolean ok = true;
        for (int t = 0; t < 2000; t++) {
            int n = 2 + rnd.nextInt(14);
            TreeNode[] nodes = new TreeNode[n];
            for (int i = 0; i < n; i++) nodes[i] = new TreeNode(i + 1);
            // 完全二叉树形状:节点 i 的左右孩子 2i+1 / 2i+2
            for (int i = 0; i < n; i++) {
                if (2 * i + 1 < n) nodes[i].left = nodes[2 * i + 1];
                if (2 * i + 2 < n) nodes[i].right = nodes[2 * i + 2];
            }
            // 找叶子(无孩子)
            java.util.List<TreeNode> leaves = new java.util.ArrayList<TreeNode>();
            for (TreeNode nd : nodes) {
                if (nd.left == null && nd.right == null) leaves.add(nd);
            }
            int k = leaves.size();
            for (int i = 0; i < k; i++) {
                TreeNode cur = leaves.get(i);
                TreeNode next = leaves.get((i + 1) % k);       // 右 = b(i+1)
                TreeNode prev = leaves.get((i - 1 + k) % k);   // 左 = b(i-1)
                cur.left = prev;
                cur.right = next;
            }
            int r1 = sol.heightOfTree(nodes[0]);
            int r2 = sol.heightOfTreeIter(nodes[0]);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch n=" + n + " main=" + r1 + " iter=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2773 全部 PASS" : "2773 存在不一致");
    }
}

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {}

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

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

复杂度分析

时间复杂度:O(n);空间复杂度:O(h)(h 为树高)。


2774. Array Upper Bound(数组的上界 🔒)

题目类型:JavaScript

题目描述

请你编写代码实现一个数组方法,任何数组都可以调用 upperBound() 方法,并返回给定目标数字的最后一个索引。nums 是一个可能包含重复数字的按升序排序的数组。如果在数组中找不到目标数字,则返回-1。

示例 1:

复制代码
输入:nums = [3,4,5], target = 5
输出:2
解释:目标值的最后一个索引是 2

示例 2:**

复制代码
输入:nums = [1,4,5], target = 2
输出:-1
解释:因为数组中没有数字 2,所以返回 -1。

示例 3:**

复制代码
输入:nums = [3,4,6,6,6,6,7], target = 6
输出:5
解释:目标值的最后一个索引是 5

提示:

  • 1 <= nums.length <= 104

  • -104 <= nums[i], target <= 104

  • nums 按升序排序。

**进阶:**你能编写一个时间复杂度为 O(log n) 的算法吗?

解题思路

有序数组上求 t a r g e t target target 最后一次出现的下标。从右线性扫描可以得到答案,题目同时允许对数时间。

二分出第一个大于 t a r g e t target target 的位置,其前一位若等于 t a r g e t target target 即为右边界,否则不存在。

代码实现

typescript 复制代码
declare global {
    interface Array<T> {
        upperBound(target: number): number;
    }
}

Array.prototype.upperBound = function (target: number) {
    let left = 0;
    let right = this.length;
    while (left < right) {
        const mid = (left + right) >> 1;
        if (this[mid] > target) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left > 0 && this[left - 1] == target ? left - 1 : -1;
};

// [3,4,5].upperBound(5); // 2
// [1,4,5].upperBound(2); // -1
// [3,4,6,6,6,6,7].upperBound(6) // 5
typescript 复制代码
declare global {
    interface Array<T> {
        upperBound(target: number): number;
    }
}

Array.prototype.upperBound = function (target: number) {
    return this.lastIndexOf(target);
};

// [3,4,5].upperBound(5); // 2
// [1,4,5].upperBound(2); // -1
// [3,4,6,6,6,6,7].upperBound(6) // 5

复杂度分析

  • 时间复杂度 :O(log n),数组已排序,二分查找。
  • 空间复杂度 :O(1),仅指针。

2775. Undefined to Null(将 undefined 转为 null 🔒)

题目类型:JavaScript

题目描述

给定一个深层嵌套的对象或数组 obj ,并创建该对象 obj 的副本,将其中的任何 undefined 值替换为 null 。

当使用 JSON.stringify() 将对象转换为 JSON 字符串时,undefined 值与 null 值的处理方式不同。该函数有助于确保序列化数据不会出现意外错误。

示例 1:**

复制代码
输入:obj = {"a": undefined, "b": 3}
输出:{"a": null, "b": 3}
解释:obj.a 的值已从 undefined 更改为 null 。

示例 2:**

复制代码
输入:obj = {"a": undefined, "b": ["a", undefined]}
输出:{"a": null,"b": ["a", null]}
解释:obj.a 和 obj.b[1] 的值已从 undefined 更改为 null 。

提示:

  • obj 是一个有效的 JSON 对象或数组

  • 2 <= JSON.stringify(obj).length <= 105

解题思路

把对象或数组中的 u n d e f i n e d undefined undefined 换成 n u l l null null,并递归处理嵌套结构。序列化时 u n d e f i n e d undefined undefined 会被丢键,不能靠 J S O N JSON JSON 往返。

遍历每个键:值仍是对象则先递归,再把仍然等于 u n d e f i n e d undefined undefined 的槽位写成 n u l l null null。数组下标同样被 f o r ⋯ i n for\cdots in for⋯in 覆盖。

代码实现

typescript 复制代码
function undefinedToNull(obj: Record<any, any>): Record<any, any> {
    for (const key in obj) {
        if (typeof obj[key] === 'object') {
            obj[key] = undefinedToNull(obj[key]);
        }
        if (obj[key] === undefined) {
            obj[key] = null;
        }
    }
    return obj;
}

/**
 * undefinedToNull({"a": undefined, "b": 3}) // {"a": null, "b": 3}
 * undefinedToNull([undefined, undefined]) // [null, null]
 */

复杂度分析

  • 时间复杂度 :O(n),深度优先遍历对象所有节点。
  • 空间复杂度 :O(d),d 为嵌套深度(递归栈)。

2776. Convert Callback Based Function to Promise Based Function(转换回调函数为 Promise 函数 🔒)

题目类型:JavaScript

题目描述

编写一个函数,接受另一个函数 fn ,并将基于回调函数的函数转换为基于 Promise 的函数。

promisify 函数接受一个函数 fn ,fn 将回调函数作为其第一个参数,并且还可以接受其他额外的参数。

promisfy 返回一个新函数,新函数会返回一个 Promise 对象。当回调函数被成功调用时,新函数返回的 Promise 对象应该使用原始函数的结果进行解析;当回调函数被调用出现错误时,返回的 Promise 对象应该被拒绝并携带错误信息。最终返回的基于 Promise 的函数应该接受额外的参数作为输入。

以下是一个可以传递给 promisify 的函数示例:

复制代码
function sum(callback, a, b) {
if (a < 0 || b < 0) {
const err = Error('a and b must be positive');
callback(undefined, err);
} else {
callback(a + b);
}
}

这是基于 Promise 的等效代码:

复制代码
async function sum(a, b) {
if (a < 0 || b < 0) {
throw Error('a and b must be positive');
} else {
return a + b;
}
}

示例 1:**

复制代码
输入:
fn = (callback, a, b, c) => {
return callback(a * b * c);
}
args = [1, 2, 3]
输出:{"resolved": 6}
解释:
const asyncFunc = promisify(fn);
asyncFunc(1, 2, 3).then(console.log); // 6

fn 以回调函数作为第一个参数和 args 作为其余参数进行调用。当使用 (1, 2, 3) 调用时,基于 Promise 的 fn 将解析为值 6。

示例 2:**

复制代码
输入:
fn = (callback, a, b, c) => {
callback(a * b * c, "Promise Rejected");
}
args = [4, 5, 6]
输出:{"rejected": "Promise Rejected"}
解释:
const asyncFunc = promisify(fn);
asyncFunc(4, 5, 6).catch(console.log); // "Promise Rejected"

fn 以回调函数作为第一个参数和 args 作为其余参数进行调用。在回调函数的第二个参数中,接受一个错误消息,因此当调用 fn 时,Promise 被拒绝并携带回调函数中提供的错误消息。请注意,不管将什么作为回调函数的第一个参数传递都无关紧要。

提示:

  • 1 <= args.length <= 100

  • 0 <= args[i] <= 104

解题思路

把先接收 n e x t ( d a t a , e r r o r ) next(data, error) next(data,error) 的回调函数改写成返回 Promise 的函数。直接调用原函数无法把成功与失败接到 t h e n / c a t c h then/catch then/catch。

返回的异步函数构造 Promise,把 r e s o l v e / r e j e c t resolve/reject resolve/reject 包进 n e x t next next,再把其余参数传给原 f n fn fn。有 e r r o r error error 则拒绝,否则兑现 d a t a data data。

代码实现

typescript 复制代码
type CallbackFn = (next: (data: number, error: string) => void, ...args: number[]) => void;
type Promisified = (...args: number[]) => Promise<number>;

function promisify(fn: CallbackFn): Promisified {
    return async function (...args) {
        return new Promise((resolve, reject) => {
            fn(
                (data, error) => {
                    if (error) {
                        reject(error);
                    } else {
                        resolve(data);
                    }
                },
                ...args,
            );
        });
    };
}

/**
 * const asyncFunc = promisify(callback => callback(42));
 * asyncFunc().then(console.log); // 42
 */

复杂度分析

  • 时间复杂度 :O(1),包装回调为 Promise。
  • 空间复杂度 :O(1),仅创建一个 Promise。

2777. Date Range Generator(日期范围生成器 🔒)

题目类型:JavaScript

题目描述

现给定起始日期 start 、结束日期 end 和正整数 step ,返回一个生成器对象,该生成器对象按照从 start 到 end(包括 start 和 end )的范围生成日期。

step 的值表示连续生成的日期之间的天数间隔。

所有日期都以字符串格式 YYYY-MM-DD 表示。

示例 1:**

复制代码
输入:start = "2023-04-01", end = "2023-04-04", step = 1
输出:["2023-04-01","2023-04-02","2023-04-03","2023-04-04"]
解释:
const g = dateRangeGenerator(start, end, step);
g.next().value // '2023-04-01'
g.next().value // '2023-04-02'
g.next().value // '2023-04-03'
g.next().value // '2023-04-04'

示例 2:**

复制代码
输入:start = "2023-04-10", end = "2023-04-20", step = 3
输出:["2023-04-10","2023-04-13","2023-04-16","2023-04-19"]
解释:
const g = dateRangeGenerator(start, end, step);
g.next().value // '2023-04-10'
g.next().value // '2023-04-13'
g.next().value // '2023-04-16'
g.next().value // '2023-04-19'

示例 3:**

复制代码
输入:start = "2023-04-10", end = "2023-04-10", step = 1
输出:["2023-04-10"]
解释:
const g = dateRangeGenerator(start, end, step);
g.next().value // '2023-04-10'

提示:

  • new Date(start) <= new Date(end)

  • start 和 end 的日期格式是 YYYY-MM-DD

  • 0 <= 结束日期与开始日期之间的天数差 <= 1500

  • 1 <= step <= 1000

解题思路

按固定步长产出闭区间内的日期字符串。手写儒略日加减容易在月末出错,而 D a t e Date Date 的 s e t D a t e setDate setDate 会自动进位。

从起始日走到结束日(含),每次 y i e l d yield yield ISO 日期,再把日份加上 s t e p step step。生成器按需暂停,不必预先展开整个区间。

代码实现

typescript 复制代码
function* dateRangeGenerator(start: string, end: string, step: number): Generator<string> {
    const startDate = new Date(start);
    const endDate = new Date(end);
    let currentDate = startDate;
    while (currentDate <= endDate) {
        yield currentDate.toISOString().slice(0, 10);
        currentDate.setDate(currentDate.getDate() + step);
    }
}

/**
 * const g = dateRangeGenerator('2023-04-01', '2023-04-04', 1);
 * g.next().value; // '2023-04-01'
 * g.next().value; // '2023-04-02'
 * g.next().value; // '2023-04-03'
 * g.next().value; // '2023-04-04'
 * g.next().done; // true
 */

复杂度分析

  • 时间复杂度 :O(1) 每次 next(),按起始与步长推进日期。
  • 空间复杂度 :O(1),生成器只保存当前日期。

2778. Sum of Squares of Special Elements(特殊元素平方和)

题目描述

给你一个下标从 1 开始、长度为 n 的整数数组 nums。

对 nums 中的元素 nums[i] 而言,如果 n 能够被 i 整除,即 n % i == 0,

则认为 nums[i] 是一个特殊元素。返回 nums 中所有特殊元素的平方和。

示例 1

复制代码
nums = [1,2,3,4] -> 21(特殊元素下标 1、2、4:1²+2²+4²=21)

示例 2

复制代码
nums = [2,7,1,19,18,3] -> 63(下标 1、2、3、6:2²+7²+1²+3²=63)

提示:

  • 1 <= nums.length == n <= 50;1 <= nums[i] <= 50。

解题思路

解法一:直接枚举,主提交版本

遍历下标 i(1-based),若 n % i == 0 则累加 numsi-1 * numsi-1。

解法二:枚举 n 的因子,对照

先求出 n 的所有因子(含 1 与 n),

只对这些下标求和,结果相同。

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

代码实现

java 复制代码
public class Solution {

    public int sumOfSquares(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 1; i <= n; i++) { // 1-based 下标
            if (n % i == 0) {
                ans += nums[i - 1] * nums[i - 1];
            }
        }
        return ans;
    }

    public int sumOfSquaresFactors(int[] nums) {
        int n = nums.length;
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if (n % i == 0) {
                ans += nums[i - 1] * nums[i - 1];
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.sumOfSquares(new int[]{1, 2, 3, 4}) == 21 ? "PASS" : "FAIL");
        System.out.println(sol.sumOfSquares(new int[]{2, 7, 1, 19, 18, 3}) == 63 ? "PASS" : "FAIL");
        System.out.println(sol.sumOfSquares(new int[]{5}) == 25 ? "PASS" : "FAIL");

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2778);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 1 + rnd.nextInt(50);
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) nums[i] = 1 + rnd.nextInt(50);
            int r1 = sol.sumOfSquares(nums);
            int r2 = sol.sumOfSquaresFactors(nums);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch n=" + n + " main=" + r1 + " fac=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2778 全部 PASS" : "2778 存在不一致");
    }
}

复杂度分析

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


2779. Maximum Beauty of an Array After Applying Operation(数组的最大美丽值)

题目描述

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

在一步操作中,你可以对每个下标最多执行一次:将 nums[i] 替换为范围

`nums[i]`-`k`, `nums[i]`+`k` 内的任一整数。

数组的美丽值定义为数组中由相等元素组成的最长子序列的长度。

执行任意次操作后,返回数组可能取得的最大美丽值。

示例 1

复制代码
nums = [4,6,1,2], k = 2 -> 3(全变为 4)

示例 2

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

提示:

  • 1 <= nums.length <= 1e5;0 <= nums[i], k <= 1e5。

解题思路

解法一:排序 + 滑动窗口,主提交版本

所有元素最终能变成同一个值 v 等价于:每个 numsi 满足 |numsi - v| <= k,

即所有相关元素必须落在长度 2k 的区间 v-k, v+k 内。

排序后,能统一为同一值的元素是排序数组中的一段连续区间 l, r,

满足 numsr - numsl <= 2k(此时可选 v = (numsl+numsr)/2 这类中点)。

用滑动窗口求满足 numsr - numsl <= 2k 的最长区间长度。

解法二:枚举目标值 + 前缀计数,对照

值域较小(<= 2e5),开计数数组,

对每个可能的目标 v(min, max 内),统计落在 v-k, v+k 内元素的最多数量。

用差分/前缀和。小规模对拍。

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

代码实现

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

public class Solution {

    public int maximumBeauty(int[] nums, int k) {
        Arrays.sort(nums);
        int n = nums.length;
        int ans = 0;
        int l = 0;
        for (int r = 0; r < n; r++) {
            while (nums[r] - nums[l] > 2 * (long) k) {
                l++;
            }
            ans = Math.max(ans, r - l + 1);
        }
        return ans;
    }

    public int maximumBeautyCnt(int[] nums, int k) {
        int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
        for (int v : nums) {
            min = Math.min(min, v);
            max = Math.max(max, v);
        }
        int LO = Math.max(0, min - k);
        int HI = max + k;
        int size = HI - LO + 1 + 2;
        int[] diff = new int[size + 2];
        for (int v : nums) {
            int a = v - k - LO;
            int b = v + k - LO;
            if (a < 0) a = 0;
            if (b >= size) b = size - 1;
            diff[a]++;
            diff[b + 1]--;
        }
        int ans = 0, cur = 0;
        for (int i = 0; i < size; i++) {
            cur += diff[i];
            ans = Math.max(ans, cur);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.maximumBeauty(new int[]{4, 6, 1, 2}, 2) == 3 ? "PASS" : "FAIL");
        System.out.println(sol.maximumBeauty(new int[]{1, 1, 1, 1}, 10) == 4 ? "PASS" : "FAIL");
        System.out.println(sol.maximumBeauty(new int[]{5}, 0) == 1 ? "PASS" : "FAIL");
        System.out.println(sol.maximumBeauty(new int[]{1, 10}, 5) == 2 ? "PASS" : "FAIL"); // 1->6,10->6

        // 随机对拍
        java.util.Random rnd = new java.util.Random(2779);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 1 + rnd.nextInt(12);
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) nums[i] = rnd.nextInt(30);
            int k = rnd.nextInt(10);
            int r1 = sol.maximumBeauty(nums, k);
            int r2 = sol.maximumBeautyCnt(nums, k);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch nums=" + Arrays.toString(nums) + " k=" + k
                        + " main=" + r1 + " cnt=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2779 全部 PASS" : "2779 存在不一致");
    }
}

复杂度分析

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


2780. Minimum Index of a Valid Split(合法分割的最小下标)

题目描述

如果在长度为 m 的整数数组 arr 中超过一半的元素值为 x,那么我们称 x 是支配元素。

给你一个下标从 0 开始长度为 n 的整数数组 nums,数据保证它含有一个支配元素。

你需要在下标 i 处将 nums 分割成两个数组 nums0...`i` 和 nums`i`+1...`n`-1。

如果一个分割满足:0 <= i < n-1 且两个子数组的支配元素相同,则称它是合法的。

返回一个合法分割的最小下标;如果合法分割不存在,返回 -1。

示例 1

复制代码
nums = [1,2,2,2] -> 2(分割 [1,2,2] / [2])

示例 2

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

示例 3

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

提示:

  • 1 <= nums.length <= 1e5;1 <= nums[i] <= 1e9;nums 有且只有一个支配元素。

解题思路

解法一:统计 + 前缀频次,主提交版本

先扫描一遍找出整个数组的支配元素 d(频次 > n/2)。

再从左到右第二次扫描,维护 d 在左边出现的次数 cntL;

位置 i 合法当且仅当:

  • 左边长度 lenL = i+1,cntL * 2 > lenL(d 是左半边支配元素);
  • 右边长度 lenR = n-i-1,cntR = totalCnt - cntL,cntR * 2 > lenR。
    返回第一个满足的 i。
    解法二:Boyer-Moore 找支配 + 遍历,对照
    用摩尔投票找候选、验证频次,
    再遍历求答案,与主解区别只在找支配元素的方式。
    时间复杂度:O(n);空间复杂度:O(1)。

代码实现

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

public class Solution {

    public int minimumIndex(List<Integer> nums) {
        int n = nums.size();
        // 找支配元素:Boyer-Moore
        int cand = -1, cnt = 0;
        for (int v : nums) {
            if (cnt == 0) cand = v;
            cnt += (v == cand) ? 1 : -1;
        }
        int total = 0;
        for (int v : nums) {
            if (v == cand) total++;
        }
        // 若不存在支配元素(题面保证存在,防御性判断)
        if (total * 2 <= n) return -1;
        int cntL = 0;
        for (int i = 0; i < n - 1; i++) {
            if (nums.get(i) == cand) cntL++;
            int lenL = i + 1;
            int lenR = n - i - 1;
            int cntR = total - cntL;
            if (cntL * 2 > lenL && cntR * 2 > lenR) return i;
        }
        return -1;
    }

    public int minimumIndexMap(List<Integer> nums) {
        int n = nums.size();
        Map<Integer, Integer> totalMap = new HashMap<Integer, Integer>();
        for (int v : nums) totalMap.put(v, totalMap.getOrDefault(v, 0) + 1);
        int cand = -1, total = 0;
        for (Map.Entry<Integer, Integer> e : totalMap.entrySet()) {
            if (e.getValue() > total) {
                total = e.getValue();
                cand = e.getKey();
            }
        }
        int cntL = 0;
        for (int i = 0; i < n - 1; i++) {
            if (nums.get(i) == cand) cntL++;
            int lenL = i + 1;
            int lenR = n - i - 1;
            int cntR = total - cntL;
            if (cntL * 2 > lenL && cntR * 2 > lenR) return i;
        }
        return -1;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.minimumIndex(java.util.Arrays.asList(1, 2, 2, 2)) == 2 ? "PASS" : "FAIL");
        System.out.println(sol.minimumIndex(java.util.Arrays.asList(2, 1, 3, 1, 1, 1, 7, 1, 2, 1)) == 4 ? "PASS" : "FAIL");
        System.out.println(sol.minimumIndex(java.util.Arrays.asList(3, 3, 3, 3, 7, 2, 2)) == -1 ? "PASS" : "FAIL");
        System.out.println(sol.minimumIndex(java.util.Arrays.asList(1, 1)) == 0 ? "PASS" : "FAIL");

        // 随机对拍:构造含支配元素的数组
        java.util.Random rnd = new java.util.Random(2780);
        boolean ok = true;
        for (int t = 0; t < 3000; t++) {
            int n = 1 + rnd.nextInt(10);
            int[] arr = new int[n];
            int d = 1 + rnd.nextInt(5);
            int cntD = n / 2 + 1; // 保证支配
            java.util.List<Integer> pool = new java.util.ArrayList<Integer>();
            for (int i = 0; i < n; i++) pool.add(rnd.nextInt(5) + 1);
            // 覆盖前 cntD 个为 d
            for (int i = 0; i < cntD; i++) pool.set(i, d);
            java.util.Collections.shuffle(pool, rnd);
            int r1 = sol.minimumIndex(pool);
            int r2 = sol.minimumIndexMap(pool);
            if (r1 != r2) {
                ok = false;
                System.out.println("Mismatch pool=" + pool + " main=" + r1 + " map=" + r2);
                break;
            }
        }
        System.out.println(ok ? "2780 全部 PASS" : "2780 存在不一致");
    }
}

复杂度分析

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

相关推荐
IT枫斗者枫哥37 分钟前
子线程断言失败,JUnit 为什么还显示通过?
java·后端
考虑考虑3 小时前
JDK27中的Set.ofLazy
java·后端·java ee
文丑颜不良啊3 小时前
LeetCode2841~2860题目解析
java·leetcode
小南家的青蛙3 小时前
Tomcat与Jetty深度对比
java·tomcat·jetty
乌暮5 小时前
Spring AOP 与 Spring 事务:用大白话彻底理解原理与实践
java·数据库·spring boot·后端·sql·学习·spring
YDS8295 小时前
Small Spring IOC篇:容器事件及其监听器
java·spring
金銀銅鐵5 小时前
带您看懂字节码指令(三):一行加法里有什么
java·jvm
文丑颜不良啊5 小时前
LeetCode2921~2940题目解析
java·leetcode
北漂人Java6 小时前
基于 Spring AI Alibaba WorkFlow 构建 Java 生产级 AI Agent
java·人工智能·spring