LeetCode1681~1700题目解析

LeetCode 1681-1700. 题解合集

文章目录


1681. Minimum Incompatibility(最小不兼容性)

题目描述

将数组 nums 划分到 k 个相同大小的子集,同一子集内没有两个相同的元素。

子集不兼容性 = 子集内最大值与最小值之差。返回各子集不兼容性和的最小值;无法划分返回 -1。

示例 1

复制代码
nums=[1,2,1,4], k=2,输出 4。

示例 2

复制代码
nums=[6,3,8,1,3,1,2,2], k=4,输出 6。

示例 3

复制代码
nums=[5,3,3,6,3,3], k=3,输出 -1。

约束:

  • 1 <= k <= nums.length <= 16;nums.length 能被 k 整除;1 <= nums[i] <= nums.length。

解题思路

解法一:状压 DP(主提交版本)

  • 每子集大小 sz = n / k。预计算所有"合法子集掩码"(大小为 sz 且内部无重复元素)的 incompat。
  • DPmask = 已分配 mask 中元素对应的最小不兼容性总和;
    转移时从未选元素中取一个合法子集 sub,dpmask \| sub = min(dpmask + inc(sub))。
    复杂度:时间复杂度 O(3^(n/k?) 级别即 O(2^n * 组合数)),n <= 16 可行,空间 O(2^n)。
    解法二:DFS + 剪枝回溯(对照)
  • 把元素放入 k 个桶,逐元素选择桶(剪枝:相同值不重复尝试相同桶)。
    复杂度:指数级(对照,适合小数据)。

代码实现

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

public class Solution {

    public int minimumIncompatibility(int[] nums, int k) {
        int n = nums.length;
        int sz = n / k;
        int total = 1 << n;
        // 预计算每个掩码的 incompat(非法为 -1)
        int[] inc = new int[total];
        Arrays.fill(inc, -1);
        for (int mask = 1; mask < total; mask++) {
            if (Integer.bitCount(mask) != sz) {
                continue;
            }
            // 检查内部是否重复值
            int[] seen = new int[n + 1];
            boolean dup = false;
            int min = Integer.MAX_VALUE, max = 0;
            for (int i = 0; i < n; i++) {
                if ((mask & (1 << i)) != 0) {
                    int v = nums[i];
                    if (seen[v] == 1) {
                        dup = true;
                        break;
                    }
                    seen[v] = 1;
                    min = Math.min(min, v);
                    max = Math.max(max, v);
                }
            }
            if (!dup) {
                inc[mask] = max - min;
            }
        }
        int[] dp = new int[total];
        Arrays.fill(dp, Integer.MAX_VALUE / 2);
        dp[0] = 0;
        for (int mask = 0; mask < total; mask++) {
            if (dp[mask] >= Integer.MAX_VALUE / 2) {
                continue;
            }
            int rest = (total - 1) ^ mask;
            // 只需枚举一个必选位,避免重复计算
            int low = rest & (-rest);
            for (int sub = rest; sub > 0; sub = (sub - 1) & rest) {
                if ((sub & low) == 0) {
                    continue;
                }
                if (inc[sub] != -1) {
                    dp[mask | sub] = Math.min(dp[mask | sub], dp[mask] + inc[sub]);
                }
            }
        }
        return dp[total - 1] >= Integer.MAX_VALUE / 2 ? -1 : dp[total - 1];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:4
        System.out.println(sol.minimumIncompatibility(new int[]{1, 2, 1, 4}, 2));
        // 示例 2:6
        System.out.println(sol.minimumIncompatibility(new int[]{6, 3, 8, 1, 3, 1, 2, 2}, 4));
        // 示例 3:-1
        System.out.println(sol.minimumIncompatibility(new int[]{5, 3, 3, 6, 3, 3}, 3));
        // 附加:k=1
        System.out.println(sol.minimumIncompatibility(new int[]{1, 2, 3}, 1));
    }
}

复杂度分析

复杂度:时间复杂度 O(3^(n/k?) 级别即 O(2^n * 组合数)),n <= 16 可行,空间 O(2^n)。


1682. Longest Palindromic Subsequence II(最长回文子序列 II)

题目描述

"好的回文子序列":是 s 的子序列、是回文、长度为偶数、除中间的两个字符外,

其余任意两个连续字符不相等。

返回 s 的最长"好的回文子序列"的长度。

示例 1

复制代码
s="bbabab",输出 4("baab")。

示例 2

复制代码
s="dcbccacdb",输出 4("dccd")。

约束:

  • 1 <= s.length <= 250;小写字母。

解题思路

解法一:记忆化搜索 + 禁止接续字符(主提交版本)

  • 偶长回文从外到内是一串成对的字符 x0, x1, ..., x_{k-1}(配对序列),
    展开后相邻的"非中间对"要求 xt != xt+1,即相邻两层配对字符不同。
  • 定义 f(i, j, ban):在 si...j 内选取好回文,要求最外层配对字符 != ban(ban 是外层已选字符),
    返回最大长度。转移:
    不选 si:f(i+1, j, ban);不选 sj:f(i, j-1, ban);
    若 si==sj==x 且 x != ban:x + 内层 f(i+1, j-1, x) + x,长度 +2。
    记忆化 (i, j, ban) 三维。
    复杂度:时间复杂度 O(n^2 * 27),空间复杂度 O(n^2 * 27)。
    解法二:区间 DP + 配对字符记录(对照)
  • dpij 记长度、lastij 记该长度下最外层配对字符;转移时避免与内层最外层相同。
  • 由于单 last 可能丢失同长度不同首字符的解,在处理冲突时回溯------为保证正确,
    同样采取二维 + 冲突时尝试 (i+1, j) 与 (i, j-1)。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n^2)。

代码实现

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

public class Solution {

    private int[][][] memo;
    private String s;

    public int longestPalindromeSubseq(String ss) {
        this.s = ss;
        int n = s.length();
        memo = new int[n][n][27]; // ban: 0 表示无限制,1..26 对应字符
        for (int[][] a : memo) {
            for (int[] b : a) {
                Arrays.fill(b, -1);
            }
        }
        return f(0, n - 1, 0);
    }

    private int f(int i, int j, int ban) {
        if (i >= j) {
            return 0;
        }
        if (memo[i][j][ban] != -1) {
            return memo[i][j][ban];
        }
        int best = Math.max(f(i + 1, j, ban), f(i, j - 1, ban));
        if (s.charAt(i) == s.charAt(j)) {
            int x = s.charAt(i) - 'a' + 1;
            if (x != ban) {
                best = Math.max(best, 2 + f(i + 1, j - 1, x));
            }
        }
        memo[i][j][ban] = best;
        return best;
    }

    public int longestPalindromeSubseqDp(String ss) {
        int n = ss.length();
        int[][] dp = new int[n][n];      // 最长好回文长度
        int[][] last = new int[n][n];    // 最外层配对字符(0 表示长度 0 或无配对)
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (i == j) {
                    dp[i][j] = 0;
                    last[i][j] = 0;
                    continue;
                }
                int bestLen = 0, bestLast = 0;
                if (ss.charAt(i) == ss.charAt(j)) {
                    int x = ss.charAt(i) - 'a' + 1;
                    boolean canPair = j == i + 1 || last[i + 1][j - 1] == 0
                            || last[i + 1][j - 1] != x;
                    if (canPair) {
                        int cand = dp[i + 1][j - 1] + 2;
                        if (cand > bestLen) {
                            bestLen = cand;
                            bestLast = x;
                        }
                    }
                }
                // 子区间
                if (dp[i + 1][j] > bestLen) {
                    bestLen = dp[i + 1][j];
                    bestLast = last[i + 1][j];
                } else if (dp[i + 1][j] == bestLen && bestLast == 0) {
                    bestLast = last[i + 1][j];
                }
                if (dp[i][j - 1] > bestLen) {
                    bestLen = dp[i][j - 1];
                    bestLast = last[i][j - 1];
                } else if (dp[i][j - 1] == bestLen && bestLast == 0) {
                    bestLast = last[i][j - 1];
                }
                dp[i][j] = bestLen;
                last[i][j] = bestLast;
            }
        }
        return dp[0][n - 1];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:4("baab")
        System.out.println(sol.longestPalindromeSubseq("bbabab") + " " + sol.longestPalindromeSubseqDp("bbabab"));
        // 示例 2:4("dccd")
        System.out.println(sol.longestPalindromeSubseq("dcbccacdb") + " " + sol.longestPalindromeSubseqDp("dcbccacdb"));
        // 附加:全相同字符(只能取中间一对,长度 2)
        System.out.println(sol.longestPalindromeSubseq("aaaa") + " " + sol.longestPalindromeSubseqDp("aaaa"));
        // 附加:交替
        System.out.println(sol.longestPalindromeSubseq("ababab") + " " + sol.longestPalindromeSubseqDp("ababab"));
        // 附加:无重复字符
        System.out.println(sol.longestPalindromeSubseq("abcdef") + " " + sol.longestPalindromeSubseqDp("abcdef"));
    }
}

复杂度分析

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

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


1683. Invalid Tweets(无效的推文)

题目类型:数据库(SQL)

题目描述

表:Tweets

复制代码
+----------------+---------+
| Column Name    | Type    |
+----------------+---------+
| tweet_id       | int     |
| content        | varchar |
+----------------+---------+
在 SQL 中,tweet_id 是这个表的主键。
content 只包含字母数字字符,'!',' ',不包含其它特殊字符。
这个表包含某社交媒体 App 中所有的推文。

查询所有无效推文的编号(ID)。当推文内容中的字符数严格大于 15 时,该推文是无效的。

以任意顺序返回结果表。

查询结果格式如下所示:

示例 1:

复制代码
**输入:**
Tweets 表:
+----------+----------------------------------+
| tweet_id | content                          |
+----------+----------------------------------+
| 1        | Vote for Biden                   |
| 2        | Let us make America great again! |
+----------+----------------------------------+

**输出:**
+----------+
| tweet_id |
+----------+
| 2        |
+----------+
**解释:**
推文 1 的长度 length = 14。该推文是有效的。
推文 2 的长度 length = 32。该推文是无效的。

解题思路

思考

无效推文是内容长度大于 15 15 15。应统计字符个数而非字节数,因而用 CHAR_LENGTH \texttt{CHAR\_LENGTH} CHAR_LENGTH 而非 LENGTH \texttt{LENGTH} LENGTH。

筛选 CHAR_LENGTH ( content ) > 15 \texttt{CHAR\_LENGTH}(\texttt{content})>15 CHAR_LENGTH(content)>15 的 tweet_id \texttt{tweet\_id} tweet_id。

考察字符串长度判断:非法推文 = 内容长度 > 15(CHAR_LENGTH 计字符数),按 tweet_id 输出。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT
    tweet_id
FROM Tweets
WHERE CHAR_LENGTH(content) > 15;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。


1684. Count the Number of Consistent Strings(统计一致字符串的数目)

题目描述

给定由不同字符组成的字符串 allowed 和字符串数组 words。

若一个字符串的每个字符都在 allowed 中,则该字符串是"一致字符串"。

返回 words 中一致字符串的数目。

示例 1

复制代码
allowed="ab", words=["ad","bd","aaab","baa","badab"],输出 2。

示例 2

复制代码
allowed="abc", words=["a","b","c","ab","ac","bc","abc"],输出 7。

示例 3

复制代码
allowed="cad", words=["cc","acd","b","ba","bac","bad","ac","d"],输出 4。

约束:

  • 1 <= words.length <= 10^4;1 <= words[i].length <= 10;allowed 字符互不相同。

解题思路

解法一:布尔掩码(主提交版本)

  • 用 26 位掩码记录 allowed 中出现的字符;对每个 word 检查其掩码是否为 allowedMask 的子集。
    复杂度:时间复杂度 O(total chars),空间复杂度 O(1)。
    解法二:集合 + contains 检查(对照)
  • 用 boolean26 数组即可,逐一判断。
    复杂度:时间复杂度 O(total chars),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public int countConsistentStrings(String allowed, String[] words) {
        int mask = 0;
        for (int i = 0; i < allowed.length(); i++) {
            mask |= 1 << (allowed.charAt(i) - 'a');
        }
        int ans = 0;
        for (String w : words) {
            int wm = 0;
            for (int i = 0; i < w.length(); i++) {
                wm |= 1 << (w.charAt(i) - 'a');
            }
            if ((wm & ~mask) == 0) {
                ans++;
            }
        }
        return ans;
    }

    public int countConsistentStringsBool(String allowed, String[] words) {
        boolean[] ok = new boolean[26];
        for (int i = 0; i < allowed.length(); i++) {
            ok[allowed.charAt(i) - 'a'] = true;
        }
        int ans = 0;
        for (String w : words) {
            boolean good = true;
            for (int i = 0; i < w.length(); i++) {
                if (!ok[w.charAt(i) - 'a']) {
                    good = false;
                    break;
                }
            }
            if (good) {
                ans++;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:2
        System.out.println(sol.countConsistentStrings("ab", new String[]{"ad", "bd", "aaab", "baa", "badab"})
                + " " + sol.countConsistentStringsBool("ab", new String[]{"ad", "bd", "aaab", "baa", "badab"}));
        // 示例 2:7
        System.out.println(sol.countConsistentStrings("abc", new String[]{"a", "b", "c", "ab", "ac", "bc", "abc"})
                + " " + sol.countConsistentStringsBool("abc", new String[]{"a", "b", "c", "ab", "ac", "bc", "abc"}));
        // 示例 3:4
        System.out.println(sol.countConsistentStrings("cad", new String[]{"cc", "acd", "b", "ba", "bac", "bad", "ac", "d"})
                + " " + sol.countConsistentStringsBool("cad", new String[]{"cc", "acd", "b", "ba", "bac", "bad", "ac", "d"}));
    }
}

复杂度分析

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


1685. Sum of Absolute Differences in a Sorted Array(有序数组中差绝对值之和)

题目描述

给定非递减有序数组 nums,返回 result,result[i] = sum(|nums[i] - nums[j]|)(对所有 j)。

示例 1

复制代码
nums=[2,3,5],输出 [4,3,5]。

示例 2

复制代码
nums=[1,4,6,8,10],输出 [24,15,13,15,21]。

约束:

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

解题思路

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

  • 因有序,resulti = numsii - prefixi + (total - prefixi+1) - numsi (n-1-i)。
    O(1) 每个元素,配合前缀和。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)(可优化为 O(1))。
    解法二:滚动累加(对照)
  • 先算总和,再从左到右滚动维护左侧和与右侧和,O(1) 空间。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public int[] getSumAbsoluteDifferences(int[] nums) {
        int n = nums.length;
        long[] pre = new long[n + 1];
        for (int i = 0; i < n; i++) {
            pre[i + 1] = pre[i] + nums[i];
        }
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            long left = (long) nums[i] * i - pre[i];
            long right = (pre[n] - pre[i + 1]) - (long) nums[i] * (n - 1 - i);
            res[i] = (int) (left + right);
        }
        return res;
    }

    public int[] getSumAbsoluteDifferencesRolling(int[] nums) {
        int n = nums.length;
        long total = 0;
        for (int v : nums) {
            total += v;
        }
        int[] res = new int[n];
        long leftSum = 0;
        for (int i = 0; i < n; i++) {
            long rightSum = total - leftSum - nums[i];
            long left = (long) nums[i] * i - leftSum;
            long right = rightSum - (long) nums[i] * (n - 1 - i);
            res[i] = (int) (left + right);
            leftSum += nums[i];
        }
        return res;
    }

    private static String join(int[] a) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < a.length; i++) {
            if (i > 0) {
                sb.append(",");
            }
            sb.append(a[i]);
        }
        return sb.append("]").toString();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:[4,3,5]
        System.out.println(join(sol.getSumAbsoluteDifferences(new int[]{2, 3, 5}))
                + " " + join(sol.getSumAbsoluteDifferencesRolling(new int[]{2, 3, 5})));
        // 示例 2:[24,15,13,15,21]
        System.out.println(join(sol.getSumAbsoluteDifferences(new int[]{1, 4, 6, 8, 10}))
                + " " + join(sol.getSumAbsoluteDifferencesRolling(new int[]{1, 4, 6, 8, 10})));
    }
}

复杂度分析

复杂度:时间复杂度 O(n),空间复杂度 O(n)(可优化为 O(1))。

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


1686. Stone Game VI(石子游戏 VI)

题目描述

Alice 和 Bob 轮流取石子,Alice 先手。Alice 取石子 i 得分 aliceValues[i],Bob 得分 bobValues[i]。

双方都知道对方的价值标准且采取最优策略。取完后得分高者胜,平局返回 0。

Alice 赢返回 1,Bob 赢返回 -1。

示例 1

复制代码
aliceValues=[1,3], bobValues=[2,1],输出 1。

示例 2

复制代码
aliceValues=[1,2], bobValues=[3,1],输出 0。

示例 3

复制代码
aliceValues=[2,4,3], bobValues=[1,6,7],输出 -1。

约束:

  • 1 <= n <= 10^5;1 <= aliceValues[i], bobValues[i] <= 100。

解题思路

解法一:按合计价值贪心(主提交版本)

  • 关键结论:每个石子对"双方得分差"的真实影响是 aliceValuesi + bobValuesi。
    因为若 Alice 拿 i 得 a_i,Bob 就少得 b_i,净差 +a_i+b_i;Bob 拿同理。故各方都优先取 a+b 最大的石子。
  • 按下标按 (ai+bi) 降序分配:Alice 取偶数位,Bob 取奇数位,比较总分。
    复杂度:时间复杂度 O(n log n),空间复杂度 O(n)。
    解法二:排序 + 模拟分配(对照)
  • 显式构造对象排序(演示不同代码风格)。
    复杂度:时间复杂度 O(n log n),空间复杂度 O(n)。

代码实现

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

public class Solution {

    public int stoneGameVI(int[] aliceValues, int[] bobValues) {
        int n = aliceValues.length;
        Integer[] idx = new Integer[n];
        for (int i = 0; i < n; i++) {
            idx[i] = i;
        }
        Arrays.sort(idx, new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                int sa = aliceValues[a] + bobValues[a];
                int sb = aliceValues[b] + bobValues[b];
                return sb - sa; // 降序
            }
        });
        int alice = 0, bob = 0;
        for (int t = 0; t < n; t++) {
            int i = idx[t];
            if (t % 2 == 0) {
                alice += aliceValues[i];
            } else {
                bob += bobValues[i];
            }
        }
        return Integer.compare(alice, bob);
    }

    public int stoneGameVIObj(int[] aliceValues, int[] bobValues) {
        int n = aliceValues.length;
        Integer[] idx = new Integer[n];
        for (int i = 0; i < n; i++) {
            idx[i] = i;
        }
        Arrays.sort(idx, (a, b) -> (aliceValues[b] + bobValues[b]) - (aliceValues[a] + bobValues[a]));
        int alice = 0, bob = 0;
        for (int t = 0; t < n; t++) {
            if (t % 2 == 0) {
                alice += aliceValues[idx[t]];
            } else {
                bob += bobValues[idx[t]];
            }
        }
        if (alice > bob) {
            return 1;
        }
        if (alice < bob) {
            return -1;
        }
        return 0;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:1
        System.out.println(sol.stoneGameVI(new int[]{1, 3}, new int[]{2, 1})
                + " " + sol.stoneGameVIObj(new int[]{1, 3}, new int[]{2, 1}));
        // 示例 2:0
        System.out.println(sol.stoneGameVI(new int[]{1, 2}, new int[]{3, 1})
                + " " + sol.stoneGameVIObj(new int[]{1, 2}, new int[]{3, 1}));
        // 示例 3:-1
        System.out.println(sol.stoneGameVI(new int[]{2, 4, 3}, new int[]{1, 6, 7})
                + " " + sol.stoneGameVIObj(new int[]{2, 4, 3}, new int[]{1, 6, 7}));
    }
}

复杂度分析

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


1687. Delivering Boxes from Storage to Ports(从仓库到码头运送箱子)

题目描述

有一辆货运卡车,每趟运输有箱子数限制 maxBoxes 和总重量限制 maxWeight。

boxes[i] = `portsi`, `weighti`,箱子必须按数组顺序运输。

卡车从仓库出发,依次把车上箱子送到对应码头(同一码头连续卸货不额外计行程),

全部卸完后必须回到仓库才能取下一批。返回把所有箱子送到码头并最终回到仓库的最少行程次数。

示例 1

复制代码
boxes=[[1,1],[2,1],[1,1]], ports=2, maxBoxes=3, maxWeight=3,输出 4。

示例 2

复制代码
boxes=[[1,2],[3,3],[3,1],[3,1],[2,4]], ports=3, maxBoxes=3, maxWeight=6,输出 6。

示例 3

复制代码
boxes=[[1,4],[1,2],[2,1],[2,1],[3,2],[3,4]], ports=3, maxBoxes=6, maxWeight=7,输出 6。

示例 4

复制代码
boxes=[[2,4],[2,5],[3,1],[3,2],[3,7],[3,1],[4,4],[1,3],[5,2]], ports=5, maxBoxes=5, maxWeight=7,输出 14。

约束:

  • 1 <= boxes.length <= 10^5;1 <= portsCount, maxBoxes, maxWeight <= 10^5。

解题思路

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

  • 把"一趟"的行程拆成:出仓库 1 次 + 码头切换次数 + 回仓库 1 次。
    若这一趟运送箱子下标 L+1...i(已装箱前 L 个),则行程 = dpL + 1 + (sumDi - sumDL+1) + 1,
    其中 sumDk 为前 k 个箱子中相邻码头不同的次数(前缀和)。
  • 左端点 L 需满足 箱数 <= maxBoxes 且 重量和 <= maxWeight(用重量前缀和判断)。
  • 对每个 i,dpi = min_{L 合法} { dpL - sumDL+1 + 2 + sumDi };
    括号中与 i 无关的部分用单调队列维护最小值,队头是候选最优 L。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。
    解法二:朴素 DP 验证(对照)
  • 枚举每个合法 L 转移,O(n^2)(仅演示验证小数据)。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n)。

代码实现

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

public class Solution {

    public int boxDelivering(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
        int n = boxes.length;
        long[] sumW = new long[n + 1];
        int[] sumD = new int[n + 2]; // +2 防止访问 sumD[i+1] 当 i=n 时越界
        for (int i = 1; i <= n; i++) {
            sumW[i] = sumW[i - 1] + boxes[i - 1][1];
            sumD[i] = sumD[i - 1] + (i >= 2 && boxes[i - 1][0] != boxes[i - 2][0] ? 1 : 0);
        }
        int[] dp = new int[n + 1];
        // 单调队列:{L, key = dp[L] - sumD[L+1] + 2},key 递增
        Deque<int[]> q = new ArrayDeque<>();
        q.offer(new int[]{0, dp[0] - sumD[1] + 2});
        int left = 0; // 当前合法窗口最左 L
        for (int i = 1; i <= n; i++) {
            while (i - left > maxBoxes || sumW[i] - sumW[left] > maxWeight) {
                left++;
            }
            while (!q.isEmpty() && q.peek()[0] < left) {
                q.poll();
            }
            dp[i] = q.peek()[1] + sumD[i];
            int key = dp[i] - sumD[i + 1] + 2;
            while (!q.isEmpty() && q.peekLast()[1] >= key) {
                q.pollLast();
            }
            q.offer(new int[]{i, key});
        }
        return dp[n];
    }

    public int boxDeliveringNaive(int[][] boxes, int portsCount, int maxBoxes, int maxWeight) {
        int n = boxes.length;
        long[] sumW = new long[n + 1];
        int[] sumD = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            sumW[i] = sumW[i - 1] + boxes[i - 1][1];
            sumD[i] = sumD[i - 1] + (i >= 2 && boxes[i - 1][0] != boxes[i - 2][0] ? 1 : 0);
        }
        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 L = 0; L < i; L++) {
                if (i - L > maxBoxes || sumW[i] - sumW[L] > maxWeight) {
                    continue;
                }
                int cost = dp[L] + 1 + (sumD[i] - sumD[L + 1]) + 1;
                dp[i] = Math.min(dp[i], cost);
            }
        }
        return dp[n];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:4
        System.out.println(sol.boxDelivering(new int[][]{{1, 1}, {2, 1}, {1, 1}}, 2, 3, 3)
                + " " + sol.boxDeliveringNaive(new int[][]{{1, 1}, {2, 1}, {1, 1}}, 2, 3, 3));
        // 示例 2:6
        System.out.println(sol.boxDelivering(new int[][]{{1, 2}, {3, 3}, {3, 1}, {3, 1}, {2, 4}}, 3, 3, 6)
                + " " + sol.boxDeliveringNaive(new int[][]{{1, 2}, {3, 3}, {3, 1}, {3, 1}, {2, 4}}, 3, 3, 6));
        // 示例 3:6
        System.out.println(sol.boxDelivering(new int[][]{{1, 4}, {1, 2}, {2, 1}, {2, 1}, {3, 2}, {3, 4}}, 3, 6, 7)
                + " " + sol.boxDeliveringNaive(new int[][]{{1, 4}, {1, 2}, {2, 1}, {2, 1}, {3, 2}, {3, 4}}, 3, 6, 7));
        // 示例 4:14
        System.out.println(sol.boxDelivering(new int[][]{{2, 4}, {2, 5}, {3, 1}, {3, 2}, {3, 7}, {3, 1}, {4, 4}, {1, 3}, {5, 2}}, 5, 5, 7)
                + " " + sol.boxDeliveringNaive(new int[][]{{2, 4}, {2, 5}, {3, 1}, {3, 2}, {3, 7}, {3, 1}, {4, 4}, {1, 3}, {5, 2}}, 5, 5, 7));
    }
}

复杂度分析

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

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


1688. Count of Matches in Tournament(比赛中的配对次数)

题目描述

赛制:偶数队伍数 n 时进行 n/2 场比赛;奇数时 n-1/2 场(1 队轮空)。

返回直到决出获胜队伍为止的总比赛场数。

示例 1

复制代码
n=7,输出 6。

示例 2

复制代码
n=14,输出 13。

约束:

  • 1 <= n <= 200。

解题思路

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

  • 每轮淘汰 n/2 支队伍;循环直到剩 1 队,累计比赛数。
    复杂度:时间复杂度 O(log n),空间复杂度 O(1)。
    解法二:数学(对照)
  • 每场比赛淘汰一支队伍,从 n 队到 1 队恰淘汰 n-1 支,故比赛数恒为 n-1。
    复杂度:时间复杂度 O(1),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public int numberOfMatches(int n) {
        int ans = 0;
        while (n > 1) {
            ans += n / 2;
            n = n / 2 + n % 2;
        }
        return ans;
    }

    public int numberOfMatchesMath(int n) {
        return n - 1;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:6
        System.out.println(sol.numberOfMatches(7) + " " + sol.numberOfMatchesMath(7));
        // 示例 2:13
        System.out.println(sol.numberOfMatches(14) + " " + sol.numberOfMatchesMath(14));
        // 附加
        System.out.println(sol.numberOfMatches(1) + " " + sol.numberOfMatchesMath(1));
        System.out.println(sol.numberOfMatches(200) + " " + sol.numberOfMatchesMath(200));
    }
}

复杂度分析

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

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


1689. Partitioning Into Minimum Number Of Deci-Binary Numbers(十-二进制数的最少数目)

题目描述

十-二进制数:十进制数字不含前导零,且每一位为 0 或 1。

给定表示十进制整数的字符串 n,返回和为 n 的十-二进制数的最少数目。

示例 1

复制代码
n="32",输出 3。

示例 2

复制代码
n="82734",输出 8。

示例 3

复制代码
n="27346209830709182346",输出 9。

约束:

  • 1 <= n.length <= 10^5;n 仅由数字组成。

解题思路

解法一:求最大数字(主提交版本)

  • 每一位数字 d 至少需要 d 个十-二进制数在该位贡献 1,故答案是各数字的最大值。
    复杂度:时间复杂度 O(len),空间复杂度 O(1)。
    解法二:字符流处理(对照)
  • 同理,遍历 char 流求最大。
    复杂度:时间复杂度 O(len),空间复杂度 O(1)。

代码实现

java 复制代码
public class Solution {

    public int minPartitions(String n) {
        int max = 0;
        for (int i = 0; i < n.length(); i++) {
            max = Math.max(max, n.charAt(i) - '0');
        }
        return max;
    }

    public int minPartitionsStream(String n) {
        int max = 0;
        for (char c : n.toCharArray()) {
            int d = c - '0';
            if (d == 9) {
                return 9; // 剪枝
            }
            max = Math.max(max, d);
        }
        return max;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:3
        System.out.println(sol.minPartitions("32") + " " + sol.minPartitionsStream("32"));
        // 示例 2:8
        System.out.println(sol.minPartitions("82734") + " " + sol.minPartitionsStream("82734"));
        // 示例 3:9
        System.out.println(sol.minPartitions("27346209830709182346") + " " + sol.minPartitionsStream("27346209830709182346"));
        // 附加
        System.out.println(sol.minPartitions("1") + " " + sol.minPartitionsStream("1"));
    }
}

复杂度分析

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


1690. Stone Game VII(石子游戏 VII)

题目描述

一排 n 块石子。玩家轮流移除最左或最右的石子,并获得该行剩余石子值之和的得分。

Bob 总是输,所以他尽力减小得分差值;Alice 目标是扩大得分差值。

双方最优,返回最终得分差值(Alice - Bob)。

示例 1

复制代码
stones=[5,3,1,4,2],输出 6。

示例 2

复制代码
stones=[7,90,5,1,100,10,10,2],输出 122。

约束:

  • 2 <= n <= 1000;1 <= stonesi <= 1000。

解题思路

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

  • dpij = 当前局面 i...j 中,先手最终能取得的 (先手总分 - 后手总分) 的最大值。
    若移除左端 i:先手得 sum(i+1...j),对手在 i+1...j 上按同规则得 dpi+1j 的"差值",
    故总差值 = sum(i+1...j) - dpi+1j。同理移除右端 = sum(i...j-1) - dpij-1。
    取两者较大。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n^2)。
    解法二:滚动数组优化(对照)
  • 只依赖 i+1 行与 j-1 列,可用一维滚动(按长度递增可从右向左滚动)。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n)。

代码实现

java 复制代码
public class Solution {

    public int stoneGameVII(int[] stones) {
        int n = stones.length;
        int[] pre = new int[n + 1];
        for (int i = 0; i < n; i++) {
            pre[i + 1] = pre[i] + stones[i];
        }
        int[][] dp = new int[n][n];
        for (int len = 2; len <= n; len++) {
            for (int i = 0; i + len - 1 < n; i++) {
                int j = i + len - 1;
                int left = (pre[j + 1] - pre[i + 1]) - dp[i + 1][j]; // 移除左端
                int right = (pre[j] - pre[i]) - dp[i][j - 1];        // 移除右端
                dp[i][j] = Math.max(left, right);
            }
        }
        return dp[0][n - 1];
    }

    public int stoneGameVIIRolling(int[] stones) {
        int n = stones.length;
        int[] pre = new int[n + 1];
        for (int i = 0; i < n; i++) {
            pre[i + 1] = pre[i] + stones[i];
        }
        int[] dp = new int[n]; // 当处理第 i 行时,dp[j] 的旧值 = dp[i+1][j]
        for (int i = n - 2; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                // dp[j] 尚未被本行覆盖,仍为上一行 dp[i+1][j]
                int left = (pre[j + 1] - pre[i + 1]) - dp[j];
                // dp[j-1] 刚被本行覆盖,即为 dp[i][j-1]
                int right = (pre[j] - pre[i]) - dp[j - 1];
                dp[j] = Math.max(left, right);
            }
        }
        return dp[n - 1];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:6
        System.out.println(sol.stoneGameVII(new int[]{5, 3, 1, 4, 2}));
        // 示例 2:122
        System.out.println(sol.stoneGameVII(new int[]{7, 90, 5, 1, 100, 10, 10, 2}));
    }
}

复杂度分析

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

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


1691. Maximum Height by Stacking Cuboids(堆叠长方体的最大高度)

题目描述

给定 n 个长方体 cuboids[i] = `widthi`, `lengthi`, `heighti`。

可选一个子集堆叠:若 width_i <= width_j 且 length_i <= length_j 且 height_i <= height_j,

则长方体 i 可以放在长方体 j 上。可旋转把长宽高重新排列。

返回堆叠长方体可获得的最大高度。

示例 1

复制代码
cuboids=[[50,45,20],[95,37,53],[45,23,12]],输出 190。

示例 2

复制代码
cuboids=[[38,25,45],[76,35,3]],输出 76。

示例 3

复制代码
cuboids=[[7,11,17],...6 个全排列],输出 102。

约束:

  • n <= 100;1 <= width, length, height <= 100。

解题思路

解法一:排序 + LIS DP(主提交版本)

  • 关键结论:每个长方体先内部排序(w <= l <= h),堆叠时以最长边为高最优;
    再按 (w, l, h) 字典序排序,之后问题转化为"三维偏序的最大权链":
    dpi = max height 以 i 为最底下,转移 dpi = h_i + max_{j 可放 i 上} dpj。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n)。
    解法二:同思路迭代写法(对照)
  • 与解法一同构,仅换种初始化(从顶到底)。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n)。

代码实现

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

public class Solution {

    public int maxHeight(int[][] cuboids) {
        int n = cuboids.length;
        // 每个长方体内部排序
        for (int[] c : cuboids) {
            Arrays.sort(c);
        }
        // 按 (w, l, h) 字典序升序
        Arrays.sort(cuboids, new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                if (a[0] != b[0]) {
                    return a[0] - b[0];
                }
                if (a[1] != b[1]) {
                    return a[1] - b[1];
                }
                return a[2] - b[2];
            }
        });
        int[] dp = new int[n];
        int ans = 0;
        for (int i = 0; i < n; i++) {
            dp[i] = cuboids[i][2];
            for (int j = 0; j < i; j++) {
                if (cuboids[j][0] <= cuboids[i][0]
                        && cuboids[j][1] <= cuboids[i][1]
                        && cuboids[j][2] <= cuboids[i][2]) {
                    dp[i] = Math.max(dp[i], dp[j] + cuboids[i][2]);
                }
            }
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:190
        System.out.println(sol.maxHeight(new int[][]{{50, 45, 20}, {95, 37, 53}, {45, 23, 12}}));
        // 示例 2:76
        System.out.println(sol.maxHeight(new int[][]{{38, 25, 45}, {76, 35, 3}}));
        // 示例 3:102
        System.out.println(sol.maxHeight(new int[][]{{7, 11, 17}, {7, 17, 11}, {11, 7, 17}, {11, 17, 7}, {17, 7, 11}, {17, 11, 7}}));
        // 附加:完全相等
        System.out.println(sol.maxHeight(new int[][]{{1, 1, 1}, {1, 1, 1}}));
    }
}

复杂度分析

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


1692. Count Ways to Distribute Candies(计算分配糖果的方式数)

题目描述

现有 n 颗不同糖果和 k 个相同手袋,每个手袋至少一颗糖果。

返回不同分配方式数(对 10^9+7 取模)。

不考虑手袋和糖果的摆放顺序(即集合划分:把 n 个不同元素分成 k 个非空无序组)。

示例 1

复制代码
n=3, k=2,输出 3。

示例 2

复制代码
n=4, k=2,输出 7。

示例 3

复制代码
n=20, k=5,输出 206085257。

约束:

  • 1 <= k <= n <= 1000。

解题思路

解法一:第二类斯特林数 DP(主提交版本)

  • S(n, k) 表示把 n 个不同元素分成 k 个非空无序集合的方案数。
    递推:S(n, k) = S(n-1, k-1) + k * S(n-1, k)。
    (新元素要么单独成组,要么放入 k 个已有组之一)
    复杂度:时间复杂度 O(nk),空间复杂度 O(n k)(可滚动到 O(k))。
    解法二:滚动数组优化(对照)
  • 依赖上一行,滚动一维数组按 k 降序更新。
    复杂度:时间复杂度 O(n*k),空间复杂度 O(k)。

代码实现

java 复制代码
public class Solution {

    private static final int MOD = 1000000007;

    public int waysToDistribute(int n, int k) {
        long[][] dp = new long[n + 1][k + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= Math.min(i, k); j++) {
                dp[i][j] = (dp[i - 1][j - 1] + j * dp[i - 1][j]) % MOD;
            }
        }
        return (int) dp[n][k];
    }

    public int waysToDistributeRolling(int n, int k) {
        long[] dp = new long[k + 1];
        dp[0] = 1; // S(0,0) = 1
        for (int i = 1; i <= n; i++) {
            int maxJ = Math.min(i, k);
            for (int j = maxJ; j >= 1; j--) {
                // dp[j-1] 是上一行的 S(i-1, j-1)(降序保证未被覆盖);dp[j] 是 S(i-1, j)
                dp[j] = (dp[j - 1] + j * dp[j]) % MOD;
            }
            if (maxJ == 0) {
                dp[0] = 1; // i=0 不会进来
            } else {
                dp[0] = 0; // S(i,0) = 0 for i >= 1,供下一行 j=1 使用
            }
        }
        return (int) dp[k];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:3
        System.out.println(sol.waysToDistribute(3, 2) + " " + sol.waysToDistributeRolling(3, 2));
        // 示例 2:7
        System.out.println(sol.waysToDistribute(4, 2) + " " + sol.waysToDistributeRolling(4, 2));
        // 示例 3:206085257
        System.out.println(sol.waysToDistribute(20, 5) + " " + sol.waysToDistributeRolling(20, 5));
    }
}

复杂度分析

复杂度:时间复杂度 O(nk),空间复杂度 O(n k)(可滚动到 O(k))。

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


1693. Daily Leads and Partners(每天的领导和合伙人)

题目类型:数据库(SQL)

题目描述

表:DailySales

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| date_id     | date    |
| make_name   | varchar |
| lead_id     | int     |
| partner_id  | int     |
+-------------+---------+
该表没有主键(具有唯一值的列)。它可能包含重复项。
该表包含日期、产品的名称,以及售给的领导和合伙人的编号。
名称只包含小写英文字母。

对于每一个 date_id 和 make_name,找出 **不同 **的 lead_id 以及 **不同 **的 partner_id 的数量。

按 任意顺序 返回结果表。

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

示例 1:

复制代码
**输入:**
DailySales 表:
+-----------+-----------+---------+------------+
| date_id   | make_name | lead_id | partner_id |
+-----------+-----------+---------+------------+
| 2020-12-8 | toyota    | 0       | 1          |
| 2020-12-8 | toyota    | 1       | 0          |
| 2020-12-8 | toyota    | 1       | 2          |
| 2020-12-7 | toyota    | 0       | 2          |
| 2020-12-7 | toyota    | 0       | 1          |
| 2020-12-8 | honda     | 1       | 2          |
| 2020-12-8 | honda     | 2       | 1          |
| 2020-12-7 | honda     | 0       | 1          |
| 2020-12-7 | honda     | 1       | 2          |
| 2020-12-7 | honda     | 2       | 1          |
+-----------+-----------+---------+------------+
**输出:**
+-----------+-----------+--------------+-----------------+
| date_id   | make_name | unique_leads | unique_partners |
+-----------+-----------+--------------+-----------------+
| 2020-12-8 | toyota    | 2            | 3               |
| 2020-12-7 | toyota    | 1            | 2               |
| 2020-12-8 | honda     | 2            | 2               |
| 2020-12-7 | honda     | 3            | 2               |
+-----------+-----------+--------------+-----------------+
**解释:**
在 2020-12-8,丰田(toyota)有领导者 = [0, 1] 和合伙人 = [0, 1, 2] ,同时本田(honda)有领导者 = [1, 2] 和合伙人 = [1, 2]。
在 2020-12-7,丰田(toyota)有领导者 = [0] 和合伙人 = [1, 2] ,同时本田(honda)有领导者 = [0, 1, 2] 和合伙人 = [1, 2]。

解题思路

思考

按日期与品牌统计不同线索数、不同合作伙伴数,直接 GROUP BY date_id, make_name \texttt{GROUP BY date\_id, make\_name} GROUP BY date_id, make_name,对两列做 COUNT ( DISTINCT ) \texttt{COUNT}(\texttt{DISTINCT}) COUNT(DISTINCT)。

考察GROUP BY 多列 + COUNT(DISTINCT):按日期和公司分组,分别统计不同 lead_id 与 partner_id 数量。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT
    date_id,
    make_name,
    COUNT(DISTINCT lead_id) AS unique_leads,
    COUNT(DISTINCT partner_id) AS unique_partners
FROM DailySales
GROUP BY 1, 2;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。


1694. Reformat Phone Number(重新格式化电话号码)

题目描述

电话号码由数字、空格、破折号组成。先删除所有空格和破折号,然后从左到右每 3 个一组分块,

直到剩下 4 个或更少数字再按 2-2 或 3-3 或 2-2-2 规则分块(不生成 1 位块)。

最后用破折号连接各块。

示例 1

复制代码
number="1-23-45 6",输出 "123-456"。

示例 2

复制代码
number="123 4-567",输出 "123-45-67"。

示例 3

复制代码
number="123 4-5678",输出 "123-456-78"。

示例 4

复制代码
number="12",输出 "12"。

示例 5

复制代码
number="--17-5 229 35-39475 ",输出 "175-229-353-94-75"。

约束:

  • 2 <= number.length <= 100;至少 2 个数字。

解题思路

解法一:逐块切割(主提交版本)

  • 先过滤出数字串 digits;length 余 1 时,最后 4 位拆成 2-2;其余每 3 位或末尾 2 位切块。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。
    解法二:索引拼接(对照)
  • 用 StringBuilder + 指针按规则拼接,等价实现。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。

代码实现

java 复制代码
public class Solution {

    public String reformatNumber(String number) {
        StringBuilder digits = new StringBuilder();
        for (int i = 0; i < number.length(); i++) {
            char c = number.charAt(i);
            if (c >= '0' && c <= '9') {
                digits.append(c);
            }
        }
        String s = digits.toString();
        StringBuilder sb = new StringBuilder();
        int i = 0;
        int n = s.length();
        while (i < n) {
            int remaining = n - i;
            if (remaining <= 4) {
                if (sb.length() > 0) {
                    sb.append('-');
                }
                if (remaining == 4) {
                    sb.append(s, i, i + 2).append('-').append(s, i + 2, i + 4);
                } else {
                    sb.append(s, i, n);
                }
                break;
            }
            if (sb.length() > 0) {
                sb.append('-');
            }
            sb.append(s, i, i + 3);
            i += 3;
        }
        return sb.toString();
    }

    public String reformatNumberScan(String number) {
        StringBuilder d = new StringBuilder();
        for (int i = 0; i < number.length(); i++) {
            char c = number.charAt(i);
            if (Character.isDigit(c)) {
                d.append(c);
            }
        }
        String s = d.toString();
        int n = s.length();
        StringBuilder sb = new StringBuilder();
        int i = 0;
        while (i < n) {
            int remain = n - i;
            if (remain > 4) {
                if (sb.length() > 0) {
                    sb.append('-');
                }
                sb.append(s, i, i + 3);
                i += 3;
            } else if (remain == 4) {
                if (sb.length() > 0) {
                    sb.append('-');
                }
                sb.append(s, i, i + 2).append('-').append(s, i + 2, i + 4);
                break;
            } else { // 2 或 3 位
                if (sb.length() > 0) {
                    sb.append('-');
                }
                sb.append(s, i, n);
                break;
            }
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:123-456
        System.out.println(sol.reformatNumber("1-23-45 6") + " " + sol.reformatNumberScan("1-23-45 6"));
        // 示例 2:123-45-67
        System.out.println(sol.reformatNumber("123 4-567") + " " + sol.reformatNumberScan("123 4-567"));
        // 示例 3:123-456-78
        System.out.println(sol.reformatNumber("123 4-5678") + " " + sol.reformatNumberScan("123 4-5678"));
        // 示例 4:12
        System.out.println(sol.reformatNumber("12") + " " + sol.reformatNumberScan("12"));
        // 示例 5:175-229-353-94-75
        System.out.println(sol.reformatNumber("--17-5 229 35-39475 ") + " " + sol.reformatNumberScan("--17-5 229 35-39475 "));
    }
}

复杂度分析

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


1695. Maximum Erasure Value(删除子数组的最大得分)

题目描述

从正整数数组 nums 中删除一个含有若干不同元素的子数组,得分为子数组元素之和。

返回只删除一个子数组可获得的最大得分。

示例 1

复制代码
nums=[4,2,4,5,6],输出 17([2,4,5,6])。

示例 2

复制代码
nums=[5,2,1,2,5,2,1,2,5],输出 8([5,2,1] 或 [1,2,5])。

约束:

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

解题思路

解法一:滑动窗口 + 频次计数(主提交版本)

  • 双指针维护窗口内元素互不重复的最大窗口,窗口和即得分,更新最大值。
    复杂度:时间复杂度 O(n),空间复杂度 O(V)(值域 10^4)。
    解法二:哈希表记录最近出现位置(对照)
  • 用 Map 记录每个值最近下标,遇到重复时把左边界跳到上次出现 +1。
    复杂度:时间复杂度 O(n),空间复杂度 O(V)。

代码实现

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

public class Solution {

    public int maximumUniqueSubarray(int[] nums) {
        int[] cnt = new int[10001];
        int left = 0;
        int sum = 0;
        int ans = 0;
        for (int right = 0; right < nums.length; right++) {
            int v = nums[right];
            cnt[v]++;
            sum += v;
            while (cnt[v] > 1) {
                int lv = nums[left];
                cnt[lv]--;
                sum -= lv;
                left++;
            }
            ans = Math.max(ans, sum);
        }
        return ans;
    }

    public int maximumUniqueSubarrayMap(int[] nums) {
        Map<Integer, Integer> last = new HashMap<>();
        int left = 0;
        int sum = 0;
        int ans = 0;
        for (int right = 0; right < nums.length; right++) {
            int v = nums[right];
            if (last.containsKey(v)) {
                // 收缩左边界到重复元素之后
                int nl = last.get(v) + 1;
                while (left < nl) {
                    sum -= nums[left];
                    last.remove(nums[left]);
                    left++;
                }
            }
            last.put(v, right);
            sum += v;
            ans = Math.max(ans, sum);
        }
        return ans;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:17
        System.out.println(sol.maximumUniqueSubarray(new int[]{4, 2, 4, 5, 6})
                + " " + sol.maximumUniqueSubarrayMap(new int[]{4, 2, 4, 5, 6}));
        // 示例 2:8
        System.out.println(sol.maximumUniqueSubarray(new int[]{5, 2, 1, 2, 5, 2, 1, 2, 5})
                + " " + sol.maximumUniqueSubarrayMap(new int[]{5, 2, 1, 2, 5, 2, 1, 2, 5}));
        // 附加:单元素
        System.out.println(sol.maximumUniqueSubarray(new int[]{1})
                + " " + sol.maximumUniqueSubarrayMap(new int[]{1}));
    }
}

复杂度分析

复杂度:时间复杂度 O(n),空间复杂度 O(V)(值域 10^4)。

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


1696. Jump Game VI(跳跃游戏 VI)

题目描述

从下标 0 出发,每步最多往前跳 k 步(不能跳出边界),得分为经过的所有数字之和。

返回到达最后位置(下标 n-1)能得到的最大得分。

示例 1

复制代码
nums=[1,-1,-2,4,-7,3], k=2,输出 7。

示例 2

复制代码
nums=[10,-5,-2,4,0,3], k=3,输出 17。

示例 3

复制代码
nums=[1,-5,-20,4,-1,3,-6,-3], k=2,输出 0。

约束:

  • 1 <= nums.length <= 105;-104 <= nums[i] <= 10^4;1 <= k <= n。

解题思路

解法一:滑动窗口最大值 DP(主提交版本)

  • dpi = numsi + max(dpi-k ... i-1),用单调递减双端队列维护窗口最大值。
    复杂度:时间复杂度 O(n),空间复杂度 O(n)。
    解法二:朴素 DP(对照,仅适合小数据)
  • 枚举 i 前 k 个取最大。
    复杂度:时间复杂度 O(n*k),空间复杂度 O(n)。

代码实现

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

public class Solution {

    public int maxResult(int[] nums, int k) {
        int n = nums.length;
        int[] dp = new int[n];
        Deque<Integer> dq = new ArrayDeque<>(); // 存下标,dp 值单调递减
        dp[0] = nums[0];
        dq.offer(0);
        for (int i = 1; i < n; i++) {
            // 弹出超出窗口 [i-k, i-1] 的下标
            while (!dq.isEmpty() && dq.peekFirst() < i - k) {
                dq.pollFirst();
            }
            dp[i] = nums[i] + dp[dq.peekFirst()];
            while (!dq.isEmpty() && dp[dq.peekLast()] <= dp[i]) {
                dq.pollLast();
            }
            dq.offerLast(i);
        }
        return dp[n - 1];
    }

    public int maxResultNaive(int[] nums, int k) {
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = nums[0];
        for (int i = 1; i < n; i++) {
            int best = Integer.MIN_VALUE;
            for (int j = Math.max(0, i - k); j < i; j++) {
                best = Math.max(best, dp[j]);
            }
            dp[i] = nums[i] + best;
        }
        return dp[n - 1];
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:7
        System.out.println(sol.maxResult(new int[]{1, -1, -2, 4, -7, 3}, 2)
                + " " + sol.maxResultNaive(new int[]{1, -1, -2, 4, -7, 3}, 2));
        // 示例 2:17
        System.out.println(sol.maxResult(new int[]{10, -5, -2, 4, 0, 3}, 3)
                + " " + sol.maxResultNaive(new int[]{10, -5, -2, 4, 0, 3}, 3));
        // 示例 3:0
        System.out.println(sol.maxResult(new int[]{1, -5, -20, 4, -1, 3, -6, -3}, 2)
                + " " + sol.maxResultNaive(new int[]{1, -5, -20, 4, -1, 3, -6, -3}, 2));
    }
}

复杂度分析

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

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


1697. Checking Existence of Edge Length Limited Paths(检查边长度限制的路径是否存在)

题目描述

n 个点的无向图,边集 edgeList[i] = `ui`, `vi`, `disi`(两点之间可能有多条边)。

查询 queries[j] = `pj`, `qj`, `limitj`:判断是否存在从 pj 到 qj 的路径,且路径上每条边严格小于 limitj。

返回布尔数组。

示例 1

复制代码
n=3, edges=[[0,1,2],[1,2,4],[2,0,8],[1,0,16]], queries=[[0,1,2],[0,2,5]],输出 [false,true]。

示例 2

复制代码
n=5, edges=[[0,1,10],[1,2,5],[2,3,9],[3,4,13]], queries=[[0,4,14],[1,4,13]],输出 [true,false]。

约束:

  • 2 <= n <= 10^5;1 <= edgeList.length, queries.length <= 10^5;边权与 limit <= 10^9。

解题思路

解法一:离线 + 并查集(主提交版本)

  • 查询按 limit 升序排序,边按 dis 升序排序;处理查询时将所有 dis < limit 的边加入并查集,
    然后检查 pj 与 qj 是否连通。
  • 关键点:边严格小于 limit,所以加入 dis < limit 的边。
    复杂度:时间复杂度 O(E log E + Q log Q + (E+Q) α(n)),空间 O(n)。
    解法二:并查集可撤销 + 在线(对照思路说明)
  • 若需要在线回答,可用按时间戳的并查集或 Q 次 BFS;本题主流做法即离线并查集。
    复杂度:O(Q * (E+Q))(朴素,仅作比较)。

代码实现

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

public class Solution {

    public boolean[] distanceLimitedPathsExist(int n, int[][] edgeList, int[][] queries) {
        int qn = queries.length;
        Integer[] order = new Integer[qn];
        for (int i = 0; i < qn; i++) {
            order[i] = i;
        }
        Arrays.sort(order, new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return queries[a][2] - queries[b][2];
            }
        });
        Arrays.sort(edgeList, new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                return a[2] - b[2];
            }
        });
        DSU dsu = new DSU(n);
        boolean[] ans = new boolean[qn];
        int e = 0;
        for (int qi : order) {
            int[] q = queries[qi];
            while (e < edgeList.length && edgeList[e][2] < q[2]) {
                dsu.union(edgeList[e][0], edgeList[e][1]);
                e++;
            }
            ans[qi] = dsu.find(q[0]) == dsu.find(q[1]);
        }
        return ans;
    }

    private static class DSU {
        private int[] parent;

        DSU(int n) {
            parent = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        int find(int x) {
            while (parent[x] != x) {
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }

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

    private static String join(boolean[] a) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < a.length; i++) {
            if (i > 0) {
                sb.append(",");
            }
            sb.append(a[i]);
        }
        return sb.append("]").toString();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:[false,true]
        System.out.println(join(sol.distanceLimitedPathsExist(3,
                new int[][]{{0, 1, 2}, {1, 2, 4}, {2, 0, 8}, {1, 0, 16}},
                new int[][]{{0, 1, 2}, {0, 2, 5}})));
        // 示例 2:[true,false]
        System.out.println(join(sol.distanceLimitedPathsExist(5,
                new int[][]{{0, 1, 10}, {1, 2, 5}, {2, 3, 9}, {3, 4, 13}},
                new int[][]{{0, 4, 14}, {1, 4, 13}})));
    }
}

复杂度分析

复杂度:时间复杂度 O(E log E + Q log Q + (E+Q) α(n)),空间 O(n)。

复杂度:O(Q * (E+Q))(朴素,仅作比较)。


1698. Number of Distinct Substrings in a String(字符串的不同子字符串个数)

题目描述

给定字符串 s,返回 s 的不同子字符串个数。

示例 1

复制代码
s="aabbaba",输出 21。

示例 2

复制代码
s="abcdefg",输出 28。

约束:

  • 1 <= s.length <= 500;小写字母。
  • 进阶:O(n) 时间(后缀自动机/后缀数组)。

解题思路

解法一:暴力 HashSet(主提交版本)

  • 枚举所有子串区间加入集合统计,O(n^3) 转 O(n^3) 因 substring 复制,用 hashCode 优化为 O(n^2):
    对每个起点 i,向后扩展维护哈希值(或直接用 HashSet,n <= 500 可行)。
    复杂度:时间复杂度 O(n^2 * len) ≈ O(n^3) 最坏;实际 n=500 可接受,空间 O(n^2)。
    解法二:后缀自动机 SAM(对照,O(n))
  • 构建后缀自动机,每个状态代表的子串集合大小为 lenst - lenlink\[st],全量求和即不同子串数。
    复杂度:时间复杂度 O(n),空间复杂度 O(n * 字母表)。

代码实现

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

public class Solution {

    public int countDistinct(String s) {
        int n = s.length();
        Set<String> set = new HashSet<>();
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j <= n; j++) {
                set.add(s.substring(i, j));
            }
        }
        return set.size();
    }

    public int countDistinctSam(String s) {
        SAM sam = new SAM();
        for (char c : s.toCharArray()) {
            sam.extend(c - 'a');
        }
        long ans = 0;
        for (int st = 1; st < sam.size; st++) {
            ans += sam.len[st] - sam.len[sam.link[st]];
        }
        return (int) ans;
    }

    private static class SAM {
        int[][] next = new int[2000][26];
        int[] link = new int[2000];
        int[] len = new int[2000];
        int last = 0;
        int size = 1;

        SAM() {
            link[0] = -1;
        }

        void extend(int c) {
            int cur = size++;
            len[cur] = len[last] + 1;
            int p = last;
            while (p != -1 && next[p][c] == 0) {
                next[p][c] = cur;
                p = link[p];
            }
            if (p == -1) {
                link[cur] = 0;
            } else {
                int q = next[p][c];
                if (len[p] + 1 == len[q]) {
                    link[cur] = q;
                } else {
                    int clone = size++;
                    len[clone] = len[p] + 1;
                    System.arraycopy(next[q], 0, next[clone], 0, 26);
                    link[clone] = link[q];
                    while (p != -1 && next[p][c] == q) {
                        next[p][c] = clone;
                        p = link[p];
                    }
                    link[q] = clone;
                    link[cur] = clone;
                }
            }
            last = cur;
        }
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:21
        System.out.println(sol.countDistinct("aabbaba") + " " + sol.countDistinctSam("aabbaba"));
        // 示例 2:28
        System.out.println(sol.countDistinct("abcdefg") + " " + sol.countDistinctSam("abcdefg"));
        // 附加:全同字符
        System.out.println(sol.countDistinct("aaa") + " " + sol.countDistinctSam("aaa"));
    }
}

复杂度分析

复杂度:时间复杂度 O(n^2 * len) ≈ O(n^3) 最坏;实际 n=500 可接受,空间 O(n^2)。

复杂度:时间复杂度 O(n),空间复杂度 O(n * 字母表)。


1699. Number of Calls Between Two Persons(两人之间的通话次数)

题目类型:数据库(SQL)

题目描述

表: Calls

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| from_id     | int     |
| to_id       | int     |
| duration    | int     |
+-------------+---------+
该表没有主键(具有唯一值的列),它可能包含重复项。
该表包含 from_id 与 to_id 间的一次电话的时长。
from_id != to_id

编写解决方案,统计每一对用户 (person1, person2) 之间的通话次数和通话总时长,其中 person1 < person2 。

以 任意顺序 返回结果表。

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

示例 1:

复制代码
**输入:**
Calls 表:
+---------+-------+----------+
| from_id | to_id | duration |
+---------+-------+----------+
| 1       | 2     | 59       |
| 2       | 1     | 11       |
| 1       | 3     | 20       |
| 3       | 4     | 100      |
| 3       | 4     | 200      |
| 3       | 4     | 200      |
| 4       | 3     | 499      |
+---------+-------+----------+
**输出:**
+---------+---------+------------+----------------+
| person1 | person2 | call_count | total_duration |
+---------+---------+------------+----------------+
| 1       | 2       | 2          | 70             |
| 1       | 3       | 1          | 20             |
| 3       | 4       | 4          | 999            |
+---------+---------+------------+----------------+
**解释:**
用户 1 和 2 打过 2 次电话,总时长为 70 (59 + 11)。
用户 1 和 3 打过 1 次电话,总时长为 20。
用户 3 和 4 打过 4 次电话,总时长为 999 (100 + 200 + 200 + 499)。

解题思路

思考

通话无向, ( a , b ) (a,b) (a,b) 与 ( b , a ) (b,a) (b,a) 应算作同一对。把较小编号定为 person1 \texttt{person1} person1、较大者为 person2 \texttt{person2} person2 再分组。

IF \texttt{IF} IF 规范化两端,按两列 GROUP BY \texttt{GROUP BY} GROUP BY,统计次数与时长之和。

考察LEAST/GREATEST + 分组聚合:把每通电话的两人规范为 (person1 < person2)并按该对分组,统计通话次数与总时长。

代码实现

sql 复制代码
# Write your MySQL query statement below
SELECT
    IF(from_id < to_id, from_id, to_id) AS person1,
    IF(from_id < to_id, to_id, from_id) AS person2,
    COUNT(1) AS call_count,
    SUM(duration) AS total_duration
FROM Calls
GROUP BY 1, 2;

复杂度分析

本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。


1700. Number of Students Unable to Eat Lunch(无法吃午餐的学生数量)

题目描述

学生们排成一队,三明治放在一个栈里。每一轮:

  • 若队首学生喜欢栈顶三明治,则拿走并离开;
  • 否则该学生回到队尾。
    直到所有学生都不喜欢栈顶三明治为止。
    返回无法吃午餐的学生数量。

示例 1

复制代码
students=[1,1,0,0], sandwiches=[0,1,0,1],输出 0。

示例 2

复制代码
students=[1,1,1,0,0,1], sandwiches=[1,0,0,0,1,1],输出 3。

约束:

  • 1 <= length <= 100;值为 0 或 1。

解题思路

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

  • 只需统计学生中喜欢 0 和 1 的人数;遍历三明治栈:
    若栈顶类型还有喜欢它的学生,则消耗一人继续;否则无法继续,剩余学生数即答案。
    复杂度:时间复杂度 O(n),空间复杂度 O(1)。
    解法二:队列模拟(对照)
  • 用数组/队列逐轮模拟完整过程(适合演示过程语义)。
    复杂度:时间复杂度 O(n^2),空间复杂度 O(n)(对照)。

代码实现

java 复制代码
public class Solution {

    public int countStudents(int[] students, int[] sandwiches) {
        int zero = 0, one = 0;
        for (int s : students) {
            if (s == 0) {
                zero++;
            } else {
                one++;
            }
        }
        for (int t : sandwiches) {
            if (t == 0) {
                if (zero == 0) {
                    return one;
                }
                zero--;
            } else {
                if (one == 0) {
                    return zero;
                }
                one--;
            }
        }
        return 0;
    }

    public int countStudentsSimulate(int[] students, int[] sandwiches) {
        int n = students.length;
        int[] queue = new int[2 * n]; // 出队后回到队尾,最多追加 n 次
        int head = 0, tail = 0;
        for (int s : students) {
            queue[tail++] = s;
        }
        int si = 0;
        int failStreak = 0; // 连续无人匹配次数
        while (head < tail && si < n) {
            if (queue[head] == sandwiches[si]) {
                head++;
                si++;
                failStreak = 0;
            } else {
                queue[tail++] = queue[head++];
                failStreak++;
                if (failStreak >= tail - head + 1) {
                    break; // 所有剩余学生都试过一次仍然不行
                }
            }
        }
        return tail - head;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        // 示例 1:0
        System.out.println(sol.countStudents(new int[]{1, 1, 0, 0}, new int[]{0, 1, 0, 1})
                + " " + sol.countStudentsSimulate(new int[]{1, 1, 0, 0}, new int[]{0, 1, 0, 1}));
        // 示例 2:3
        System.out.println(sol.countStudents(new int[]{1, 1, 1, 0, 0, 1}, new int[]{1, 0, 0, 0, 1, 1})
                + " " + sol.countStudentsSimulate(new int[]{1, 1, 1, 0, 0, 1}, new int[]{1, 0, 0, 0, 1, 1}));
    }
}

复杂度分析

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

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

相关推荐
愚农搬码1 小时前
电子表单、动态表单、表单设计器和表单引擎到底有什么区别?
java·后端
CarIise1 小时前
Spring 框架AOP与 XML 基础
java·开发语言
m0_639906941 小时前
SpringBoot 实现大模型统一调度网关|对接 OpenAI、Claude 协议差异与适配方案
java·spring boot·spring
雪芽蓝域zzs1 小时前
第 1 章:IDEA 全局配置 Maven + 新建 SpringBoot3 Maven 项目
java·maven·intellij-idea
代码什么用1 小时前
Maven&Nexus
java·maven
开开心心就好1 小时前
网页图片批量下载工具,输入链接就保存
服务器·算法·leetcode·支持向量机·pdf·ocr·1024程序员节
顧棟1 小时前
OpenAPI 网关:鉴权 + 流量治理 一体化设计方案与实现
java·spring boot·设计模式
Rain的Java大神之路1 小时前
「设计一个在线文档协同编辑系统」:从 WebSocket、OT/CRDT 到快照回放,一篇讲透这道面试顶流题
java·后端·面试