LeetCode 1681-1700. 题解合集
文章目录
- [LeetCode 1681-1700. 题解合集](#LeetCode 1681-1700. 题解合集)
-
- [1681. Minimum Incompatibility(最小不兼容性)](#1681. Minimum Incompatibility(最小不兼容性))
- [1682. Longest Palindromic Subsequence II(最长回文子序列 II)](#1682. Longest Palindromic Subsequence II(最长回文子序列 II))
- [1683. Invalid Tweets(无效的推文)](#1683. Invalid Tweets(无效的推文))
- [1684. Count the Number of Consistent Strings(统计一致字符串的数目)](#1684. Count the Number of Consistent Strings(统计一致字符串的数目))
- [1685. Sum of Absolute Differences in a Sorted Array(有序数组中差绝对值之和)](#1685. Sum of Absolute Differences in a Sorted Array(有序数组中差绝对值之和))
- [1686. Stone Game VI(石子游戏 VI)](#1686. Stone Game VI(石子游戏 VI))
- [1687. Delivering Boxes from Storage to Ports(从仓库到码头运送箱子)](#1687. Delivering Boxes from Storage to Ports(从仓库到码头运送箱子))
- [1688. Count of Matches in Tournament(比赛中的配对次数)](#1688. Count of Matches in Tournament(比赛中的配对次数))
- [1689. Partitioning Into Minimum Number Of Deci-Binary Numbers(十-二进制数的最少数目)](#1689. Partitioning Into Minimum Number Of Deci-Binary Numbers(十-二进制数的最少数目))
- [1690. Stone Game VII(石子游戏 VII)](#1690. Stone Game VII(石子游戏 VII))
- [1691. Maximum Height by Stacking Cuboids(堆叠长方体的最大高度)](#1691. Maximum Height by Stacking Cuboids(堆叠长方体的最大高度))
- [1692. Count Ways to Distribute Candies(计算分配糖果的方式数)](#1692. Count Ways to Distribute Candies(计算分配糖果的方式数))
- [1693. Daily Leads and Partners(每天的领导和合伙人)](#1693. Daily Leads and Partners(每天的领导和合伙人))
- [1694. Reformat Phone Number(重新格式化电话号码)](#1694. Reformat Phone Number(重新格式化电话号码))
- [1695. Maximum Erasure Value(删除子数组的最大得分)](#1695. Maximum Erasure Value(删除子数组的最大得分))
- [1696. Jump Game VI(跳跃游戏 VI)](#1696. Jump Game VI(跳跃游戏 VI))
- [1697. Checking Existence of Edge Length Limited Paths(检查边长度限制的路径是否存在)](#1697. Checking Existence of Edge Length Limited Paths(检查边长度限制的路径是否存在))
- [1698. Number of Distinct Substrings in a String(字符串的不同子字符串个数)](#1698. Number of Distinct Substrings in a String(字符串的不同子字符串个数))
- [1699. Number of Calls Between Two Persons(两人之间的通话次数)](#1699. Number of Calls Between Two Persons(两人之间的通话次数))
- [1700. Number of Students Unable to Eat Lunch(无法吃午餐的学生数量)](#1700. Number of Students Unable to Eat Lunch(无法吃午餐的学生数量))
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)(对照)。