LeetCode2821~2840题目解析

LeetCode 2821-2840. 题解合集

文章目录


2821. Delay the Resolution of Each Promise(延迟每个 Promise 对象的解析)

题目类型:JavaScript

题目描述

给定一个函数数组 functions 和一个数字 ms,返回一个新的函数数组。

复制代码
- `functions` 是一个返回 `Promise` 对象的函数数组。

- `ms` 表示延迟的时间,以毫秒为单位。它决定了在新数组中的每个函数返回的 `Promise` 在解析之前等待的时间。

新数组中的每个函数应该返回一个 Promise 对象,在延迟了 ms 毫秒后解析,保持原始 functions 数组中的顺序。delayAll 函数应确保从 functions 中的每个 Promise 都被延迟执行,形成返回延迟的 Promise 的函数的新数组。

示例 1:**

复制代码
输入:
functions = [
   () => new Promise((resolve) => setTimeout(resolve, 30))
],
ms = 50
输出:[80]
解释:数组中的 Promise 在 30 毫秒后解析,但被延迟了 50 毫秒,所以总共延迟了 30 毫秒 + 50 毫秒 = 80 毫秒。

示例 2:**

复制代码
输入:
functions = [
    () => new Promise((resolve) => setTimeout(resolve, 50)),
    () => new Promise((resolve) => setTimeout(resolve, 80))
],
ms = 70
输出:[120,150]
解释:数组中的 Promise 在 50 毫秒和 80 毫秒后解析,但它们被延迟了 70 毫秒,所以总共延迟了 50 毫秒 + 70 毫秒 = 120 毫秒 和 80 毫秒 + 70 毫秒 = 150 毫秒。

提示:

复制代码
- `functions` 是一个返回 `Promise` 对象的函数数组

- `10 <= ms <= 500`

- `1 <= functions.length <= 10`

解题思路

解法一:async/await 包装(主提交版本)

  • 对 functions 中的每个函数,返回一个 async () => { await sleep(ms); return await fn(); };
  • sleep 用 new Promise(r => setTimeout(r, ms)) 实现;
  • 保持原始顺序,每个 Promise 都在额外延迟 ms 毫秒后解析。

代码实现

javascript 复制代码
const delayAll = (functions, ms) =>
    functions.map(
        (fn) =>
            async function () {
                await new Promise((resolve) => setTimeout(resolve, ms));
                return await fn();
            },
    );

复杂度分析

  • 时间复杂度 :O(n),n 为输入规模,单次遍历(或递归)。
  • 空间复杂度 :O(n),需要存储中间结果/计数器。

2822. Inversion of Object(对象反转)

题目类型:JavaScript

题目描述

给定一个对象 obj,返回一个反转的对象 invertedObj。

invertedObj 应该以 obj 的键作为值,以 obj 的值作为键。题目保证 obj 中的值仅为字符串。该函数应该处理重复值,也就是说,如果在 obj 中有多个具有相同值的键,那么 invertedObj 应该将该值映射到一个包含所有相应键的数组中。

示例 1:**

复制代码
输入:obj = {"a": "1", "b": "2", "c": "3", "d": "4"}
输出:invertedObj = {"1": "a", "2": "b", "3": "c", "4": "d"}
解释:obj 中的键变成 invertedObj 中的值,而 obj 中的值变成 invertedObj 中的键。

示例 2:**

复制代码
输入:obj = {"a": "1", "b": "2", "c": "2", "d": "4"}
输出:invertedObj = {"1": "a", "2": ["b", "c"], "4": "d"}
解释:在 obj 中有两个具有相同值的键,invertedObj 将该值映射到一个包含所有对应键的数组中。

示例 3:**

复制代码
输入:obj = ["1", "2", "3", "4"]
输出:invertedObj = {"1": "0", "2": "1", "3": "2", "4": "3"}
解释:数组也是对象,因此数组已经转换为一个对象,obj 中的键(索引)变成了 invertedObj 中的值,而 obj 中的值变成了 invertedObj 中的键。

提示:

复制代码
- `obj` 是一个有效的 `JSON` 对象

- `typeof obj[key] === "string"`

- `2 <= JSON.stringify(obj).length <= 10**5`

解题思路

解法一:遍历键值反转(主提交版本)

  • 遍历 obj 的每个键值对,以值为键、原键为值构造 invertedObj;
  • 若值已出现过(重复值),把该键追加到对应数组中:
    invertedObj[val] = [...(invertedObj[val] ?? []), key];
  • 仅出现一次的值仍映射为单个字符串(非数组)。

代码实现

javascript 复制代码
var invertObject = function (obj) {
    const res = {};
    for (const key in obj) {
        const val = obj[key];
        res[val] = res[val] === undefined ? key : [...(Array.isArray(res[val]) ? res[val] : [res[val]]), key];
    }
    return res;
};

复杂度分析

  • 时间复杂度 :O(n),n 为输入规模,单次遍历(或递归)。
  • 空间复杂度 :O(n),需要存储中间结果/计数器。

2823. Deep Object Filter(深度对象筛选)

题目类型:JavaScript

题目描述

给定一个对象 obj 和一个函数 fn,返回一个经过筛选的对象 filteredObject。

函数 deepFilter 应该在对象 obj 上执行深度筛选操作。深度筛选操作应该移除筛选函数 fn 输出为 false 的属性,以及在键被移除后仍然存在的任何空对象或数组。

如果深度筛选操作导致对象或数组为空,没有剩余属性,deepFilter 应该返回 undefined,表示在 filteredObject 中没有有效数据。

示例 1:**

复制代码
输入:
obj = [-5, -4, -3, -2, -1, 0, 1],
fn = (x) => x > 0
输出:[1]
解释:所有不大于 0 的值都被移除。

示例 2:**

复制代码
输入:
obj = {"a": 1, "b": "2", "c": 3, "d": "4", "e": 5, "f": 6, "g": {"a": 1}},
fn = (x) => typeof x === "string"
输出:{"b":"2","d":"4"}
解释:所有值不是字符串的键都被移除。在筛选过程中移除键后,任何导致为空的对象也被移除。

示例 3:**

复制代码
输入:
obj = [-1, [-1, -1, 5, -1, 10], -1, [-1], [-5]],
fn = (x) => x > 0
输出:[[5,10]]
解释:所有不大于 0 的值都被移除。在筛选过程中移除值后,任何导致为空的数组也被移除。

示例 4:**

复制代码
输入:
obj = [[[[5]]]],
fn = (x) => Array.isArray(x)
输出:undefined

提示:

复制代码
- `fn` 是一个返回布尔值的函数

- `obj` 是一个有效的 `JSON` 对象

- `2 <= JSON.stringify(obj).length <= 10**5`

解题思路

解法一:递归深度筛选(主提交版本)

  • 对数组:逐元素递归调用 deepFilter,过滤掉返回 undefined 的元素;
  • 对对象:逐键递归调用,删除值为 undefined 的属性;
  • 筛选完成后:空数组 / 空对象(无剩余键)返回 undefined;
  • 叶子值直接调用 fn 判定保留与否。

代码实现

javascript 复制代码
var deepFilter = function (obj, fn) {
    if (Array.isArray(obj)) {
        const res = [];
        for (const v of obj) {
            const r = deepFilter(v, fn);
            if (r !== undefined) res.push(r);
        }
        return res.length ? res : undefined;
    }
    if (obj !== null && typeof obj === 'object') {
        const res = {};
        for (const k in obj) {
            const r = deepFilter(obj[k], fn);
            if (r !== undefined) res[k] = r;
        }
        return Object.keys(res).length ? res : undefined;
    }
    return fn(obj) ? obj : undefined;
};

复杂度分析

  • 时间复杂度 :O(n),n 为输入规模,单次遍历(或递归)。
  • 空间复杂度 :O(n),需要存储中间结果/计数器。

2824. Count Pairs Whose Sum is Less than Target(统计和小于目标的下标对数目)

题目描述

给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 target ,

请你返回满足 0 <= i < j < n 且 nums[i] + nums[j] < target 的下标对 (i, j) 的数目。

示例 1

  • 输入 :nums = -1,1,2,3,1, target = 2
  • 输出:3
  • 解释:满足条件的是 (0,1)、(0,2)、(0,4),共 3 对。

示例 2

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

解题思路

解法一:排序 + 双指针

nums 本身下标顺序无关紧要(只要求 i<j 的"对"数),排序后对每个 i,

找最大的 j 使 numsi+numsj < target,则 i 的对数为 j-i(去重)。

双指针从两端逼近:i 从左、j 从右;若 numsi+numsj < target,则贡献 j-i 个对,i 前进;

否则 j 后退。

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

解法二:两层暴力

直接枚举所有 i<j 检查条件。时间复杂度 O(n^2),空间复杂度 O(1),仅用于对拍。

代码实现

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

public class Solution {
    // ---------- 解法一:排序 + 双指针(主提交版本) ----------
    public int countPairs(List<Integer> nums, int target) {
        int[] a = new int[nums.size()];
        for (int i = 0; i < a.length; i++) a[i] = nums.get(i);
        Arrays.sort(a);
        int ans = 0;
        int i = 0, j = a.length - 1;
        while (i < j) {
            if (a[i] + a[j] < target) {
                ans += j - i;
                i++;
            } else {
                j--;
            }
        }
        return ans;
    }

    // ---------- 解法二:暴力(对照) ----------
    public int countPairsBrute(List<Integer> nums, int target) {
        int n = nums.size();
        int ans = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (nums.get(i) + nums.get(j) < target) ans++;
            }
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.countPairs(Arrays.asList(-1, 1, 2, 3, 1), 2) == 3, "2824 例1");
        check(s.countPairs(Arrays.asList(-6, 2, 5, -2, -7, -1, 3), -2) == 10, "2824 例2");

        java.util.Random rnd = new java.util.Random(42);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(20);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(-50 + rnd.nextInt(101));
            int target = -50 + rnd.nextInt(101);
            int m1 = s.countPairs(a, target);
            int m2 = s.countPairsBrute(a, target);
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 a=" + a + " target=" + target + " 双指针=" + m1 + " 暴力=" + m2);
                break;
            }
        }
        check(allOk, "2824 随机对拍");
        System.out.println("2824 all tests done");
    }

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

复杂度分析

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

直接枚举所有 i<j 检查条件。时间复杂度 O(n^2),空间复杂度 O(1),仅用于对拍。


2825. Make String a Subsequence Using Cyclic Increments(循环增长使字符串子序列等于另一个字符串)

题目描述

给你一个下标从 0 开始的字符串 str1 和 str2 。

一次操作中,你选择 str1 中的若干下标。对于选中的每一个下标 i ,你将 str1[i] 循环递增,变成下一个字符。

也就是说 'a' 变成 'b' ,'b' 变成 'c' ,以此类推,'z' 变成 'a' 。

如果执行以上操作至多一次,可以让 str2 成为 str1 的子序列,请你返回 true ,否则返回 false 。

注意:一个字符串的子序列指的是从原字符串中删除一些字符后,剩下字符按照原本先后顺序组成的新字符串。

示例 1

  • 输入 :str1 = "abc", str2 = "ad"
  • 输出 :true
  • 解释 :选择 str1 中的下标 2 ,将 str1[2] 循环递增得到 'd' ,str1 变成 "abd" 且 str2 是子序列。

示例 2

  • 输入 :str1 = "zc", str2 = "ad"
  • 输出 :true

示例 3

  • 输入 :str1 = "ab", str2 = "d"
  • 输出 :false
    提示:
  • 1 <= str1.length <= 10^5
  • 1 <= str2.length <= 10^5
  • str1 和 str2 只包含小写英文字母。

解题思路

解法一:双指针贪心

用指针 p1 扫 str1、p2 扫 str2。对 str2p2 的目标字符 c,在 str1 中找到第一个

满足「等于 c 或 c-1(循环前一个)」的字符,因为每个字符最多被循环递增一次

(只变下一字符,不能再变回)。若找到则 p1、p2 各前进;否则返回 false。

时间复杂度 O(|str1|+|str2|),空间复杂度 O(1)。

解法二:BFS/回溯模拟

枚举所有「选哪些下标递增」的组合(2^n 种)检查子序列,仅适用于极短字符串,用递归穷举对照。

代码实现

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

public class Solution {
    // ---------- 解法一:双指针贪心(主提交版本) ----------
    public boolean canMakeSubsequence(String str1, String str2) {
        int p1 = 0, p2 = 0;
        int n = str1.length();
        while (p2 < str2.length()) {
            // 找 str1 中第一个能匹配 str2[p2] 的位置
            while (p1 < n && !match(str1.charAt(p1), str2.charAt(p2))) {
                p1++;
            }
            if (p1 == n) return false;
            p1++;
            p2++;
        }
        return true;
    }

    // c1 经过至多一次循环递增能否变成 c2(即 c1 == c2 或 c1+1 == c2,z 后回到 a)
    private boolean match(char c1, char c2) {
        if (c1 == c2) return true;
        char next = (char) ((c1 - 'a' + 1) % 26 + 'a');
        return next == c2;
    }

    // ---------- 解法二:递归穷举(小规模对照) ----------
    public boolean canMakeSubsequenceBrute(String str1, String str2) {
        return dfs(str1, str2, 0, 0);
    }

    // 从 str1 的 pos 位置开始,尝试匹配 str2 的 idx 位置
    private boolean dfs(String str1, String str2, int pos, int idx) {
        if (idx == str2.length()) return true;
        if (pos == str1.length()) return false;
        // 不选 str1[pos]
        if (dfs(str1, str2, pos + 1, idx)) return true;
        // 选 str1[pos](原始或递增后)匹配 str2[idx]
        if (match(str1.charAt(pos), str2.charAt(idx)) && dfs(str1, str2, pos + 1, idx + 1)) return true;
        return false;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.canMakeSubsequence("abc", "ad"), "2825 例1 true");
        check(s.canMakeSubsequence("zc", "ad"), "2825 例2 true");
        check(!s.canMakeSubsequence("ab", "d"), "2825 例3 false");

        Random rnd = new Random(43);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n1 = 1 + rnd.nextInt(8);
            int n2 = 1 + rnd.nextInt(8);
            StringBuilder sb1 = new StringBuilder(), sb2 = new StringBuilder();
            for (int i = 0; i < n1; i++) sb1.append((char) ('a' + rnd.nextInt(26)));
            for (int i = 0; i < n2; i++) sb2.append((char) ('a' + rnd.nextInt(26)));
            String a = sb1.toString(), b = sb2.toString();
            boolean x = s.canMakeSubsequence(a, b);
            boolean y = s.canMakeSubsequenceBrute(a, b);
            if (x != y) {
                allOk = false;
                System.out.println("对拍失败 str1=" + a + " str2=" + b + " 贪心=" + x + " 穷举=" + y);
                break;
            }
        }
        check(allOk, "2825 随机对拍");
        System.out.println("2825 all tests done");
    }

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

复杂度分析

时间复杂度 O(|str1|+|str2|),空间复杂度 O(1)。


2826. Sorting Three Groups(将三个组排序)

题目描述

给你一个整数数组 nums 。nums 的每个元素是 1,2 或 3。在每次操作中,你可以删除 nums 中的一个元素。

返回使 nums 成为非递减顺序所需操作数的最小值。

示例 1

  • 输入 :nums = 2,1,3,2,1
  • 输出:3
  • 解释 :删除 nums[0],nums[2] 和 nums[3] 后得到 1,2,1。

示例 2

  • 输入 :nums = 1,3,2,1,3,3
  • 输出:2
  • 解释 :删除 nums[1] 和 nums[2] 后得到 1,1,3,3。

示例 3

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

解题思路

解法一:LIS 最长非递减子序列

删除最少的元素使剩余序列非递减 = n - 最长非递减子序列长度(LNDS)。

标准 DP:dpi = 以 i 结尾的最长非递减长度,dpi = max(dpj) + 1 (j<i 且 aj<=ai)。

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

进阶 O(n log n):因为值域只有 3,可退化为「前缀选择枚举」,见解法三。

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

枚举所有保留子集检查非递减,仅限 n <= 15 的小规模对拍。

解法三:枚举分组边界 O(n)

最终序列形如 1...1 2...2 3...3(可缺省某些组)。枚举两个边界位置,

统计「原数组与最终形态不同的元素数」即需删除数。O(n + 9) 常数。作为第三验证。

代码实现

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

public class Solution {
    // ---------- 解法一:LNDS(主提交版本) ----------
    public int minimumOperations(List<Integer> nums) {
        int n = nums.size();
        int[] dp = new int[n];
        int best = 0;
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums.get(j) <= nums.get(i)) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            best = Math.max(best, dp[i]);
        }
        return n - best;
    }

    // ---------- 解法二:枚举删除子集(小规模对照) ----------
    public int minimumOperationsBrute(List<Integer> nums) {
        int n = nums.size();
        int best = 0;
        for (int mask = 0; mask < (1 << n); mask++) {
            int cnt = Integer.bitCount(mask);
            if (cnt <= best) continue;
            boolean ok = true;
            int prev = 0, first = -1;
            for (int i = 0; i < n; i++) {
                if (((mask >> i) & 1) != 0) {
                    int v = nums.get(i);
                    if (first != -1 && v < prev) { ok = false; break; }
                    prev = v;
                    first = 1;
                }
            }
            if (ok) best = cnt;
        }
        return n - best;
    }

    // ---------- 解法三:枚举边界 O(n)(对照) ----------
    public int minimumOperationsEnum(List<Integer> nums) {
        int n = nums.size();
        int res = n;
        // 最终形态:前 a 个是 1,中间 b 个是 2,后 n-a-b 个是 3
        for (int a = 0; a <= n; a++) {
            for (int b = 0; a + b <= n; b++) {
                int changes = 0;
                for (int i = 0; i < n; i++) {
                    int want = (i < a) ? 1 : (i < a + b ? 2 : 3);
                    if (nums.get(i) != want) changes++;
                }
                res = Math.min(res, changes);
            }
        }
        return res;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.minimumOperations(Arrays.asList(2, 1, 3, 2, 1)) == 3, "2826 例1");
        check(s.minimumOperations(Arrays.asList(1, 3, 2, 1, 3, 3)) == 2, "2826 例2");
        check(s.minimumOperations(Arrays.asList(2, 2, 2, 2, 3, 3)) == 0, "2826 例3");
        check(s.minimumOperations(Arrays.asList(2, 2, 1, 1)) == 2, "2826 附加");

        java.util.Random rnd = new java.util.Random(44);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(13);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(1 + rnd.nextInt(3));
            int m1 = s.minimumOperations(a);
            int m2 = s.minimumOperationsBrute(a);
            int m3 = s.minimumOperationsEnum(a);
            if (m1 != m2 || m1 != m3) {
                allOk = false;
                System.out.println("对拍失败 a=" + a + " LNDS=" + m1 + " 暴力=" + m2 + " 边界=" + m3);
                break;
            }
        }
        check(allOk, "2826 三方对拍");
        System.out.println("2826 all tests done");
    }

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

复杂度分析

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


2827. Number of Beautiful Integers in the Range(范围中美丽整数的数目)

题目描述

给你正整数 low ,high 和 k 。

如果一个数满足以下两个条件,那么它是美丽的:

  • 偶数数位的数目与奇数数位的数目相同。
  • 这个整数可以被 k 整除。
    请你返回范围 `low`, `high` 中美丽整数的数目。

示例 1

  • 输入 :low = 10, high = 20, k = 3
  • 输出:2
  • 解释:范围中有 2 个美丽数字:12,18。

示例 2

  • 输入 :low = 1, high = 10, k = 1
  • 输出:1
  • 解释:给定范围中有 1 个美丽数字:10。

示例 3

  • 输入 :low = 5, high = 5, k = 2
  • 输出 :0
    提示:
  • 1 <= low <= high <= 10^9
  • 1 <= k <= 20

解题思路

解法一:数位 DP

统计 1, x 内美丽整数个数 f(x),答案 = f(high) - f(low - 1)。

f(x) 用数位 DP:dfs(pos, rem, diff, tight, started),其中

pos 当前位、rem 是当前数对 k 的余数、diff = 偶数位个数 - 奇数位个数(转偏移量)、

tight 是否贴上界、started 是否已开始(处理前导零)。

转移:每位填 d,rem' = (rem*10 + d) % k,diff' = diff + (d 为偶数 ? +1 : -1)。

结束时判断 rem == 0 且 diff == 0(偏移归零)。

注意 diff 偏移范围 -10...10(最多 10 位),用 diff + 11 作下标。

时间复杂度 O(位数 * k * 偏移),空间复杂度同。

解法二:暴力枚举

直接遍历 low, high 每个数检查两个条件(用于小范围对拍)。

代码实现

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

public class Solution {
    // ---------- 解法一:数位 DP(主提交版本) ----------
    public int numberOfBeautifulIntegers(int low, int high, int k) {
        return f(high, k) - f(low - 1, k);
    }

    private int f(int x, int k) {
        if (x <= 0) return 0;
        char[] s = String.valueOf(x).toCharArray();
        int n = s.length;
        // memo[pos][rem][off]  off = diff + 11 (0..22),仅非贴上下界时缓存
        Integer[][][] memo = new Integer[n][k][23];
        return dfs(s, 0, 0, 0, true, false, k, memo);
    }

    private int dfs(char[] s, int pos, int rem, int off, boolean tight, boolean started, int k,
                    Integer[][][] memo) {
        if (pos == s.length) {
            return (started && rem == 0 && off == 0) ? 1 : 0;
        }
        if (!tight && started && memo[pos][rem][off + 11] != null) {
            return memo[pos][rem][off + 11];
        }
        int limit = tight ? (s[pos] - '0') : 9;
        int cnt = 0;
        for (int d = 0; d <= limit; d++) {
            boolean nt = tight && (d == limit);
            if (!started) {
                if (d == 0) {
                    cnt += dfs(s, pos + 1, rem, off, nt, false, k, memo);
                } else {
                    int nrem = (rem * 10 + d) % k;
                    int noff = off + (d % 2 == 0 ? 1 : -1);
                    cnt += dfs(s, pos + 1, nrem, noff, nt, true, k, memo);
                }
            } else {
                int nrem = (rem * 10 + d) % k;
                int noff = off + (d % 2 == 0 ? 1 : -1);
                cnt += dfs(s, pos + 1, nrem, noff, nt, true, k, memo);
            }
        }
        if (!tight && started) {
            memo[pos][rem][off + 11] = cnt;
        }
        return cnt;
    }

    // ---------- 解法二:暴力(对照) ----------
    public int numberOfBeautifulIntegersBrute(int low, int high, int k) {
        int ans = 0;
        for (int v = low; v <= high; v++) {
            int even = 0, odd = 0, tmp = v;
            while (tmp > 0) {
                int d = tmp % 10;
                if (d % 2 == 0) even++;
                else odd++;
                tmp /= 10;
            }
            if (even == odd && v % k == 0) ans++;
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.numberOfBeautifulIntegers(10, 20, 3) == 2, "2827 例1");
        check(s.numberOfBeautifulIntegers(1, 10, 1) == 1, "2827 例2");
        check(s.numberOfBeautifulIntegers(5, 5, 2) == 0, "2827 例3");

        java.util.Random rnd = new java.util.Random(45);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int hi = 1 + rnd.nextInt(20000);
            int lo = 1 + rnd.nextInt(hi);
            int k = 1 + rnd.nextInt(20);
            int a = s.numberOfBeautifulIntegers(lo, hi, k);
            int b = s.numberOfBeautifulIntegersBrute(lo, hi, k);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 lo=" + lo + " hi=" + hi + " k=" + k + " dp=" + a + " 暴力=" + b);
                break;
            }
        }
        check(allOk, "2827 随机对拍");
        System.out.println("2827 all tests done");
    }

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

复杂度分析

时间复杂度 O(位数 * k * 偏移),空间复杂度同。


2828. Check if a String Is an Acronym of Words(判别首字母缩略词)

题目描述

给你一个字符串数组 words 和一个字符串 s ,请你判断 s 是不是 words 的首字母缩略词。

如果可以按顺序串联 words 中每个字符串的第一个字符形成字符串 s ,则认为 s 是 words 的首字母缩略词。

例如,"ab" 可以由 "`apple`", "`banana`" 形成,但是无法从 "`bear`", "`aardvark`" 形成。

如果 s 是 words 的首字母缩略词,返回 true ;否则,返回 false 。

示例 1

  • 输入 :words = "`alice`","`bob`","`charlie`", s = "abc"
  • 输出 :true

示例 2

  • 输入 :words = "an","`apple`", s = "a"
  • 输出 :false

示例 3

  • 输入 :words = "`never`","`gonna`","`give`","`up`","on","`you`", s = "ngguoy"
  • 输出 :true
    提示:
  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 10
  • 1 <= s.length <= 100
  • words[i] 和 s 由小写英文字母组成

解题思路

解法一:拼缩写后比较

遍历 words,把每个单词首字母拼成 StringBuilder,最后与 s 比较。

时间复杂度 O(总字符数),空间复杂度 O(n)。

解法二:逐字符比对

直接遍历校验 s 的第 i 个字符是否等于 wordsi 的首字符,同时检查长度一致。

时间复杂度 O(n),空间复杂度 O(1)。作为对照。

代码实现

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

public class Solution {
    // ---------- 解法一:拼缩写比较(主提交版本) ----------
    public boolean isAcronym(List<String> words, String s) {
        if (words.size() != s.length()) return false;
        for (int i = 0; i < words.size(); i++) {
            if (words.get(i).charAt(0) != s.charAt(i)) return false;
        }
        return true;
    }

    // ---------- 解法二:拼接再比较(对照) ----------
    public boolean isAcronymBrute(List<String> words, String s) {
        StringBuilder sb = new StringBuilder();
        for (String w : words) sb.append(w.charAt(0));
        return sb.toString().equals(s);
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.isAcronym(Arrays.asList("alice", "bob", "charlie"), "abc"), "2828 例1 true");
        check(!s.isAcronym(Arrays.asList("an", "apple"), "a"), "2828 例2 false");
        check(s.isAcronym(Arrays.asList("never", "gonna", "give", "up", "on", "you"), "ngguoy"), "2828 例3 true");

        java.util.Random rnd = new java.util.Random(46);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(10);
            List<String> words = new ArrayList<>();
            StringBuilder acro = new StringBuilder();
            for (int i = 0; i < n; i++) {
                int len = 1 + rnd.nextInt(4);
                StringBuilder w = new StringBuilder();
                for (int j = 0; j < len; j++) w.append((char) ('a' + rnd.nextInt(26)));
                words.add(w.toString());
                acro.append(w.charAt(0));
            }
            String s1 = acro.toString();
            String s2 = s1 + (char) ('a' + rnd.nextInt(26)); // 可能多一个字符
            if (rnd.nextBoolean()) s2 = s2.substring(0, s2.length() - 1);
            else if (s2.length() > 1 && rnd.nextBoolean()) {
                // 随机改一个字符
                char[] c = s2.toCharArray();
                c[rnd.nextInt(c.length)] = (char) ('a' + rnd.nextInt(26));
                s2 = new String(c);
            }
            boolean a = s.isAcronym(words, s1);
            boolean b = s.isAcronymBrute(words, s1);
            boolean c1 = s.isAcronym(words, s2);
            boolean c2 = s.isAcronymBrute(words, s2);
            if (a != b || c1 != c2) {
                allOk = false;
                System.out.println("对拍失败 words=" + words + " 原=" + a + "/" + b);
                break;
            }
        }
        check(allOk, "2828 随机对拍");
        System.out.println("2828 all tests done");
    }

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

复杂度分析

时间复杂度 O(总字符数),空间复杂度 O(n)。

时间复杂度 O(n),空间复杂度 O(1)。作为对照。


2829. Determine the Minimum Sum of a k-avoiding Array(k-avoiding 数组的最小总和)

题目描述

给你两个整数 n 和 k 。

对于一个由不同正整数组成的数组,如果其中不存在任何求和等于 k 的不同元素对,则称其为 k-avoiding 数组。

返回长度为 n 的 k-avoiding 数组的可能的最小总和。

示例 1

  • 输入 :n = 5, k = 4
  • 输出:18
  • 解释 :设若 k-avoiding 数组为 1,2,4,5,6 ,其元素总和为 18 。

示例 2

  • 输入 :n = 2, k = 6
  • 输出:3
  • 解释 :可以构造数组 1,2 ,其元素总和为 3 。
    提示:
  • 1 <= n, k <= 50

解题思路

解法一:贪心取最小合法数

从小到大逐个尝试正整数,若 x 与已选集合中任意元素的和 == k 则跳过,否则选入,直到选满 n 个。

为什么贪心正确:最小和的 k-avoiding 数组一定是字典序最小的一组互不冲突正整数(交换论证)。

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

解法二:枚举所有组合

用 DFS/回溯枚举长度为 n 的互异正整数组合,检查是否 k-avoiding,取最小和(候选值上界

设为 k+n 足够,因为最多选 n 个,第 n 个 <= k+n-1 必然可行)。用于小规模对拍。

代码实现

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

public class Solution {
    // ---------- 解法一:贪心(主提交版本) ----------
    public int minimumSum(int n, int k) {
        boolean[] used = new boolean[2 * k + 60];
        int sum = 0;
        int cnt = 0;
        for (int x = 1; cnt < n; x++) {
            if (k - x >= 1 && k - x < used.length && used[k - x] && k - x != x) {
                continue; // 与已选元素和为 k
            }
            used[x] = true;
            sum += x;
            cnt++;
        }
        return sum;
    }

    // ---------- 解法二:回溯枚举(对照) ----------
    private int bestSum;
    private int nVal, kVal;

    public int minimumSumBrute(int n, int k) {
        nVal = n;
        kVal = k;
        bestSum = Integer.MAX_VALUE;
        dfs(1, 0, 0, new boolean[200]);
        return bestSum;
    }

    private void dfs(int start, int cnt, int sum, boolean[] used) {
        if (cnt == nVal) {
            bestSum = Math.min(bestSum, sum);
            return;
        }
        if (sum >= bestSum) return;
        for (int x = start; x <= 120; x++) {
            // 检查是否与已选元素和为 k
            boolean conflict = false;
            for (int y = 1; y < x; y++) {
                if (used[y] && y + x == kVal) { conflict = true; break; }
            }
            if (conflict) continue;
            used[x] = true;
            dfs(x + 1, cnt + 1, sum + x, used);
            used[x] = false;
        }
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.minimumSum(5, 4) == 18, "2829 例1");
        check(s.minimumSum(2, 6) == 3, "2829 例2");
        check(s.minimumSum(1, 1) == 1, "2829 附加1");

        java.util.Random rnd = new java.util.Random(47);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int n = 1 + rnd.nextInt(6);
            int k = 1 + rnd.nextInt(12);
            int a = s.minimumSum(n, k);
            int b = s.minimumSumBrute(n, k);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 n=" + n + " k=" + k + " 贪心=" + a + " 回溯=" + b);
                break;
            }
        }
        check(allOk, "2829 随机对拍");
        System.out.println("2829 all tests done");
    }

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

复杂度分析

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


2830. Maximize the Profit as the Salesman(销售利润最大化)

题目描述

给你一个整数 n 表示数轴上的房屋数量,编号从 0 到 n - 1 。

另给你一个二维整数数组 offers ,其中 offers[i] = `starti`, `endi`, `goldi`

表示第 i 个买家想要以 goldi 枚金币的价格购买从 starti 到 endi 的所有房屋。

作为一名销售,你需要有策略地选择并销售房屋使自己的收入最大化。

返回你可以赚取的金币的最大数目。

注意:同一所房屋不能卖给不同的买家,并且允许保留一些房屋不进行出售。

示例 1

  • 输入 :n = 5, offers = \[0,0,1,0,2,2,1,3,2]
  • 输出:3

示例 2

  • 输入 :n = 5, offers = \[0,0,1,0,2,10,1,3,2]
  • 输出 :10
    提示:
  • 1 <= n <= 10^5
  • 1 <= offers.length <= 10^5
  • offers[i].length == 3
  • 0 <= starti <= endi <= n - 1
  • 1 <= goldi <= 10^3

解题思路

解法一:DP + 按结束位置分组

dpi = 只考虑前 i 间房屋(编号 0...i-1)的最大收益。

转移:dpi+1 = max(dpi, dpi+1); 对每个以 i 结尾的 offer s, i, g,

dpi+1 = max(dpi+1, dps + g)(s 之前房屋与 s...i 不重叠)。

先把 offers 按 endi 分组,再顺序 DP。

时间复杂度 O(n + offers.length),空间复杂度 O(n + offers.length)。

解法二:暴力 DFS 子集

枚举选中哪些 offer 集合,检查区间互不重叠,求和取最大(小规模对拍)。

代码实现

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

public class Solution {
    // ---------- 解法一:DP(主提交版本) ----------
    public int maximizeTheProfit(int n, List<List<Integer>> offers) {
        List<int[]>[] byEnd = new List[n];
        for (int i = 0; i < n; i++) byEnd[i] = new ArrayList<>();
        for (List<Integer> o : offers) {
            byEnd[o.get(1)].add(new int[]{o.get(0), o.get(2)});
        }
        int[] dp = new int[n + 1];
        for (int i = 0; i < n; i++) {
            dp[i + 1] = dp[i]; // 不卖以 i 结尾的任何 offer
            for (int[] of : byEnd[i]) {
                dp[i + 1] = Math.max(dp[i + 1], dp[of[0]] + of[1]);
            }
        }
        return dp[n];
    }

    // ---------- 解法二:DFS 子集(对照) ----------
    private int best;

    public int maximizeTheProfitBrute(int n, List<List<Integer>> offers) {
        int m = offers.size();
        best = 0;
        for (int mask = 0; mask < (1 << m); mask++) {
            if (m > 20) break; // 防爆
            int sum = 0;
            boolean ok = true;
            boolean[] occupied = new boolean[n];
            for (int i = 0; i < m; i++) {
                if (((mask >> i) & 1) != 0) {
                    int s = offers.get(i).get(0), e = offers.get(i).get(1);
                    for (int p = s; p <= e; p++) {
                        if (occupied[p]) { ok = false; break; }
                        occupied[p] = true;
                    }
                    if (!ok) break;
                    sum += offers.get(i).get(2);
                }
            }
            if (ok) best = Math.max(best, sum);
        }
        return best;
    }

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

        java.util.Random rnd = new java.util.Random(48);
        boolean allOk = true;
        for (int t = 0; t < 100; t++) {
            int n = 3 + rnd.nextInt(6);
            int m = 1 + rnd.nextInt(12);
            List<List<Integer>> offers = new ArrayList<>();
            for (int i = 0; i < m; i++) {
                int st = rnd.nextInt(n);
                int e = st + rnd.nextInt(n - st);
                offers.add(Arrays.asList(st, e, 1 + rnd.nextInt(50)));
            }
            int a = s.maximizeTheProfit(n, offers);
            int b = s.maximizeTheProfitBrute(n, offers);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 n=" + n + " offers=" + offers + " dp=" + a + " 暴力=" + b);
                break;
            }
        }
        check(allOk, "2830 随机对拍");
        System.out.println("2830 all tests done");
    }

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

复杂度分析

时间复杂度 O(n + offers.length),空间复杂度 O(n + offers.length)。


2831. Find the Longest Equal Subarray(找出最长等值子数组)

题目描述

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

如果子数组中所有元素都相等,则认为子数组是一个等值子数组。注意,空数组是等值子数组。

从 nums 中删除最多 k 个元素后,返回可能的最长等值子数组的长度。

子数组是数组中一个连续且可能为空的元素序列。

示例 1

  • 输入 :nums = 1,3,2,3,1,3, k = 3
  • 输出:3
  • 解释 :删除下标 2 和 4 后 nums=1,3,3,3,最长等值子数组长度 3。

示例 2

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

解题思路

解法一:哈希表分组 + 滑动窗口

对每个值 v,记录其所有出现下标列表 posv。

对每个 v 的 pos 列表做滑动窗口:窗口 l...r 覆盖的下标区间跨度 = posr - posl + 1,

窗口内需要删除的元素数 = 跨度 - 窗口长度(即不是 v 的元素数)。

若需要删除数 > k 则 l 右移;窗口长度 r-l+1 即为该 v 的最长等值子数组长度。

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

解法二:暴力枚举 + 校验

枚举每个子数组的候选 v 与长度(小规模对拍),检查删除 <= k 后能否全等。

代码实现

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

public class Solution {
    // ---------- 解法一:分组 + 滑动窗口(主提交版本) ----------
    public int longestEqualSubarray(List<Integer> nums, int k) {
        int n = nums.size();
        Map<Integer, List<Integer>> pos = new HashMap<>();
        for (int i = 0; i < n; i++) {
            pos.computeIfAbsent(nums.get(i), x -> new ArrayList<>()).add(i);
        }
        int ans = 0;
        for (List<Integer> idx : pos.values()) {
            int l = 0;
            for (int r = 0; r < idx.size(); r++) {
                // 跨度 = idx[r] - idx[l] + 1,窗口内 v 出现次数 = r-l+1
                while (idx.get(r) - idx.get(l) + 1 - (r - l + 1) > k) {
                    l++;
                }
                ans = Math.max(ans, r - l + 1);
            }
        }
        return ans;
    }

    // ---------- 解法二:暴力(对照) ----------
    public int longestEqualSubarrayBrute(List<Integer> nums, int k) {
        int n = nums.size();
        int ans = 0;
        Set<Integer> values = new HashSet<>();
        for (int x : nums) values.add(x);
        for (int v : values) {
            // 预处理 v 的位置
            List<Integer> idx = new ArrayList<>();
            for (int i = 0; i < n; i++) if (nums.get(i) == v) idx.add(i);
            for (int l = 0; l < idx.size(); l++) {
                for (int r = l; r < idx.size(); r++) {
                    int span = idx.get(r) - idx.get(l) + 1;
                    int cnt = r - l + 1;
                    if (span - cnt <= k) ans = Math.max(ans, cnt);
                }
            }
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.longestEqualSubarray(Arrays.asList(1, 3, 2, 3, 1, 3), 3) == 3, "2831 例1");
        check(s.longestEqualSubarray(Arrays.asList(1, 1, 2, 2, 1, 1), 2) == 4, "2831 例2");

        java.util.Random rnd = new java.util.Random(49);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(10);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(1 + rnd.nextInt(4));
            int k = rnd.nextInt(n + 1);
            int m1 = s.longestEqualSubarray(a, k);
            int m2 = s.longestEqualSubarrayBrute(a, k);
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 a=" + a + " k=" + k + " 滑窗=" + m1 + " 暴力=" + m2);
                break;
            }
        }
        check(allOk, "2831 随机对拍");
        System.out.println("2831 all tests done");
    }

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

复杂度分析

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


2832. Maximal Range That Each Element Is Maximum in It(每个元素为最大值的最大范围)

题目描述

现给定一个由不同整数构成的 0 索引数组 nums 。

我们用以下方式定义与 nums 长度相同的 0 索引数组 ans :

ans[i] 是子数组 nums`l`...`r` 的最大长度,该子数组中的最大元素等于 nums[i] 。

返回数组 ans 。

注意,子数组是数组的连续部分。

示例 1

  • 输入 :nums = 1,5,4,3,6
  • 输出:1,4,2,1,5
  • 解释 :对于 nums[0],最长的子数组,其中最大值为 1,是 nums0...0,所以 ans[0] = 1。
    对于 nums[1],最长的子数组是 nums0...3,其中最大值为 5,所以 ans[1] = 4。
    对于 nums[2],最长的子数组是 nums2...3,其中最大值为 4,所以 ans[2] = 2。
    对于 nums[3],最长的子数组是 nums3...3,其中最大值为 3,所以 ans[3] = 1。
    对于 nums[4],最长的子数组是 nums0...4,其中最大值为 6,所以 ans[4] = 5。

示例 2

  • 输入 :nums = 1,2,3,4,5
  • 输出 :1,2,3,4,5
    提示:
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • nums 中的所有整数互不相同

解题思路

解法一:单调栈求左右边界

ansi 是「以 numsi 为最大值的最大子数组长度」= Ri - Li - 1,其中

Li = 左边第一个比 numsi 大的下标(开区间),Ri = 右边第一个比 numsi 大的下标。

用单调递减栈一次遍历求 L(从左往右),一次遍历求 R(从右往左)。

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

解法二:O(n^2) 向两边扩展

对每个 i,向左右找第一个比它大的元素,计算区间长度。仅用于小规模对拍。

代码实现

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

public class Solution {
    // ---------- 解法一:单调栈(主提交版本) ----------
    public int[] maximumLengthOfRanges(int[] nums) {
        int n = nums.length;
        int[] L = new int[n]; // 左边第一个更大的下标(无则 -1)
        int[] R = new int[n]; // 右边第一个更大的下标(无则 n)
        Deque<Integer> st = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            while (!st.isEmpty() && nums[st.peek()] < nums[i]) st.pop();
            L[i] = st.isEmpty() ? -1 : st.peek();
            st.push(i);
        }
        st.clear();
        for (int i = n - 1; i >= 0; i--) {
            while (!st.isEmpty() && nums[st.peek()] < nums[i]) st.pop();
            R[i] = st.isEmpty() ? n : st.peek();
            st.push(i);
        }
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) ans[i] = R[i] - L[i] - 1;
        return ans;
    }

    // ---------- 解法二:向两边扩展(对照) ----------
    public int[] maximumLengthOfRangesBrute(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            int l = i, r = i;
            while (l - 1 >= 0 && nums[l - 1] < nums[i]) l--;
            while (r + 1 < n && nums[r + 1] < nums[i]) r++;
            ans[i] = r - l + 1;
        }
        return ans;
    }

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

        java.util.Random rnd = new java.util.Random(50);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(10);
            int[] arr = new int[n];
            java.util.Set<Integer> used = new java.util.HashSet<>();
            for (int i = 0; i < n; i++) {
                int v;
                do { v = 1 + rnd.nextInt(60); } while (used.contains(v));
                used.add(v);
                arr[i] = v;
            }
            int[] a = s.maximumLengthOfRanges(arr);
            int[] b = s.maximumLengthOfRangesBrute(arr);
            if (!Arrays.equals(a, b)) {
                allOk = false;
                System.out.println("对拍失败 arr=" + Arrays.toString(arr) + " 栈=" + Arrays.toString(a)
                        + " 扩展=" + Arrays.toString(b));
                break;
            }
        }
        check(allOk, "2832 随机对拍");
        System.out.println("2832 all tests done");
    }

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

复杂度分析

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


2833. Furthest Point From Origin(距离原点最远的点)

题目描述

给你一个长度为 n 的字符串 moves ,该字符串仅由字符 'L'、'R' 和 '_' 组成。

字符串表示你在一条原点为 0 的数轴上的若干次移动。

你的初始位置就在原点(0),第 i 次移动过程中,你可以根据对应字符选择移动方向:

  • 如果 moves[i] = 'L' 或 moves[i] = '_' ,可以选择向左移动一个单位距离
  • 如果 moves[i] = 'R' 或 moves[i] = '_' ,可以选择向右移动一个单位距离
    返回在移动 n 次之后,可以到达的距离原点最远的点到原点的距离。

示例 1

  • 输入 :moves = "L_RL__R"
  • 输出:3
  • 解释 :可以到达的距离原点 0 最远的点是 -3 ,移动的序列为 "LLRLLLR" 。

示例 2

  • 输入 :moves = "_R__LL_"
  • 输出:5

示例 3

  • 输入 :moves = "_______"
  • 输出 :7
    提示:
  • 1 <= moves.length == n <= 50
  • moves 只含有字符 'L'、'R' 和 '_'

解题思路

解法一:计数公式

所有 '' 可以统一选择方向。若把全部 ' ' 都往右走,终点 = (R 数 + '' 数) - L 数;
若全部往左走,终点 = R 数 - (L 数 + '
' 数)。取两者绝对值的较大者。

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

解法二:枚举 '_' 方向

'' 数量有限(<=50),枚举每个 '' 选左或右的所有组合,计算最远距离。用于对拍。

代码实现

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

public class Solution {
    // ---------- 解法一:计数公式(主提交版本) ----------
    public int furthestDistanceFromOrigin(String moves) {
        int l = 0, r = 0, u = 0;
        for (char c : moves.toCharArray()) {
            if (c == 'L') l++;
            else if (c == 'R') r++;
            else u++;
        }
        return Math.max(Math.abs((r + u) - l), Math.abs(r - (l + u)));
    }

    // ---------- 解法二:枚举 '_' 方向(对照) ----------
    public int furthestDistanceFromOriginBrute(String moves) {
        int n = moves.length();
        int cntU = 0;
        for (char c : moves.toCharArray()) if (c == '_') cntU++;
        int best = 0;
        for (int mask = 0; mask < (1 << cntU); mask++) {
            int pos = 0, u = 0;
            for (char c : moves.toCharArray()) {
                if (c == 'L') pos--;
                else if (c == 'R') pos++;
                else {
                    if (((mask >> u) & 1) != 0) pos++;
                    else pos--;
                    u++;
                }
            }
            best = Math.max(best, Math.abs(pos));
        }
        return best;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.furthestDistanceFromOrigin("L_RL__R") == 3, "2833 例1");
        check(s.furthestDistanceFromOrigin("_R__LL_") == 5, "2833 例2");
        check(s.furthestDistanceFromOrigin("_______") == 7, "2833 例3");

        Random rnd = new Random(51);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            int n = rnd.nextInt(12) + 1;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i++) {
                int r = rnd.nextInt(3);
                sb.append(r == 0 ? 'L' : (r == 1 ? 'R' : '_'));
            }
            String m = sb.toString();
            int a = s.furthestDistanceFromOrigin(m);
            int b = s.furthestDistanceFromOriginBrute(m);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 moves=" + m + " 公式=" + a + " 枚举=" + b);
                break;
            }
        }
        check(allOk, "2833 随机对拍");
        System.out.println("2833 all tests done");
    }

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

复杂度分析

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


2834. Find the Minimum Possible Sum of a Beautiful Array(找出美丽数组的最小和)

题目描述

给你两个正整数:n 和 target 。

如果数组 nums 满足下述条件,则称其为美丽数组:

  • nums.length == n
  • nums 由两两互不相同的正整数组成
  • 在范围 0, `n`-1 内,不存在两个不同下标 i 和 j ,使得 nums[i] + nums[j] == target
    返回符合条件的美丽数组所可能具备的最小和,并对结果取模 10^9 + 7。

示例 1

  • 输入 :n = 2, target = 3
  • 输出:4
  • 解释 :nums = 1,3 是美丽数组,其最小和为 4。

示例 2

  • 输入 :n = 3, target = 3
  • 输出:8
  • 解释 :nums = 1,3,4 是美丽数组,其最小和为 8。
    提示:
  • 1 <= n <= 10^9
  • 1 <= target <= 10^9

解题思路

解法一:分段求和公式

为让和最小,应尽量选小正数。但若选入 x,则 target - x 不能再选(除非 x == target - x,

即 2x == target,此时无冲突)。

因此把 1...target-1 分成互斥的配对 (x, target-x)。为和最小,每对只能选较小的 x(即 <= target/2)。

策略:

  1. 先取 1...min(n, target/2) 这些最小合法数,共 c1 = min(n, target/2) 个,和为 1+2+...+c1;
  2. 若还差 n - c1 个,改从 target、target+1、... 取(这些数彼此两两之和 > target,且与前面所选也不冲突),
    共取 n - c1 个数,和为 target + (target+1) + ... + (target + (n-c1) - 1)。
    用等差数列求和公式 + 取模。
    时间复杂度 O(1),空间复杂度 O(1)。
    解法二:贪心逐个构造 + 哈希
    从小到大尝试正整数,冲突则跳过直到取满 n 个(小规模对拍用)。

代码实现

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

public class Solution {
    private static final long MOD = 1_000_000_007L;

    // ---------- 解法一:分段求和公式(主提交版本) ----------
    public int minimumPossibleSum(int n, int target) {
        int c1 = Math.min(n, target / 2);
        // 1..c1 的和
        long sum1 = sum1to(c1);
        long sum2 = 0;
        int rest = n - c1;
        if (rest > 0) {
            // target, target+1, ..., target+rest-1
            sum2 = (sum1to(target + rest - 1) - sum1to(target - 1) + MOD) % MOD;
        }
        return (int) ((sum1 + sum2) % MOD);
    }

    private long sum1to(long x) {
        if (x <= 0) return 0;
        return (x * (x + 1) / 2) % MOD;
    }

    // ---------- 解法二:贪心构造(对照) ----------
    public int minimumPossibleSumBrute(int n, int target) {
        boolean[] used = new boolean[target + n + 5];
        long sum = 0;
        int cnt = 0;
        for (int x = 1; cnt < n; x++) {
            if (target - x >= 1 && target - x < used.length && used[target - x]) continue;
            used[x] = true;
            sum = (sum + x) % MOD;
            cnt++;
        }
        return (int) sum;
    }

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

        java.util.Random rnd = new java.util.Random(52);
        boolean allOk = true;
        for (int t = 0; t < 300; t++) {
            int n = 1 + rnd.nextInt(20);
            int target = 1 + rnd.nextInt(30);
            int a = s.minimumPossibleSum(n, target);
            int b = s.minimumPossibleSumBrute(n, target);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 n=" + n + " target=" + target + " 公式=" + a + " 贪心=" + b);
                break;
            }
        }
        check(allOk, "2834 随机对拍");

        // 大数自测
        int big = s.minimumPossibleSum(1_000_000_000, 1_000_000_000);
        System.out.println("n=1e9,target=1e9 -> " + big);
        System.out.println("2834 all tests done");
    }

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

复杂度分析

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


2835. Minimum Operations to Form Subsequence With Target Sum(使子序列的和等于目标的最少操作次数)

题目描述

给你一个下标从 0 开始的数组 nums ,它包含非负整数,且全部为 2 的幂,同时给你一个整数 target 。

一次操作中,你必须对数组做以下修改:

  • 选择数组中一个元素 nums[i] ,满足 nums[i] > 1 。
  • 将 nums[i] 从数组中删除。
  • 在 nums 的末尾添加两个数,值都为 nums[i] / 2 。
    你的目标是让 nums 的一个子序列的元素和等于 target ,请你返回达成这一目标的最少操作次数。
    如果无法得到这样的子序列,请你返回 -1 。

示例 1

  • 输入 :nums = 1,2,8, target = 7
  • 输出:1
  • 解释:选择元素 8 拆成 4,4,数组变为 1,2,4,4,子序列 1,2,4 和为 7。

示例 2

  • 输入 :nums = 1,32,1,2, target = 12
  • 输出:2
  • 解释 :选择 32 拆成 16,16,再选择 16 拆成 8,8,数组变为 1,16,16,8,8,1,2,
    子序列 1,8,1,2 和为 12。

示例 3

  • 输入 :nums = 1,2,8, target = 4
  • 输出 :1
    提示:
  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 10^9
  • nums[i] 是 2 的幂
  • 1 <= target <= 10^9

解题思路

解法一:按位贪心拆分

把 target 看成二进制。统计 cntt = 值为 2^t 的元素个数(t=0...30)。

从低位到高位检查 target 的第 t 位:

  • 若该位为 1:优先用现有 2^t 拼(1 个即可,不足用更高位拆分补足);
    更精确的做法:逐位统计"可用量",当某一位需要但不足时,从更高一位拆分一次(操作次数 +1,可用量翻倍)。
    具体:维护 cur 表示当前 2^t 的可用个数(含拆分产生的)。对每个 t:
    need = (target >> t) & 1
    cur += cntt(本身拥有的)
    如果 need == 1 且 cur == 0,则需要从更高位拆分:找到最近的高位 p>t 使得 cntp>0 或拆分链存在,
    一路拆分下来操作次数累加。
    若 need == 1 且 cur > 0:cur--。
    每向上一级传递未使用的低半量:cur /= 2 提供给下一轮(可复用拆分余量)。
    当所有位处理完且仍有未用元素时,总 sum < target 返回 -1。
    时间复杂度 O(31),空间复杂度 O(31)。
    解法二:BFS/递归拆分子集搜索
    对很小规模枚举所有拆分结果(限制次数),检查子序列和能否等于 target。仅用于对拍。

代码实现

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

public class Solution {
    // ---------- 解法一:按位贪心(主提交版本) ----------
    public int minOperations(List<Integer> nums, int target) {
        long[] cnt = new long[32];
        long total = 0;
        for (int v : nums) {
            int t = 0;
            while ((1L << t) < v) t++;
            cnt[t]++;
            total += v;
        }
        if (total < target) return -1;
        int ops = 0;
        long cur = 0;
        for (int t = 0; t < 31; t++) {
            cur += cnt[t]; // 当前位上可用的 2^t 个数
            int need = (target >> t) & 1;
            if (need == 1) {
                if (cur > 0) {
                    cur--;
                } else {
                    // 向上借位拆分
                    int p = t + 1;
                    while (p < 31 && cnt[p] == 0) p++;
                    if (p == 31) return -1;
                    // 把 2^p 逐级拆到 t
                    for (int q = p; q > t; q--) {
                        cnt[q]--;
                        cnt[q - 1] += 2;
                        ops++;
                    }
                    cur += 1; // 拆分产生的 2^t
                    cur--;
                }
            }
            // 未使用的 2 个 2^t 合并成 1 个 2^(t+1) 供下一轮
            cur /= 2;
        }
        return ops;
    }

    // ---------- 解法二:暴力拆分搜索(小规模对照) ----------
    public int minOperationsBrute(List<Integer> nums, int target) {
        // 检查原始数组
        if (canSum(nums, target)) return 0;
        // BFS:状态 = 排序后的数组(List),操作数
        java.util.Deque<int[]> q = new java.util.ArrayDeque<>();
        java.util.Set<String> seen = new java.util.HashSet<>();
        // 初始化:把 nums 统计成计数数组
        long[] init = new long[32];
        for (int v : nums) {
            int t = 0;
            while ((1L << t) < v) t++;
            init[t]++;
        }
        String key0 = key(init);
        seen.add(key0);
        q.add(toArr(init));
        int ops = 0;
        while (!q.isEmpty()) {
            int sz = q.size();
            for (int s = 0; s < sz; s++) {
                long[] st = fromArr(q.poll());
                if (canSumFrom(st, target)) return ops;
                // 尝试拆分任意 >1 的元素
                for (int t = 31; t >= 1; t--) {
                    if (st[t] > 0) {
                        long[] ns = st.clone();
                        ns[t]--;
                        ns[t - 1] += 2;
                        String k = key(ns);
                        if (!seen.contains(k)) {
                            seen.add(k);
                            q.add(toArr(ns));
                        }
                    }
                }
            }
            ops++;
            if (ops > 60) break; // 防止过度搜索
        }
        return -1;
    }

    private boolean canSum(List<Integer> nums, int target) {
        long[] st = new long[32];
        for (int v : nums) {
            int t = 0;
            while ((1L << t) < v) t++;
            st[t]++;
        }
        return canSumFrom(st, target);
    }

    // 判断用 st 表示的 2 的幂集合能否组成 target(不允许拆分/借位,纯子集选择)
    private boolean canSumFrom(long[] st, int target) {
        long[] avail = st.clone();
        for (int t = 0; t < 31; t++) {
            long need = (target >> t) & 1;
            if (need == 1) {
                if (avail[t] == 0) return false; // 低位无 2^t 可配,且不能借高位(借=需要操作)
                avail[t]--;
            }
            // 剩余的低位 2 个合成 1 个高位,供更高位使用(子集里多个 2^t 之和等价于 2^(t+1))
            avail[t + 1] += avail[t] / 2;
        }
        return true;
    }

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

    private int[] toArr(long[] a) {
        int[] r = new int[32];
        for (int i = 0; i < 32; i++) r[i] = (int) a[i];
        return r;
    }

    private long[] fromArr(int[] a) {
        long[] r = new long[32];
        for (int i = 0; i < 32; i++) r[i] = a[i];
        return r;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.minOperations(Arrays.asList(1, 2, 8), 7) == 1, "2835 例1");
        check(s.minOperations(Arrays.asList(1, 32, 1, 2), 12) == 2, "2835 例2");
        check(s.minOperations(Arrays.asList(1, 2, 8), 4) == 1, "2835 例3");

        java.util.Random rnd = new java.util.Random(53);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int n = 1 + rnd.nextInt(6);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(1 << rnd.nextInt(6));
            int target = 1 + rnd.nextInt(64);
            int m1 = s.minOperations(a, target);
            int m2 = s.minOperationsBrute(a, target);
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 a=" + a + " target=" + target + " 贪心=" + m1 + " BFS=" + m2);
                break;
            }
        }
        check(allOk, "2835 随机对拍");
        System.out.println("2835 all tests done");
    }

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

复杂度分析

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


2836. Maximize Value of Function in a Ball Passing Game(在传球游戏中最大化函数值)

题目描述

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

总共有 n 名玩家,玩家编号互不相同,且为 0, `n` - 1 中的整数。这些玩家玩一个传球游戏,

receiver[i] 表示编号为 i 的玩家会传球给编号为 receiver[i] 的玩家。玩家可以传球给自己。

你需要从 n 名玩家中选择一名玩家作为游戏开始时唯一手中有球的玩家,球会被传恰好 k 次。

如果选择编号为 x 的玩家作为开始玩家,定义函数 f(x) 表示从编号为 x 的玩家开始,

k 次传球内所有接触过球玩家的编号之和,如果有玩家多次触球,则累加多次。

换句话说, f(x) = x + receiver[x] + receiver`receiver[x]` + ... + receiverk(共 k+1 项)。

请选择开始玩家 x ,使 f(x) 最大,返回最大值。

示例 1

  • 输入 :receiver = 2,0,1, k = 4
  • 输出:6
  • 解释 :从 x = 2 开始:2 -> 1 -> 0 -> 2 -> 1,f(2) = 2+1+0+2+1 = 6。

示例 2

  • 输入 :receiver = 1,1,1,2,3, k = 3
  • 输出:10
  • 解释 :从 x = 4 开始:4 -> 3 -> 2 -> 1,f(4) = 4+3+2+1 = 10。
    提示:
  • 1 <= receiver.length == n <= 10^5
  • 0 <= receiver[i] <= n - 1
  • 1 <= k <= 10^10

解题思路

解法一:倍增(Binary Lifting)

预处理两个表:

  • nxtji:从 i 出发走 2^j 步到达的玩家编号;
  • sumji:从 i 出发走 2^j 步过程中触球玩家编号之和(含起点 i,不含到达后尚未继续的终点,按 2^j 层定义)。
    递推:nxt0i = receiveri,sum0i = i;
    nxtji = nxtj-1nxt\[j-1i],
    sumji = sumj-1i + sumj-1nxt\[j-1i]。
    查询 f(x):把 k 拆成二进制位从低到高,从 x 开始累加对应块的 sum,并更新 x = nxtjx。
    对每个 x 求 f(x) 取最大。
    f(x) 是 k+1 个节点之和(含起点),sum 定义已含起点,拼接时注意不重复计算。
    实现细节:sumji 定义为「从 i 出发走 2^j 步覆盖的 2^j+1 个节点之和」,
    拼接 a -> b(各走 2^j 步)时,总和 = sumA + sumB - b(b 被算两次),
    其中 b = 由 a 走完前半段后所在的节点。更稳妥的做法:累计时先加起点再走。
    具体:ans=0, cur=x;对每个 set 的位 j:ans += sumjcur;然后若还有更高位,cur = nxtjcur。
    注意最后一步 cur 更新放在循环末尾,避免把 k+1 项外的节点计入(sum 含起点,最后一项不重复)。
    时间复杂度 O(n log k),空间复杂度 O(n log k)。
    解法二:直接模拟走 k 步
    对每个 x 循环 k+1 次累加(小 k、小 n 时用),用于对拍。

代码实现

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

public class Solution {
    // ---------- 解法一:倍增(主提交版本) ----------
    public long getMaxFunctionValue(List<Integer> receiver, long k) {
        int n = receiver.size();
        int bits = 0;
        long kk = k;
        while (kk > 0) { bits++; kk >>= 1; }
        bits = Math.max(bits, 1);
        int[][] nxt = new int[bits][n];
        long[][] sum = new long[bits][n];
        for (int i = 0; i < n; i++) {
            nxt[0][i] = receiver.get(i);
            sum[0][i] = i; // 含起点
        }
        for (int j = 1; j < bits; j++) {
            for (int i = 0; i < n; i++) {
                int mid = nxt[j - 1][i];
                nxt[j][i] = nxt[j - 1][mid];
                sum[j][i] = sum[j - 1][i] + sum[j - 1][mid];
            }
        }
        // 查询 f(x):走恰好 k 步,即 k+1 个节点。
        // sum[j][i] 语义 = 从 i 出发走 2^j 步经过的前 2^j 个节点之和(不含终点 nxt[j][i])。
        // 把 k 拆二进制:某位 j 为 1,累加 sum[j][cur] 并把 cur 跳转 nxt[j][cur](只在此刻跳转)。
        // 循环结束后 cur = 走完 k 步所在的最终节点,需要补加(nxt 链上的第 k+1 个节点)。
        long best = 0;
        for (int x = 0; x < n; x++) {
            long tot = 0;
            int cur = x;
            for (int j = 0; j < bits; j++) {
                if (((k >> j) & 1L) != 0) {
                    tot += sum[j][cur];
                    cur = nxt[j][cur];
                }
            }
            tot += cur; // 最终节点
            best = Math.max(best, tot);
        }
        return best;
    }

    // ---------- 解法二:直接模拟(对照) ----------
    public long getMaxFunctionValueBrute(List<Integer> receiver, long k) {
        int n = receiver.size();
        long best = Long.MIN_VALUE;
        for (int x = 0; x < n; x++) {
            long tot = 0;
            int cur = x;
            for (long step = 0; step <= k; step++) {
                tot += cur;
                cur = receiver.get(cur);
            }
            best = Math.max(best, tot);
        }
        return best;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.getMaxFunctionValue(Arrays.asList(2, 0, 1), 4) == 6, "2836 例1");
        check(s.getMaxFunctionValue(Arrays.asList(1, 1, 1, 2, 3), 3) == 10, "2836 例2");

        java.util.Random rnd = new java.util.Random(54);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int n = 2 + rnd.nextInt(8);
            List<Integer> a = new ArrayList<>();
            for (int i = 0; i < n; i++) a.add(rnd.nextInt(n));
            long k = rnd.nextInt(20);
            long m1 = s.getMaxFunctionValue(a, k);
            long m2 = s.getMaxFunctionValueBrute(a, k);
            if (m1 != m2) {
                allOk = false;
                System.out.println("对拍失败 a=" + a + " k=" + k + " 倍增=" + m1 + " 模拟=" + m2);
                break;
            }
        }
        check(allOk, "2836 随机对拍");

        // 大 k 自测
        long big = s.getMaxFunctionValue(Arrays.asList(2, 0, 1), 10_000_000_000L);
        System.out.println("k=1e10 -> " + big);
        System.out.println("2836 all tests done");
    }

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

复杂度分析

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


2837. Total Traveled Distance(总旅行距离)

题目类型:数据库(SQL)

题目描述

表:Users

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| user_id     | int     |
| name        | varchar |
+-------------+---------+
user_id 是此表中具有唯一值的列。
此表的每一行包含用户 id 和姓名。

表:Rides

复制代码
+--------------+------+
| Column Name  | Type |
+--------------+------+
| ride_id      | int  |
| user_id      | int  |
| distance     | int  |
+--------------+------+
ride_id 是此表中具有唯一值的列。
此表中的每一行包含乘车 id、用户 id 和旅行距离。

编写一个解决方案,计算每个用户的旅行距离 distance 。如果有用户没有任何旅行记录,那么他们的 distance 应被视为 0 。输出 user_id, name 和总旅行距离 traveled distance 。

按 升序排序 的 user_id 返回结果表。

结果格式如下示例。

示例 1:**

复制代码
输入:
Users table:
+---------+---------+
| user_id | name    |
+---------+---------+
| 17      | Addison |
| 14      | Ethan   |
| 4       | Michael |
| 2       | Avery   |
| 10      | Eleanor |
+---------+---------+
Rides table:
+---------+---------+----------+
| ride_id | user_id | distance |
+---------+---------+----------+
| 72      | 17      | 160      |
| 42      | 14      | 161      |
| 45      | 4       | 59       |
| 32      | 2       | 197      |
| 15      | 4       | 357      |
| 56      | 2       | 196      |
| 10      | 14      | 25       |
+---------+---------+----------+
输出:
+---------+---------+-------------------+
| user_id | name    | traveled distance |
+---------+---------+-------------------+
| 2       | Avery   | 393               |
| 4       | Michael | 416               |
| 10      | Eleanor | 0                 |
| 14      | Ethan   | 186               |
| 17      | Addison | 160               |
+---------+---------+-------------------+
解释:
-  User id 为 2 的用户完成了两次旅行,分别为 197 和 196,总旅行距离为 393。
-  User id 为 4 的用户完成了两次旅行,分别为 59 和 357,总旅行距离为 416。
-  User id 为 14 的用户完成了两次旅行,分别为 161 和 25,总旅行距离为 186。
-  User id 为 16 的用户只完成了一次旅行,距离为 160。
-  User id 为 10 的用户没有完成任何旅行,因此总旅行距离为 0。
按升序排序的 `user_id` 返回结果表

解题思路

解法一:LEFT JOIN + 聚合(主提交版本)

  • Users 为左表 LEFT JOIN Rides,保证没有乘车记录的用户也保留;
  • 按 user_id 分组 SUM(distance),无记录者为 NULL,用 IFNULL(..., 0) 补 0;
  • 结果按 user_id 升序。

代码实现

sql 复制代码
SELECT user_id, name, IFNULL(SUM(distance), 0) AS 'traveled distance'
FROM
    Users
    LEFT JOIN Rides USING (user_id)
GROUP BY 1
ORDER BY 1;

复杂度分析

  • 时间复杂度 :O(n),n 为表行数,单次扫描聚合(或窗口/连接)。
  • 空间复杂度 :O(n),分组/排序/窗口需要存储结果集。

2838. Maximum Coins Heroes Can Collect(英雄可以获得的最大金币数)

题目描述

在一场战斗中,n 位英雄正在试图击败 m 个怪物。你将得到两个下标从 1 开始的正整数数组

heroes 和 monsters,长度分别为 n 和 m。数组 heroes[i] 代表第 i 位英雄的力量,

而 monsters[i] 代表第 i 个怪物的力量。如果 monsters[j] <= heroes[i],则第 i 位英雄

可以击败第 j 个怪物。你还将获得下标从 1 开始的正整数数组 coins,长度为 m;

coins[i] 表示每位英雄在击败第 i 个怪物后可以获得的金币数。

返回一个长度为 n 的数组 ans,其中 ans[i] 是第 i 位英雄能收集到的最大金币数。

注意:击败怪物后英雄生命值不会减少;多位英雄可以击败同一个怪物,但每个怪物只能被

同一位英雄击败一次。

示例 1

  • 输入 :heroes = 1,4,2, monsters = 1,1,5,2,3, coins = 2,3,4,5,6
  • 输出:5,16,10

示例 2

  • 输入 :heroes = 5, monsters = 2,3,1,2, coins = 10,6,5,2
  • 输出:23

示例 3

  • 输入 :heroes = 4,4, monsters = 5,7,8, coins = 1,1,1
  • 输出 :0,0
    提示:
  • 1 <= n == heroes.length <= 10^5
  • 1 <= m == monsters.length <= 10^5
  • coins.length == m
  • 1 <= heroes[i], monsters[i], coins[i] <= 10^9

解题思路

解法一:排序 + 前缀和 + 二分

将怪物按力量升序排序(同时携带对应金币),计算金币的前缀和。

对每位英雄,二分查找最大可击败怪物下标 idx(monstersidx <= heroesi),

答案即前缀和 prefidx + 1。

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

解法二:暴力逐个英雄扫描

对每个英雄扫描全部怪物累加(小规模对拍)。

代码实现

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

public class Solution {
    // ---------- 解法一:排序 + 前缀和 + 二分(主提交版本) ----------
    public long[] maximumCoins(int[] heroes, int[] monsters, int[] coins) {
        int m = monsters.length;
        int[][] pairs = new int[m][2];
        for (int i = 0; i < m; i++) {
            pairs[i][0] = monsters[i];
            pairs[i][1] = coins[i];
        }
        Arrays.sort(pairs, (a, b) -> Integer.compare(a[0], b[0]));
        long[] pref = new long[m + 1];
        for (int i = 0; i < m; i++) pref[i + 1] = pref[i] + pairs[i][1];
        int n = heroes.length;
        long[] ans = new long[n];
        for (int i = 0; i < n; i++) {
            int lo = 0, hi = m - 1, idx = -1;
            while (lo <= hi) {
                int mid = (lo + hi) >>> 1;
                if (pairs[mid][0] <= heroes[i]) {
                    idx = mid;
                    lo = mid + 1;
                } else {
                    hi = mid - 1;
                }
            }
            ans[i] = idx >= 0 ? pref[idx + 1] : 0L;
        }
        return ans;
    }

    // ---------- 解法二:暴力(对照) ----------
    public long[] maximumCoinsBrute(int[] heroes, int[] monsters, int[] coins) {
        int n = heroes.length;
        long[] ans = new long[n];
        for (int i = 0; i < n; i++) {
            long sum = 0;
            for (int j = 0; j < monsters.length; j++) {
                if (monsters[j] <= heroes[i]) sum += coins[j];
            }
            ans[i] = sum;
        }
        return ans;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(Arrays.equals(s.maximumCoins(new int[]{1, 4, 2}, new int[]{1, 1, 5, 2, 3}, new int[]{2, 3, 4, 5, 6}), new long[]{5, 16, 10}), "2838 例1");
        check(Arrays.equals(s.maximumCoins(new int[]{5}, new int[]{2, 3, 1, 2}, new int[]{10, 6, 5, 2}), new long[]{23}), "2838 例2");
        check(Arrays.equals(s.maximumCoins(new int[]{4, 4}, new int[]{5, 7, 8}, new int[]{1, 1, 1}), new long[]{0, 0}), "2838 例3");

        java.util.Random rnd = new java.util.Random(58);
        boolean allOk = true;
        for (int t = 0; t < 200; t++) {
            int n = 1 + rnd.nextInt(8);
            int m = 1 + rnd.nextInt(10);
            int[] h = new int[n], mo = new int[m], c = new int[m];
            for (int i = 0; i < n; i++) h[i] = 1 + rnd.nextInt(12);
            for (int i = 0; i < m; i++) { mo[i] = 1 + rnd.nextInt(12); c[i] = 1 + rnd.nextInt(20); }
            long[] a = s.maximumCoins(h, mo, c);
            long[] b = s.maximumCoinsBrute(h, mo, c);
            if (!Arrays.equals(a, b)) {
                allOk = false;
                System.out.println("对拍失败 heroes=" + Arrays.toString(h) + " monsters=" + Arrays.toString(mo) + " coins=" + Arrays.toString(c));
                break;
            }
        }
        check(allOk, "2838 随机对拍");
        System.out.println("2838 all tests done");
    }

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

复杂度分析

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


2839. Check if Strings Can be Made Equal With Operations I(判断通过操作能否让字符串相等 I)

题目描述

给你两个字符串 s1 和 s2,长度都为 4,且只包含小写英文字母。

你可以对两个字符串中的任意一个执行以下操作任意次:

  • 选择两个下标 i 和 j 且满足 j - i = 2,然后交换这个字符串中两个下标对应的字符。
    如果你可以让字符串 s1 和 s2 相等,那么返回 true,否则返回 false。

示例 1

  • 输入 :s1 = "abcd", s2 = "cdab"
  • 输出 :true

示例 2

  • 输入 :s1 = "abcd", s2 = "dacb"
  • 输出 :false
    提示:
  • s1.length == s2.length == 4
  • s1 和 s2 只包含小写英文字母。

解题思路

解法一:奇偶位分组判断

操作只允许交换距离为 2 的下标,即位置 0 与 2 可互换、位置 1 与 3 可互换。

因此 s1 的 {0,2} 位字符多集必须与 s2 的 {0,2} 位字符多集相同,

{1,3} 位同理。排序后比较即可。

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

解法二:BFS 全排列状态

从 s1 出发,不断交换满足条件的位(距离 2),用集合去重,看能否得到 s2。

代码实现

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

public class Solution {
    // ---------- 解法一:奇偶位分组(主提交版本) ----------
    public boolean canBeEqual(String s1, String s2) {
        char[] a = new char[]{s1.charAt(0), s1.charAt(2)};
        char[] b = new char[]{s2.charAt(0), s2.charAt(2)};
        Arrays.sort(a);
        Arrays.sort(b);
        if (a[0] != b[0] || a[1] != b[1]) return false;
        a = new char[]{s1.charAt(1), s1.charAt(3)};
        b = new char[]{s2.charAt(1), s2.charAt(3)};
        Arrays.sort(a);
        Arrays.sort(b);
        return a[0] == b[0] && a[1] == b[1];
    }

    // ---------- 解法二:BFS(对照) ----------
    public boolean canBeEqualBrute(String s1, String s2) {
        Set<String> seen = new HashSet<>();
        Deque<String> q = new ArrayDeque<>();
        seen.add(s1);
        q.add(s1);
        while (!q.isEmpty()) {
            String cur = q.poll();
            if (cur.equals(s2)) return true;
            char[] cs = cur.toCharArray();
            for (int i = 0; i + 2 < 4; i++) {
                char[] nxt = cs.clone();
                char tmp = nxt[i];
                nxt[i] = nxt[i + 2];
                nxt[i + 2] = tmp;
                String ns = new String(nxt);
                if (seen.add(ns)) q.add(ns);
            }
        }
        return false;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.canBeEqual("abcd", "cdab"), "2839 例1");
        check(!s.canBeEqual("abcd", "dacb"), "2839 例2");

        java.util.Random rnd = new java.util.Random(59);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 4; i++) sb.append((char) ('a' + rnd.nextInt(4)));
            StringBuilder sb2 = new StringBuilder();
            for (int i = 0; i < 4; i++) sb2.append((char) ('a' + rnd.nextInt(4)));
            String x = sb.toString(), y = sb2.toString();
            boolean a = s.canBeEqual(x, y);
            boolean b = s.canBeEqualBrute(x, y);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 s1=" + x + " s2=" + y + " 主解=" + a + " BFS=" + b);
                break;
            }
        }
        check(allOk, "2839 随机对拍");
        System.out.println("2839 all tests done");
    }

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

复杂度分析

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


2840. Check if Strings Can be Made Equal With Operations II(判断通过操作能否让字符串相等 II)

题目描述

给你两个字符串 s1 和 s2,长度都为 n,且只包含小写英文字母。

你可以对两个字符串中的任意一个执行以下操作任意次:

  • 选择两个下标 i 和 j,满足 i < j 且 j - i 是偶数,然后交换这个字符串中两个下标对应的字符。
    如果你可以让字符串 s1 和 s2 相等,那么返回 true,否则返回 false。

示例 1

  • 输入 :s1 = "abcdba", s2 = "cabdab"
  • 输出 :true

示例 2

  • 输入 :s1 = "abe", s2 = "bea"
  • 输出 :false
    提示:
  • n == s1.length == s2.length
  • 1 <= n <= 10^5
  • s1 和 s2 只包含小写英文字母。

解题思路

解法一:奇偶下标分组

j - i 为偶数意味着两个下标奇偶性相同,因此同奇偶的位置之间可以任意交换字符。

等价于:s1 偶数下标位置字符的多重集 == s2 偶数下标位置字符的多重集,且奇数下标位置同理。

用两个 26 长计数数组分别统计奇偶位,比较即可。

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

解法二:BFS/DFS 状态搜索

小规模时枚举所有可交换状态(对照)。

代码实现

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

public class Solution {
    // ---------- 解法一:奇偶计数(主提交版本) ----------
    public boolean checkStrings(String s1, String s2) {
        int[] even = new int[26], odd = new int[26];
        for (int i = 0; i < s1.length(); i++) {
            if ((i & 1) == 0) even[s1.charAt(i) - 'a']++;
            else odd[s1.charAt(i) - 'a']++;
        }
        for (int i = 0; i < s2.length(); i++) {
            if ((i & 1) == 0) even[s2.charAt(i) - 'a']--;
            else odd[s2.charAt(i) - 'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if (even[i] != 0 || odd[i] != 0) return false;
        }
        return true;
    }

    // ---------- 解法二:BFS(对照,小长度) ----------
    public boolean checkStringsBrute(String s1, String s2) {
        if (s1.length() > 8) return checkStrings(s1, s2); // 状态爆炸保护
        Set<String> seen = new HashSet<>();
        Deque<String> q = new ArrayDeque<>();
        seen.add(s1);
        q.add(s1);
        while (!q.isEmpty()) {
            String cur = q.poll();
            if (cur.equals(s2)) return true;
            char[] cs = cur.toCharArray();
            int n = cs.length;
            for (int i = 0; i < n; i++) {
                for (int j = i + 2; j < n; j += 2) {
                    char[] nxt = cs.clone();
                    char tmp = nxt[i];
                    nxt[i] = nxt[j];
                    nxt[j] = tmp;
                    String ns = new String(nxt);
                    if (seen.add(ns)) q.add(ns);
                }
            }
        }
        return false;
    }

    // ---------- 本地自测 ----------
    public static void main(String[] args) {
        Solution s = new Solution();
        check(s.checkStrings("abcdba", "cabdab"), "2840 例1");
        check(!s.checkStrings("abe", "bea"), "2840 例2");

        java.util.Random rnd = new java.util.Random(60);
        boolean allOk = true;
        for (int t = 0; t < 500; t++) {
            int n = 1 + rnd.nextInt(6);
            StringBuilder sb = new StringBuilder(), sb2 = new StringBuilder();
            for (int i = 0; i < n; i++) {
                sb.append((char) ('a' + rnd.nextInt(4)));
                sb2.append((char) ('a' + rnd.nextInt(4)));
            }
            String x = sb.toString(), y = sb2.toString();
            boolean a = s.checkStrings(x, y);
            boolean b = s.checkStringsBrute(x, y);
            if (a != b) {
                allOk = false;
                System.out.println("对拍失败 s1=" + x + " s2=" + y + " 主解=" + a + " BFS=" + b);
                break;
            }
        }
        check(allOk, "2840 随机对拍");
        System.out.println("2840 all tests done");
    }

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

复杂度分析

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

相关推荐
apcipot_rain1 小时前
C++转Java日记(10)
java·开发语言
简 洁 冬冬1 小时前
泛型方法写法全面辨析:从「能编译」到「能跑」到「能用」
java·泛型
LGL6030A2 小时前
Java学习历程31——初识Spring Boot
java·spring boot·学习
Lansonli4 小时前
大数据Spark(八十八):Spark任务资源之Standalone集群测试
java·服务器·spark
budingxiaomoli4 小时前
Spring AOP
java·spring·java-ee
特立独行的猫A4 小时前
Ionic + 仓颉语言的 cj-tauri 开发框架实战:开发精美移动端 APP
java·后端·python
Pniubi4 小时前
力扣24. 两两交换链表中的节点(递归)
算法·leetcode·链表
nice_lcj5204 小时前
LRU 缓存入门:它是什么、怎么用、面试怎么手写
java·数据结构·缓存·面试·lru
文丑颜不良啊5 小时前
LeetCode2741~2760题目解析
java·leetcode