LeetCode 1861-1880. 题解合集
文章目录
- [LeetCode 1861-1880. 题解合集](#LeetCode 1861-1880. 题解合集)
-
- [1861. Rotating the Box(旋转盒子)](#1861. Rotating the Box(旋转盒子))
- [1862. Sum of Floored Pairs(向下取整数对和)](#1862. Sum of Floored Pairs(向下取整数对和))
- [1863. Sum of All Subset XOR Totals(找出所有子集的异或总和再求和)](#1863. Sum of All Subset XOR Totals(找出所有子集的异或总和再求和))
- [1864. Minimum Number of Swaps to Make the Binary String Alternating](#1864. Minimum Number of Swaps to Make the Binary String Alternating)
- [1865. Finding Pairs With a Certain Sum(找出和为指定值的下标对)](#1865. Finding Pairs With a Certain Sum(找出和为指定值的下标对))
- [1866. Number of Ways to Rearrange Sticks With K Sticks Visible](#1866. Number of Ways to Rearrange Sticks With K Sticks Visible)
- [1867. Orders With Maximum Quantity Above Average(最大数量高于平均水平的订单)](#1867. Orders With Maximum Quantity Above Average(最大数量高于平均水平的订单))
- [1868. Product of Two Run-Length Encoded Arrays(两个行程编码数组的积)](#1868. Product of Two Run-Length Encoded Arrays(两个行程编码数组的积))
- [1869. Longer Contiguous Segments of Ones than Zeros(哪种连续子字符串更长)](#1869. Longer Contiguous Segments of Ones than Zeros(哪种连续子字符串更长))
- [1870. Minimum Speed to Arrive on Time(准时到达的列车最小时速)](#1870. Minimum Speed to Arrive on Time(准时到达的列车最小时速))
- [1871. Jump Game VII(跳跃游戏 VII)](#1871. Jump Game VII(跳跃游戏 VII))
- [1872. Stone Game VIII(石子游戏 VIII)](#1872. Stone Game VIII(石子游戏 VIII))
- [1873. Calculate Special Bonus(计算特殊奖金)](#1873. Calculate Special Bonus(计算特殊奖金))
- [1874. Minimize Product Sum of Two Arrays(两个数组的最小乘积和)](#1874. Minimize Product Sum of Two Arrays(两个数组的最小乘积和))
- [1875. Group Employees of the Same Salary(将工资相同的雇员分组)](#1875. Group Employees of the Same Salary(将工资相同的雇员分组))
- [1876. Substrings of Size Three with Distinct Characters](#1876. Substrings of Size Three with Distinct Characters)
- [1877. Minimize Maximum Pair Sum in Array(数组中最大数对和的最小值)](#1877. Minimize Maximum Pair Sum in Array(数组中最大数对和的最小值))
- [1878. Get Biggest Three Rhombus Sums in a Grid(矩阵中最大的三个菱形和)](#1878. Get Biggest Three Rhombus Sums in a Grid(矩阵中最大的三个菱形和))
- [1879. Minimum XOR Sum of Two Arrays(两个数组最小的异或值之和)](#1879. Minimum XOR Sum of Two Arrays(两个数组最小的异或值之和))
- [1880. Check if Word Equals Summation of Two Words(检查某单词是否等于两单词之和)](#1880. Check if Word Equals Summation of Two Words(检查某单词是否等于两单词之和))
1861. Rotating the Box(旋转盒子)
题目描述
给你一个 m x n 的字符矩阵 boxGrid,它表示一个箱子的侧视图。箱子的每一个格子可能为:
- '#' 表示石头
- '*' 表示固定的障碍物
- '.' 表示空位置
这个箱子被顺时针旋转 90 度,由于重力原因,部分石头的位置会发生改变。每个石头会垂直掉落,
直到它遇到障碍物、另一个石头或者箱子的底部。重力不会影响障碍物的位置,同时箱子旋转不会产生
惯性,也就是说石头的水平位置不会发生改变。
题目保证初始时boxGrid中的石头要么在一个障碍物上,要么在另一个石头上,要么在箱子的底部。
请你返回一个nxm的矩阵,表示按照上述旋转后,箱子内的结果。
示例 1
- 输入 :
box= \["#",".","#"] - 输出:\[".","#","#"]
示例 2
- 输入 :
box= \["#",".","*",".","#","#","*","."] - 输出 :\["#",".","#","#","*","*",".","."]
示例 3
- 输入 :
box= \["#","#","*",".","*",".","#","#","#","\*",".",".","#","#","#",".","#","."] - 输出 :\[".","#","#",".","#","#","#","#","*","#","* ",".","#",".","\*","#",".","."]
提示: - m == boxGrid.length- n == boxGrid[i].length- 1 <= m, n <= 500-
boxGrid[i][j]只可能是 '#'、'*' 或者 '.'。
解题思路
解法一:重力沉降 + 旋转(主提交版本)
对每一行从左到右用"下一个空位"指针做重力沉降(所有石头落向行尾),
然后顺时针旋转 90° 得到结果的物理解释:旋转结果 resultcm-1-r = boxGridrc,
即原矩阵第 r 行变成结果矩阵第 (m-1-r) 列。沉降 O(mn),旋转 O(m n)。
解法二:直接构造,对照
不显式沉降,而是对旋转后的每个格子 resultij,倒推其在原矩阵
同一列的石头位置分布:由于每列独立,先扫描该列统计"底部可用的空位",再按石头数量从下往
上放置。思路等价但以列为视角,帮助加深理解。
代码实现
java
import java.util.Arrays;
public class Solution {
public char[][] rotateTheBox(char[][] boxGrid) {
int m = boxGrid.length, n = boxGrid[0].length;
// 第一步:每一行做重力沉降(石头向右下落,障碍物不动)
for (int r = 0; r < m; r++) {
int write = n - 1; // 可写入位置(从行末开始)
for (int c = n - 1; c >= 0; c--) {
if (boxGrid[r][c] == '*') { // 障碍物:write 跳到它的左边
write = c - 1;
} else if (boxGrid[r][c] == '#') {
if (write != c) {
boxGrid[r][write] = '#';
boxGrid[r][c] = '.';
}
write--;
}
}
}
// 第二步:顺时针旋转 90 度。result[c][m-1-r] = boxGrid[r][c]
char[][] res = new char[n][m];
for (int r = 0; r < m; r++) {
for (int c = 0; c < n; c++) {
res[c][m - 1 - r] = boxGrid[r][c];
}
}
return res;
}
public char[][] rotateTheBoxCol(char[][] boxGrid) {
int m = boxGrid.length, n = boxGrid[0].length;
char[][] res = new char[n][m];
// 旋转后坐标 (i, j) 对应原矩阵 (m-1-j, i),即每列取自原矩阵同一行方向的逆序
for (int j = 0; j < m; j++) { // 结果第 j 列来自原矩阵第 (m-1-j) 行
// 统计原行中石头数量,并从结果列底部向上放置
for (int i = n - 1; i >= 0; i--) {
res[i][j] = '.';
}
int bottom = n - 1;
for (int c = n - 1; c >= 0; c--) {
char cur = boxGrid[m - 1 - j][c];
if (cur == '*') {
res[c][j] = '*';
bottom = c - 1;
} else if (cur == '#') {
res[bottom][j] = '#';
bottom--;
}
}
}
return res;
}
private static String gridStr(char[][] g) {
StringBuilder sb = new StringBuilder();
for (char[] row : g) {
sb.append(Arrays.toString(row)).append('\n');
}
return sb.toString();
}
private static boolean same(char[][] a, char[][] b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (!Arrays.equals(a[i], b[i])) return false;
}
return true;
}
public static void main(String[] args) {
Solution s = new Solution();
char[][][] cases = {
{{'#', '.', '#'}},
{{'#', '.', '*', '.'}, {'#', '#', '*', '.'}},
{{'#', '#', '*', '.', '*', '.'}, {'#', '#', '#', '*', '.', '.'}, {'#', '#', '#', '.', '#', '.'}},
{{'.', '#', '*'}, {'#', '.', '.'}}
};
for (char[][] c : cases) {
char[][] r1 = s.rotateTheBox(c);
char[][] r2 = s.rotateTheBoxCol(c);
System.out.println((same(r1, r2) ? "OK" : "FAIL"));
}
}
}
复杂度分析
1862. Sum of Floored Pairs(向下取整数对和)
题目描述
给你一个整数数组 nums,请你返回所有下标对 0 <= i, j < nums.length 的 floor(nums[i] / nums[j])
之和,结果对 10^9 + 7 取余。函数 floor() 返回输入数字的整数部分。
注意:这里包含 i == j 的情况(官方题面要求 i、j 为任意下标对,含相同下标)。
示例 1
- 输入 :
nums= 2,5,9 - 输出:10
- 解释 :floor(2/5)=floor(2/9)=floor(5/9)=0,floor(2/2)=floor(5/5)=floor(9/9)=1,
floor(5/2)=2,floor(9/2)=4,floor(9/5)=1,总和 10。
示例 2
- 输入 :
nums= 7,7,7,7,7,7,7 - 输出 :49
提示: - 1 <= nums.length <= 10^5- 1 <= nums[i] <= 10^5
解题思路
设数值上限 U = max(nums)(<= 10^5)。
解法一:值域计数 + 调和级数枚举,主提交版本
统计每个取值 v 的出现次数 freqv。
对于一个取值为 v 的数作为分母,分子 x 落在 m\*v, (m+1)*v-1 时 floor(x/v) = m。
对每个 v,枚举 m = 1...U/v,用值域前缀和 O(1) 得到该区间内数字个数 cnt,累加
cnt * m * freqv。总复杂度 O(U * (1 + 1/2 + ... + 1)) = O(U log U)。
解法二:排序 + 二分,对照
排序后对每个分母 x = numsj,用二分找到每个商区间
m* x, (m+1)\*x-1 的元素个数累加(前缀和快速区间计数),思路与解法一同构但以原数组
为基础;另附一个 O(n^2) 暴力作为最朴素理解(小规模验证用)。
代码实现
java
import java.util.Arrays;
public class Solution {
private static final int MOD = 1000000007;
public int sumOfFlooredPairs(int[] nums) {
int max = 0;
for (int v : nums) {
if (v > max) max = v;
}
long[] freq = new long[max + 1];
for (int v : nums) {
freq[v]++;
}
long[] pref = new long[max + 1];
for (int i = 1; i <= max; i++) {
pref[i] = pref[i - 1] + freq[i];
}
long ans = 0;
for (int v = 1; v <= max; v++) {
if (freq[v] == 0) continue;
long fv = freq[v];
// 商 m = 1..max/v,分子取值范围 [m*v, (m+1)*v-1]
for (int m = 1; m * v <= max; m++) {
int hi = Math.min((m + 1) * v - 1, max);
long cnt = pref[hi] - pref[m * v - 1];
ans = (ans + (cnt % MOD) * (m % MOD) % MOD * (fv % MOD) % MOD) % MOD;
}
// 商为 0 的部分 floor(x/v)=0,贡献为 0,忽略
}
return (int) ans;
}
private int maxAll;
private long[] p;
public int sumOfFlooredPairsSorted(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
maxAll = nums[n - 1];
p = new long[n + 1];
for (int i = 0; i < n; i++) {
p[i + 1] = p[i] + nums[i]; // 本解法实际上不用前缀和,改用二分定位
}
long ans = 0;
// 对每个下标 j 作为分母
for (int j = 0; j < n; j++) {
int x = nums[j];
// 商 m 从 1 开始,直到 m*x > max
for (int m = 1; m * x <= maxAll; m++) {
int lo = m * x;
int hi = Math.min((m + 1) * x - 1, maxAll);
int from = lowerBound(nums, lo);
int to = lowerBound(nums, hi + 1) - 1;
if (to >= from) {
ans = (ans + (long) (to - from + 1) * m) % MOD;
}
}
}
return (int) ans;
}
private int lowerBound(int[] a, int key) {
int lo = 0, hi = a.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (a[mid] < key) lo = mid + 1;
else hi = mid;
}
return lo;
}
public int sumOfFlooredPairsBrute(int[] nums) {
long ans = 0;
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums.length; j++) {
ans = (ans + nums[i] / nums[j]) % MOD;
}
}
return (int) ans;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] cases = {
{2, 5, 9},
{7, 7, 7, 7, 7, 7, 7},
{1, 1, 1},
{2, 2, 2, 3, 3},
{10, 20, 30, 40, 50}
};
for (int[] c : cases) {
int r1 = s.sumOfFlooredPairs(c);
int r2 = s.sumOfFlooredPairsSorted(c);
int r3 = s.sumOfFlooredPairsBrute(c);
System.out.println(r1 + " | " + r2 + " | " + r3
+ (r1 == r2 && r1 == r3 ? " OK" : " FAIL"));
}
}
}
复杂度分析
1863. Sum of All Subset XOR Totals(找出所有子集的异或总和再求和)
题目描述
一个数组的异或总和定义为数组中所有元素按位 XOR 的结果;如果数组为空,则异或总和为 0。
例如,数组 2,5,6 的异或总和为 2 XOR 5 XOR 6 = 1。
给你一个数组 nums,请你求出 nums 中每个子集的异或总和,计算并返回这些值之和。
注意:在本题中,元素相同但下标不同的子集应多次计数。
数组 a 是数组 b 的一个子集的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a。
示例 1
- 输入 :
nums= 1,3 - 输出:6
- 解释:\[\]→0、1→1、3→3、1,3→2,总和 6。
示例 2
- 输入 :
nums= 5,1,6 - 输出:28
示例 3
- 输入 :
nums= 3,4,5,6,7,8 - 输出 :480
提示: - 1 <= nums.length <= 12- 1 <= nums[i] <= 20
解题思路
解法一:按位贡献公式,主提交版本
对于一个位 b,若任一元素该位为 1,则所有 2^n 个子集中
恰有一半子集该位异或结果为 1(因为只考虑该位,选择含奇/偶数个"该位为 1 的元素"的
组合数各占一半)。因此答案 = Σ_b (2^(n-1) * 2^b)。复杂度 O(n)。
解法二:递归枚举子集,对照
dfs 遍历每个元素"选/不选",累加每个子集的异或和。
O(2^n) 状态枚举,n <= 12 完全可行,逻辑直观可验证公式法。
代码实现
java
public class Solution {
public int subsetXORSum(int[] nums) {
int or = 0;
for (int v : nums) {
or |= v;
}
return or << (nums.length - 1);
}
public int subsetXORSumBacktrack(int[] nums) {
int[] res = {0};
dfs(nums, 0, 0, res);
return res[0];
}
private void dfs(int[] nums, int idx, int cur, int[] res) {
if (idx == nums.length) {
res[0] += cur;
return;
}
dfs(nums, idx + 1, cur, res); // 不选
dfs(nums, idx + 1, cur ^ nums[idx], res); // 选
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] cases = {{1, 3}, {5, 1, 6}, {3, 4, 5, 6, 7, 8}, {2, 2, 2}, {1}, {10, 20, 30}};
for (int[] c : cases) {
int r1 = s.subsetXORSum(c);
int r2 = s.subsetXORSumBacktrack(c);
System.out.println(r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
}
}
复杂度分析
1864. Minimum Number of Swaps to Make the Binary String Alternating
题目描述
(构成交替字符串需要的最小交换次数)
给你一个二进制字符串 s,现需要将其转化为一个交替字符串。请你计算并返回转化所需的最小字符
交换次数,如果无法完成转化,返回 -1。
交替字符串是指:相邻字符之间不存在相等情况的字符串。例如,字符串 "010" 和 "1010" 属于交替
字符串,但 "0100" 不是。
任意两个字符都可以进行交换,不必相邻。
示例 1
- 输入 :
s= "111000" - 输出:1
- 解释 :交换位置 1 和 4:"11 100 0" -> "10 1010"。
示例 2
- 输入 :
s= "010" - 输出:0
示例 3
- 输入 :
s= "1110" - 输出 :-1
提示: - 1 <= s.length <= 1000-
s[i]的值为 '0' 或 '1'
解题思路
交替串只可能有两种形态:以 '0' 开头或以 '1' 开头。长度为 n 的交替串中 '1' 的数量是固定的:
- n 为偶数:两种形态都恰好含 n/2 个 '1';
- n 为奇数:以 '1' 开头的形态含 (n+1)/2 个 '1',以 '0' 开头的形态含 (n-1)/2 个 '1'。
因此先统计 s 中 '1' 的个数 ones: - 若 |ones - (n - ones)| > 1(即 '0' 与 '1' 数量差超过 1),任何交替串都凑不出,返回 -1;
- 否则对每一种可行的形态,统计"该位置应为 '0' 却为 '1'"或"应为 '1' 却为 '0'"的错位数
mismatch。任意交换可以把两个错位同时归位,因此该形态所需最少交换 = mismatch / 2。
两种形态取最小值。
解法一:错位计数贪心(主提交版本)
O(n) 一次遍历统计两种形态的错位量,再按 ones 判断可行形态取最小。
解法二:构造目标串对比,对照
显式构造可行的目标交替串,逐位对比统计错位,再除以 2;
思路等价,便于理解"交换 = 两个错位归位"的推导。
代码实现
java
public class Solution {
public int minSwaps(String s) {
int n = s.length();
int ones = 0;
for (char c : s.toCharArray()) {
if (c == '1') ones++;
}
if (Math.abs(n - 2 * ones) > 1) {
return -1; // '0' 与 '1' 数量差超过 1,无法构成任何交替串
}
// mis0:以 '0' 开头形态的错位数(偶数位应为 '0'、奇数位应为 '1')
// mis1:以 '1' 开头形态的错位数(偶数位应为 '1'、奇数位应为 '0')
int mis0 = 0, mis1 = 0;
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
if (i % 2 == 0) {
if (c != '0') mis0++;
if (c != '1') mis1++;
} else {
if (c != '1') mis0++;
if (c != '0') mis1++;
}
}
if (n % 2 == 0) {
// 偶数长:两种形态都要求恰好 n/2 个 '1'(已通过 |n-2*ones|<=1 且偶数知 ones==n/2)
return Math.min(mis0 / 2, mis1 / 2);
}
// 奇数长:'1' 更多只能以 '1' 开头;'0' 更多只能以 '0' 开头
if (ones > n - ones) {
return mis1 / 2;
}
return mis0 / 2;
}
public int minSwapsBuild(String s) {
int n = s.length();
int ones = 0;
for (char c : s.toCharArray()) {
if (c == '1') ones++;
}
int best = Integer.MAX_VALUE;
// 推断可行的开头字符
char[] starts;
if (Math.abs(n - 2 * ones) > 1) {
return -1;
} else if (n % 2 == 0) {
starts = new char[]{'0', '1'};
} else {
starts = new char[]{ones > n - ones ? '1' : '0'};
}
for (char st : starts) {
int mismatch = 0;
for (int i = 0; i < n; i++) {
char expect = ((i % 2 == 0) ? st : (char) ('1' - (st - '0')));
if (s.charAt(i) != expect) mismatch++;
}
best = Math.min(best, mismatch / 2);
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
String[] cases = {"111000", "010", "1110", "00", "11", "1001", "1010", "110011", "01", "10", "1", "0"};
boolean ok = true;
for (String c : cases) {
int r1 = s.minSwaps(c);
int r2 = s.minSwapsBuild(c);
boolean same = r1 == r2;
ok &= same;
System.out.println(c + " -> " + r1 + " | " + r2 + (same ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1865. Finding Pairs With a Certain Sum(找出和为指定值的下标对)
题目描述
给你两个整数数组 nums1 和 nums2,请你实现一个支持下述两类查询的数据结构:
- 累加:将一个正整数加到
nums2中指定下标对应元素上。 - 计数:统计满足
nums1[i]+nums2[j]等于指定值的下标对 (i,j) 数目。
实现FindSumPairs类: - FindSumPairs(
int[]nums1,int[]nums2) 使用整数数组nums1和nums2初始化对象。 voidadd(intindex,intval) 将val加到nums2[index]上,即执行nums2[index]+=val。intcount(inttot) 返回满足nums1[i]+nums2[j]==tot的下标对 (i,j) 数目。
示例
- 输入 :
"FindSumPairs","`count`","add","`count`","`count`","add","add","`count`"
\[\[1,1,2,2,2,3\],\[1,4,5,2,5,4\]\],\[7\],\[3,2\],\[8\],\[4\],\[0,1\],\[1,1\],\[7\]
- 输出 :`null`,8,`null`,2,1,`null`,`null`,11
提示: - 1 <= nums1.length <= 1000- 1 <= nums2.length <= 10^5- 1 <= nums1[i], nums2[i] <= 10^9- 0 <= index < nums2.length- 1 <= val <= 10^5- 1 <= tot <= 10^9-
- 最多调用
add和count函数各 1000 次
- 最多调用
解题思路
解法一:对 nums2 建频次表,主提交版本
用 HashMap<Integer, Integer> 统计 nums2 中每个
值的出现次数;count(tot) 时遍历 nums1(最多 1000 个元素),对每个 nums1i 查表取
tot - nums1i 的频次累加;add 时更新 nums2index 并在频次表上减一加一。add 与 count 均
O(n1)(n1 <= 1000,调用次数 1000,总复杂度 10^6 以内)。
解法二:对 nums1 建频次表,对照
原理对称------把 nums1 的频次放入 Map,count 时遍历
nums2 的"不同值"(Map 键集)计算;由于 nums2 可能很长但不同值少时较快;实现对称,
便于对照理解该结构设计的两种视角。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public static class FindSumPairs {
private final int[] nums1;
private final int[] nums2;
private final Map<Integer, Integer> freq = new HashMap<>();
public FindSumPairs(int[] nums1, int[] nums2) {
this.nums1 = nums1;
this.nums2 = nums2;
for (int v : nums2) {
freq.put(v, freq.getOrDefault(v, 0) + 1);
}
}
public void add(int index, int val) {
int old = nums2[index];
nums2[index] += val;
int cnt = freq.get(old);
if (cnt == 1) {
freq.remove(old);
} else {
freq.put(old, cnt - 1);
}
int nv = nums2[index];
freq.put(nv, freq.getOrDefault(nv, 0) + 1);
}
public int count(int tot) {
long ans = 0;
for (int v : nums1) {
Integer c = freq.get(tot - v);
if (c != null) {
ans += c;
}
}
return (int) ans;
}
}
public static class FindSumPairsRev {
private final int[] nums1;
private final int[] nums2;
private final Map<Integer, Integer> f1 = new HashMap<>();
public FindSumPairsRev(int[] nums1, int[] nums2) {
this.nums1 = nums1;
this.nums2 = nums2;
for (int v : nums1) {
f1.put(v, f1.getOrDefault(v, 0) + 1);
}
}
public void add(int index, int val) {
nums2[index] += val;
}
public int count(int tot) {
long ans = 0;
for (Map.Entry<Integer, Integer> e : f1.entrySet()) {
int need = tot - e.getKey();
long c2 = 0;
for (int v : nums2) {
if (v == need) c2++;
}
ans += c2 * e.getValue();
}
return (int) ans;
}
}
public static void main(String[] args) {
FindSumPairs f = new FindSumPairs(new int[]{1, 1, 2, 2, 2, 3}, new int[]{1, 4, 5, 2, 5, 4});
System.out.println(f.count(7)); // 8
f.add(3, 2);
System.out.println(f.count(8)); // 2
System.out.println(f.count(4)); // 1
f.add(0, 1);
f.add(1, 1);
System.out.println(f.count(7)); // 11
FindSumPairsRev r = new FindSumPairsRev(new int[]{1, 1, 2, 2, 2, 3}, new int[]{1, 4, 5, 2, 5, 4});
System.out.println(r.count(7)); // 8
r.add(3, 2);
System.out.println(r.count(8)); // 2
System.out.println(r.count(4)); // 1
r.add(0, 1);
r.add(1, 1);
System.out.println(r.count(7)); // 11
}
}
复杂度分析
1866. Number of Ways to Rearrange Sticks With K Sticks Visible
题目描述
(恰有 K 根木棍可以看到的排列数目)
有 n 根长度互不相同的木棍,长度为从 1 到 n 的整数。请你将这些木棍排成一排,并满足从左侧
可以看到恰好 k 根木棍。从左侧可以看到木棍的前提是这个木棍的左侧不存在比它更长的木棍。
例如,如果木棍排列为 1,3,2,5,4,那么从左侧可以看到的就是长度分别为 1、3、5 的木棍。
给你 n 和 k,返回符合题目要求的排列数目。由于答案可能很大,请返回对 10^9 + 7 取余的结果。
示例 1
- 输入 :
n= 3,k= 2 - 输出:3
- 解释:1,3,2、2,3,1 和 2,1,3 是仅有的能满足恰好 2 根木棍可以看到的排列。
示例 2
- 输入 :
n= 5,k= 5 - 输出:1
示例 3
- 输入 :
n= 20,k= 11 - 输出 :647427950
提示: - 1 <= n <= 1000- 1 <= k <= n
解题思路
解法一:第一类斯特林数 DP,主提交版本
考虑"从 n 根木棍中取最短的那根"放置策略。把长度
1 的木棍放到哪里并不改变比它长的木棍之间的可见性关系:因此从大到小逐个插入木棍 i,若
插到当前排列的最左端,它会成为"新的可见木棍"(可见数 +1);插到其他 i-1 个空隙位置中的
任意一个,它被左侧更长的木棍挡住(可见数不变)。于是 dpij = dpi-1j-1 + (i-1)dpi-1j,
即第一类斯特林数 s(n,k)。O(n k) 时间、O(k) 空间(滚动数组)。
解法二:DFS 记忆化,对照
定义 f(n, k) 为恰好看到 k 根的排列数,递推同上式的记忆化版本;
便于递推式的推导理解,因 n <= 1000 用 long 取模实现。
代码实现
java
public class Solution {
private static final int MOD = 1000000007;
public int rearrangeSticks(int n, int k) {
long[] dp = new long[k + 1];
dp[0] = 1; // 0 根木棍时看到 0 根:1 种
for (int i = 1; i <= n; i++) {
// 从大到小更新,避免覆盖本轮结果
for (int j = Math.min(i, k); j >= 1; j--) {
dp[j] = (dp[j - 1] + dp[j] * (i - 1)) % MOD;
}
dp[0] = 0;
}
return (int) dp[k];
}
public int rearrangeSticksCombo(int n, int k) {
// 另一种视角:先选出 k 根作为"从左到右可见序列"(升序固定),再插入其余木棍
// 等价于第一类斯特林数,因此直接调用同构递推,但使用二维表便于阅读
long[][] s = new long[n + 1][k + 1];
s[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= Math.min(i, k); j++) {
s[i][j] = (s[i - 1][j - 1] + s[i - 1][j] * (i - 1)) % MOD;
}
}
return (int) s[n][k];
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] cases = {{3, 2}, {5, 5}, {20, 11}, {1, 1}, {5, 1}, {6, 3}, {1000, 500}};
boolean ok = true;
for (int[] c : cases) {
int r1 = s.rearrangeSticks(c[0], c[1]);
int r2 = s.rearrangeSticksCombo(c[0], c[1]);
boolean same = r1 == r2;
ok &= same;
System.out.println("n=" + c[0] + " k=" + c[1] + " -> " + r1 + " | " + r2 + (same ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1867. Orders With Maximum Quantity Above Average(最大数量高于平均水平的订单)
题目类型:数据库(SQL)
题目描述
表:OrdersDetails
+-------------+------+
| Column Name | Type |
+-------------+------+
| order_id | int |
| product_id | int |
| quantity | int |
+-------------+------+
(order_id, product_id) 是该表的主键。一个订单由多行表示,每行对应订单中的一个产品,包含产品 product_id 的订购数量 quantity。
不平衡订单 是指这样一个订单:其最大 商品数量严格大于 所有订单(包括该订单自身)的平均 商品数量。一个订单的平均商品数量 =(该订单所有商品的总数量)/(该订单中不同商品的数量)。最大商品数量是该订单中任意单个商品 quantity 中的最大值。找出所有不平衡订单的 order_id。
示例 1:
输入:
OrdersDetails 表:
+----------+------------+----------+
| order_id | product_id | quantity |
+----------+------------+----------+
| 1 | 1 | 12 |
| 1 | 2 | 10 |
| 1 | 3 | 15 |
| 2 | 1 | 8 |
| 2 | 4 | 4 |
| 2 | 5 | 6 |
| 3 | 3 | 5 |
| 3 | 4 | 18 |
| 4 | 5 | 2 |
| 4 | 6 | 8 |
| 5 | 7 | 9 |
| 5 | 8 | 9 |
| 3 | 9 | 20 |
| 2 | 9 | 4 |
+----------+------------+----------+
输出:
+----------+
| order_id |
+----------+
| 1 |
| 3 |
+----------+
解释:每个订单的平均商品数量分别为 12.33、5.5、14.33、5、9;「所有订单的平均商品数量」取这些平均值的最大者 14.33,订单 1 的 max=15、订单 3 的 max=20 均严格大于它,故不平衡。
解题思路
按 order_id 分组求出各订单的 MAX(quantity) 与 AVG(quantity);
定义"所有订单的平均商品数量"为各订单平均值的最大者 MAX(avg_q)(最严格口径),
保留 max_q > 该最大值 的订单即为不平衡订单。
代码实现
sql
WITH t AS (
SELECT order_id,
MAX(quantity) AS max_q,
AVG(quantity) AS avg_q
FROM OrdersDetails
GROUP BY order_id
)
SELECT order_id
FROM t
WHERE max_q > (SELECT MAX(avg_q) FROM t);
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度;考察重点为 JOIN / 窗口函数 / 自连接 / 聚合过滤等 SQL 语法与查询逻辑的正确性,执行效率由数据库优化器决定。
1868. Product of Two Run-Length Encoded Arrays(两个行程编码数组的积)
题目描述
行程编码(Run-length encoding)是一种压缩算法,能让一个含有许多段连续重复数字的整数类型数组
nums 以一个(通常更小的)二维数组 encoded 表示。每个 encoded[i] = `vali`, `freqi` 表示
nums 中第 i 段重复数字,其中 vali 是该段重复数字,重复了 freqi 次。
例如,nums = 1,1,1,2,2,2,2,2 可表示为 encoded = \[1,3,2,5]。
两个行程编码数组 encoded1 和 encoded2 的积可以按下列步骤计算:
- 将
encoded1和encoded2分别扩展成完整数组nums1和nums2。 - 创建一个新的数组
prodNums,长度为nums1.length并设prodNums[i]=nums1[i]*nums2[i]。 - 将
prodNums压缩成一个行程编码数组并返回之。
给定两个行程编码数组encoded1和encoded2,分别表示完整数组nums1和nums2。nums1和nums2
的长度相同。
返回encoded1和encoded2的乘积。注意:行程编码数组需压缩成可能的最小长度。
示例 1
- 输入 :
encoded1= \[1,3,2,3],encoded2= \[6,3,3,3] - 输出:\[6,6]
- 解释 :
prodNums= 6,6,6,6,6,6,压缩成 \[6,6]。
示例 2
- 输入 :
encoded1= \[1,3,2,1,3,2],encoded2= \[2,3,3,3] - 输出 :\[2,3,6,1,9,2]
提示: - 1 <= encoded1.length, encoded2.length <= 10^5-
- encoded1i.length == 2,encoded2j.length == 2
-
- 1 <=
vali,freqi<= 10^4(近似,原题val<= 10^4)
- 1 <=
-
encoded1和encoded2表示的完整数组长度相同。
解题思路
解法一:双指针按段逐段合并,主提交版本
i、j 分别指向两个数组当前段,取两段的较短长度
作为本次合并长度 take = min(f1, f2),乘积段为 val1\*val2, take,与结果末尾段相同值则合并;
两段各自扣减 take 后推进指针。一次性遍历,O(n1 + n2)。
解法二:展开模拟,对照
先展开(受限于总长,用于小数据直观验证);再把逐位乘积压成 RLE。
总长度可能较大,仅作小规模测试参照。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public List<List<Integer>> findRLEArray(int[][] encoded1, int[][] encoded2) {
int i = 0, j = 0;
int n1 = encoded1.length, n2 = encoded2.length;
List<List<Integer>> res = new ArrayList<>();
while (i < n1 && j < n2) {
int take = Math.min(encoded1[i][1], encoded2[j][1]);
int prod = encoded1[i][0] * encoded2[j][0];
// 与结果末尾段相同值合并(行程压缩最小长度)
if (!res.isEmpty() && res.get(res.size() - 1).get(0) == prod) {
List<Integer> last = res.get(res.size() - 1);
last.set(1, last.get(1) + take);
} else {
res.add(Arrays.asList(prod, take));
}
encoded1[i][1] -= take;
encoded2[j][1] -= take;
if (encoded1[i][1] == 0) i++;
if (encoded2[j][1] == 0) j++;
}
return res;
}
public List<List<Integer>> findRLEArrayExpand(int[][] encoded1, int[][] encoded2) {
List<Integer> a = expand(encoded1);
List<Integer> b = expand(encoded2);
List<List<Integer>> res = new ArrayList<>();
for (int t = 0; t < a.size(); t++) {
int prod = a.get(t) * b.get(t);
if (!res.isEmpty() && res.get(res.size() - 1).get(0) == prod) {
List<Integer> last = res.get(res.size() - 1);
last.set(1, last.get(1) + 1);
} else {
res.add(Arrays.asList(prod, 1));
}
}
return res;
}
private List<Integer> expand(int[][] enc) {
List<Integer> list = new ArrayList<>();
for (int[] seg : enc) {
for (int t = 0; t < seg[1]; t++) {
list.add(seg[0]);
}
}
return list;
}
private static String str(List<List<Integer>> res) {
StringBuilder sb = new StringBuilder("[");
for (List<Integer> seg : res) {
sb.append(Arrays.toString(new int[]{seg.get(0), seg.get(1)})).append(',');
}
if (res.size() > 0) sb.deleteCharAt(sb.length() - 1);
sb.append(']');
return sb.toString();
}
public static void main(String[] args) {
Solution s = new Solution();
int[][][] e1Cases = {
{{1, 3}, {2, 3}},
{{1, 3}, {2, 1}, {3, 2}},
{{1, 2}, {5, 2}},
{{4, 5}}
};
int[][][] e2Cases = {
{{6, 3}, {3, 3}},
{{2, 3}, {3, 3}},
{{3, 2}, {2, 2}},
{{2, 5}}
};
for (int t = 0; t < e1Cases.length; t++) {
List<List<Integer>> r1 = s.findRLEArray(copy(e1Cases[t]), copy(e2Cases[t]));
List<List<Integer>> r2 = s.findRLEArrayExpand(copy(e1Cases[t]), copy(e2Cases[t]));
System.out.println(str(r1) + " | " + str(r2) + (str(r1).equals(str(r2)) ? " OK" : " FAIL"));
}
}
private static int[][] copy(int[][] a) {
int[][] b = new int[a.length][2];
for (int i = 0; i < a.length; i++) {
b[i][0] = a[i][0];
b[i][1] = a[i][1];
}
return b;
}
}
复杂度分析
1869. Longer Contiguous Segments of Ones than Zeros(哪种连续子字符串更长)
题目描述
给你一个二进制字符串 s。如果字符串中由 1 组成的最长连续子字符串严格长于由 0 组成的
最长连续子字符串,返回 true;否则返回 false。
例如,s = "110100010" 中由 1 组成的最长连续子字符串的长度是 2("11"),由 0 组成的
最长连续子字符串的长度是 3("000"),所以返回 false。
示例 1
- 输入 :
s= "1101" - 输出 :
true - 解释:由 1 组成的最长连续子字符串的长度是 2,由 0 组成的最长连续子字符串的长度是 1。
示例 2
- 输入 :
s= "111000" - 输出 :
false
示例 3
- 输入 :
s= "110100010" - 输出 :
false
提示: - 1 <= s.length <= 100-
s[i]不是 '0' 就是 '1'
解题思路
解法一:单遍扫描记录最大连续长度,主提交版本
遍历字符串,维护当前连续段字符及长度,
遇到不同字符时结算前一段并更新对应字符的最大值;最后一截在循环外结算。
O(n) 时间、O(1) 空间。
解法二:分组统计,对照
对 s 按连续相同字符分组(手工切组),每组统计长度,取 '1' 组与
'0' 组的各自最大长度再比较。逻辑直白,便于验证解法一。
代码实现
java
public class Solution {
public boolean checkZeroOnes(String s) {
int max1 = 0, max0 = 0;
int run = 0;
char cur = ' ';
for (int i = 0; i <= s.length(); i++) {
if (i < s.length() && s.charAt(i) == cur) {
run++; // 延续当前段
} else {
if (i > 0) { // 结算上一段
if (cur == '1') {
max1 = Math.max(max1, run);
} else {
max0 = Math.max(max0, run);
}
}
if (i < s.length()) {
cur = s.charAt(i);
run = 1;
}
}
}
return max1 > max0;
}
public boolean checkZeroOnesGroup(String s) {
int max1 = 0, max0 = 0;
int i = 0, n = s.length();
while (i < n) {
int j = i;
while (j < n && s.charAt(j) == s.charAt(i)) {
j++;
}
int len = j - i;
if (s.charAt(i) == '1') {
max1 = Math.max(max1, len);
} else {
max0 = Math.max(max0, len);
}
i = j;
}
return max1 > max0;
}
public static void main(String[] args) {
Solution s = new Solution();
String[] cases = {"1101", "111000", "110100010", "1", "0", "111", "000", "101", "010"};
boolean ok = true;
for (String c : cases) {
boolean r1 = s.checkZeroOnes(c);
boolean r2 = s.checkZeroOnesGroup(c);
ok &= (r1 == r2);
System.out.println(c + " -> " + r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1870. Minimum Speed to Arrive on Time(准时到达的列车最小时速)
题目描述
给你一个浮点数 hour,表示你到达办公室可用的总通勤时间。要到达办公室,你必须按给定次序乘坐
n 趟列车。另给你一个长度为 n 的整数数组 dist,其中 dist[i] 表示第 i 趟列车的行驶距离(千米)。
每趟列车均只能在整点发车,所以你可能需要在两趟列车之间等待一段时间。例如,第 1 趟列车需要
1.5 小时,那你必须再等待 0.5 小时,搭乘在第 2 小时发车的第 2 趟列车。
返回能满足你在时限前到达办公室所要求全部列车的最小正整数时速(千米每小时);如果无法准时到达,
返回 -1。生成的测试用例保证答案不超过 10^7,且 hour 的小数点后最多存在两位数字。
示例 1
- 输入 :
dist= 1,3,2,hour= 6 - 输出:1
示例 2
- 输入 :
dist= 1,3,2,hour= 2.7 - 输出:3
示例 3
- 输入 :
dist= 1,3,2,hour= 1.9 - 输出 :-1
提示: - n == dist.length- 1 <= n <= 10^5- 1 <= dist[i] <= 10^5- 1 <= hour <= 10^9-
hour小数点后最多存在两位数字
解题思路
解法一:二分答案 + 整数精度判定,主提交版本
对速度 v,除最后一趟外每趟耗时 ceil(disti/v)
小时,最后一趟取实数 distn-1/v。为免浮点误差,把总时间乘以 100:前 n-1 趟贡献
100 * ceil(disti/v),最后一趟贡献 100 * distn-1 / v 向下取整与余数比较------
判定"耗时 * 100 <= hour * 100"转为整数比较。二分下界 1、上界 maxDist(或 10^7)。
O(n log M)。
解法二:逐速度线性尝试,对照
从 1 开始递增尝试每个速度,直到满足或超出上界;实现直观,
只在答案较小或测试规模小时可用,用于验证解法一。
代码实现
java
public class Solution {
public int minSpeedOnTime(int[] dist, double hour) {
int n = dist.length;
long hour100 = Math.round(hour * 100);
int lo = 1, hi = 10_000_000; // 题保证答案不超过 10^7
int ans = -1;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
if (canFinish(dist, n, hour100, mid)) {
ans = mid;
hi = mid - 1;
} else {
lo = mid + 1;
}
}
return ans;
}
private boolean canFinish(int[] dist, int n, long hour100, int v) {
long cost100 = 0;
for (int i = 0; i < n - 1; i++) {
cost100 += 100L * ((dist[i] + v - 1) / v); // 整点发车:向上取整
}
// 最后一趟取实数耗时 dist[n-1]/v:cost100 += 10000 * dist[n-1] / v 并进位取整比较
long lastNum = 100L * dist[n - 1]; // 100 * dist / v,精确到 1/v 小时
cost100 += lastNum / v;
long rem = lastNum % v;
// 需要 cost100 + rem/v <= hour100,等价于 cost100 * v + rem <= hour100 * v
return cost100 * v + rem <= hour100 * v;
}
public int minSpeedOnTimeLinear(int[] dist, double hour) {
int n = dist.length;
long hour100 = Math.round(hour * 100);
for (int v = 1; v <= 10_000_000; v++) {
if (canFinish(dist, n, hour100, v)) {
return v;
}
}
return -1;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] ds = {
{1, 3, 2}, {1, 3, 2}, {1, 3, 2}, {5, 3, 4, 6, 2, 2, 7}, {1}, {1}
};
double[] hs = {6.0, 2.7, 1.9, 10.92, 1.0, 5.0};
boolean ok = true;
for (int t = 0; t < ds.length; t++) {
int r1 = s.minSpeedOnTime(ds[t], hs[t]);
int r2 = s.minSpeedOnTimeLinear(ds[t], hs[t]);
ok &= (r1 == r2);
System.out.println(r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1871. Jump Game VII(跳跃游戏 VII)
题目描述
给你一个下标从 0 开始的二进制字符串 s 和两个整数 minJump 和 maxJump。一开始,你在下标 0
处,且该位置的值一定为 '0'。当同时满足如下条件时,你可以从下标 i 移动到下标 j 处:
i + minJump <= j <= min(i + maxJump, s.length - 1)s[j] == '0'
请你判断是否可以到达s.length- 1 处,如果可以返回true,否则返回false。
示例 1
- 输入 :
s= "011010",minJump= 2,maxJump= 3 - 输出 :
true - 解释:第一步从 0 到 3,第二步从 3 到 5。
示例 2
- 输入 :
s= "01101110",minJump= 2,maxJump= 3 - 输出 :
false
提示: - 2 <= s.length <= 10^5-
s[i]要么是 '0',要么是 '1'
- s[0] == '0'- 1 <= minJump <= maxJump < s.length
解题思路
解法一:滑动窗口可达性 DP,主提交版本
dpi = 下标 i 是否可达。对每个 i,判断区间
i - maxJump, i - minJump 内是否存在可达且为 '0' 的位置。用窗口计数器维护该区间内
dp 为 true 的个数:i 每右移一位,加入 dpi-minJump、弹出 dpi-maxJump-1。
dpi = (si'0') && (窗口计数 > 0)。O(n)。
解法二:差分前缀和辅助,对照
用 diff 数组记录"每个可达点向右可达范围"的加一区间,
再计算前缀和得到每个下标被覆盖(可达)的次数;dpi = cnti > 0 && si'0',
每算出一个可达点,就在 i+minJump, i+maxJump 区间加一。O(n)。
代码实现
java
public class Solution {
public boolean canReach(String s, int minJump, int maxJump) {
int n = s.length();
boolean[] dp = new boolean[n];
dp[0] = true;
int window = 0; // 区间 [i - maxJump, i - minJump] 中 dp=true 的个数
for (int i = 1; i < n; i++) {
// i 每步右移:进入窗口的新下界元素 & 离开窗口的旧元素
if (i - minJump >= 0) {
window += dp[i - minJump] ? 1 : 0;
}
if (i - maxJump - 1 >= 0) {
window -= dp[i - maxJump - 1] ? 1 : 0;
}
if (s.charAt(i) == '0' && window > 0) {
dp[i] = true;
}
}
return dp[n - 1];
}
public boolean canReachDiff(String s, int minJump, int maxJump) {
int n = s.length();
int[] diff = new int[n + 1];
int covered = 0; // 当前下标被可达点覆盖的次数
boolean[] dp = new boolean[n];
dp[0] = true;
for (int i = 0; i < n; i++) {
covered += diff[i];
if (i > 0) {
// i 可达与否取决于此前覆盖
dp[i] = (s.charAt(i) == '0') && covered > 0;
}
if (dp[i]) {
if (i + minJump < n) diff[i + minJump]++;
if (i + maxJump + 1 < n) diff[i + maxJump + 1]--;
}
}
return dp[n - 1];
}
public static void main(String[] args) {
Solution s = new Solution();
String[][] cases = {
{"011010", "2", "3"},
{"01101110", "2", "3"},
{"00", "1", "1"},
{"00000", "1", "2"},
{"01110", "2", "3"},
{"010", "1", "1"},
{"00110", "2", "2"}
};
boolean ok = true;
for (String[] c : cases) {
boolean r1 = s.canReach(c[0], Integer.parseInt(c[1]), Integer.parseInt(c[2]));
boolean r2 = s.canReachDiff(c[0], Integer.parseInt(c[1]), Integer.parseInt(c[2]));
ok &= (r1 == r2);
System.out.println(c[0] + " -> " + r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1872. Stone Game VIII(石子游戏 VIII)
题目描述
Alice 和 Bob 玩一个游戏,两人轮流操作,Alice 先手。总共有 n 个石子排成一行。轮到某个玩家的
回合时,如果石子的数目大于 1,他将执行以下操作:
- 选择一个整数
x> 1,并且移除最左边的x个石子。 - 将移除的石子价值之和累加到该玩家的分数中。
- 将一个新的石子放在最左边,且新石子的值为被移除石子值之和。
当只剩下一个石子时,游戏结束。
Alice和Bob的分数之差为 (Alice的分数 -Bob的分数)。Alice的目标是最大化分数差,Bob的
目标是最小化分数差。
给你一个长度为n的整数数组stones,其中stones[i]是从左边起第i个石子的价值。请你返回
在双方都采用最优策略的情况下,Alice和Bob的分数之差。
示例 1
- 输入 :
stones= -1,2,-3,4,-5 - 输出:5
示例 2
- 输入 :
stones= 7,-6,5,10,5,-2,-6 - 输出:13
示例 3
- 输入 :
stones= -10,-12 - 输出 :-22
提示: - n == stones.length- 2 <= n <= 10^5- -10^4 <= stones[i] <= 10^4
解题思路
设 Pt = 前 t 个石子之和(P0=0,Pn=总和)。
定义状态 k(0 <= k <= n):棋面为 P\[k, stonesk, ..., stonesn-1],即最左是一个
值为 Pk 的"合成石子"。若 k = n,棋面只剩一个石子 P\[n],游戏结束。
从状态 k 选择 x >= 2(移除最左 x 个)后,得分 = Pt+x-1,棋面转变为状态 t+x-1。
记 gk = 当前行动方从状态 k 开始到终局的最优净胜(本家累计分 - 对方累计分),则
gk = max_{j = k+1...n} ( Pj - gj ),且 gn = 0(游戏结束)。
初始棋面 stones\[0...] 的第一步相当于从状态 0 之外直接选 j in 2, n,因此
答案 ans = max_{j = 2...n} (Pj - gj) = g1。
递推可由后缀最大值线性完成:gn-1 = Pn,对 k = n-2...1:
gk = max( gk+1, Pk+1 - gk+1 )。
解法一:前缀和 + 后缀最大 DP(主提交版本)
O(n) 时间、O(n) 空间,long 防溢出。
解法二:记忆化搜索,对照
直接枚举 j 递归计算 g(k),O(n^2),供小规模验证。
代码实现
java
import java.util.Arrays;
public class Solution {
public int stoneGameVIII(int[] stones) {
int n = stones.length;
long[] p = new long[n + 1];
for (int i = 0; i < n; i++) {
p[i + 1] = p[i] + stones[i];
}
long[] g = new long[n + 1];
g[n] = 0; // 状态 n:只剩一个石子,游戏结束
g[n - 1] = p[n] - g[n]; // n-1 状态唯一可移到 j = n
for (int k = n - 2; k >= 1; k--) {
g[k] = Math.max(g[k + 1], p[k + 1] - g[k + 1]);
}
return (int) g[1]; // 初始可移 x=2..n,对应 j=2..n,即 g[1]
}
public int stoneGameVIII_Memo(int[] stones) {
int n = stones.length;
long[] p = new long[n + 1];
for (int i = 0; i < n; i++) {
p[i + 1] = p[i] + stones[i];
}
memo = new long[n + 1];
Arrays.fill(memo, Long.MIN_VALUE / 4);
memo[n] = 0;
return (int) dfs(1, p, n); // 初始答案与解法一同:g[1]
}
private long[] memo;
private long dfs(int k, long[] p, int n) {
if (memo[k] != Long.MIN_VALUE / 4) {
return memo[k];
}
long res = Long.MIN_VALUE / 4;
for (int j = k + 1; j <= n; j++) { // x >= 2,即 j >= k+1
res = Math.max(res, p[j] - dfs(j, p, n));
}
memo[k] = res;
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] cases = {
{-1, 2, -3, 4, -5},
{7, -6, 5, 10, 5, -2, -6},
{-10, -12},
{1, 2},
{-5, -3, -1},
{3, 1, 2}
};
boolean ok = true;
for (int[] c : cases) {
int r1 = s.stoneGameVIII(c);
int r2 = s.stoneGameVIII_Memo(c);
ok &= (r1 == r2);
System.out.println(Arrays.toString(c) + " -> " + r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1873. Calculate Special Bonus(计算特殊奖金)
题目类型:数据库(SQL)
题目描述
表:Employees
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| employee_id | int |
| name | varchar |
| salary | int |
+-------------+---------+
employee_id 是这个表的主键。
编写解决方案,计算每个雇员的奖金。如果一个雇员的 id 是奇数 并且他的名字不是以 'M' 开头 ,那么他的奖金是他工资的 100%,否则奖金为 0。返回的结果按照 employee_id 排序。
示例 1:
输入:
Employees 表:
+-------------+---------+--------+
| employee_id | name | salary |
+-------------+---------+--------+
| 2 | Meir | 3000 |
| 3 | Michael | 3800 |
| 7 | Addilyn | 7400 |
| 8 | Juan | 6100 |
| 9 | Kannon | 7700 |
+-------------+---------+--------+
输出:
+-------------+-------+
| employee_id | bonus |
+-------------+-------+
| 2 | 0 |
| 3 | 0 |
| 7 | 7400 |
| 8 | 0 |
| 9 | 7700 |
+-------------+-------+
解题思路
用 IF(条件, salary, 0) 实现条件分支:id 为奇数(employee_id % 2 = 1)且名字不以 'M'
开头(name NOT LIKE 'M%')时奖金为原工资,否则为 0;最后按 employee_id 排序。
代码实现
sql
SELECT employee_id,
IF(employee_id % 2 = 1 AND name NOT LIKE 'M%', salary, 0) AS bonus
FROM Employees
ORDER BY employee_id;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度;考察重点为 JOIN / 窗口函数 / 自连接 / 聚合过滤等 SQL 语法与查询逻辑的正确性,执行效率由数据库优化器决定。
1874. Minimize Product Sum of Two Arrays(两个数组的最小乘积和)
题目描述
给定两个长度相等的数组 a 和 b,它们的乘积和为数组中所有的 a[i] * b[i] 之和,其中
0 <= i < a.length。你可以对 a 或 b 中的元素做任意次重新排列。请你返回在重新排列后
能够得到的最小乘积和。
(补充:原始英文题面为 "Minimize Product Sum of Two Arrays",要求重排数组后最小化
sum(a[i] * b[i])。)
示例 1
- 输入 :
nums1= 5,3,4,2,nums2= 4,2,2,5 - 输出:40
- 解释 :重排
nums1为 5,3,2,4,nums2为 2,4,5,2(本质:最大配最小)得到
5*2 + 3*4 + 2*5 + 4*2 = 10 + 12 + 10 + 8 = 40
示例 2
- 输入 :
nums1= 2,1,4,5,7,nums2= 3,2,4,8,6 - 输出 :65
提示: - n == nums1.length == nums2.length- 1 <= n <= 10^5- 0 <= nums1[i], nums2[i] <= 100
解题思路
经典结论(排序不等式):两个数组一个升序、一个降序,对应相乘求和得到最小乘积和;
同序(都升序)对应相乘得到最大乘积和。
解法一:排序 + 一升一降配对,主提交版本
nums1 升序、nums2 降序(或相反),累加
nums1i * nums2i。O(n log n)。
解法二:计数排序固定值域,对照
由于元素值域仅 0...100,可计数后按"最小配最大"双向
指针配对累加,O(n + 101),不依赖比较排序,作为思路对照。
代码实现
java
import java.util.Arrays;
public class Solution {
public int minProductSum(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int n = nums1.length;
long ans = 0;
for (int i = 0; i < n; i++) {
ans += (long) nums1[i] * nums2[n - 1 - i]; // nums2 逆序使用
}
return (int) ans;
}
public int minProductSumCount(int[] nums1, int[] nums2) {
int[] cnt1 = new int[101];
int[] cnt2 = new int[101];
for (int v : nums1) cnt1[v]++;
for (int v : nums2) cnt2[v]++;
int i = 0, j = 100; // cnt1 从小往大取、cnt2 从大往小取
long ans = 0;
while (i <= 100 && j >= 0) {
while (i <= 100 && cnt1[i] == 0) i++;
while (j >= 0 && cnt2[j] == 0) j--;
if (i > 100 || j < 0) break;
int take = Math.min(cnt1[i], cnt2[j]);
ans += (long) i * j * take;
cnt1[i] -= take;
cnt2[j] -= take;
}
return (int) ans;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] a = {{5, 3, 4, 2}, {2, 1, 4, 5, 7}, {1, 1, 1}, {2, 2}, {10, 20, 30}};
int[][] b = {{4, 2, 2, 5}, {3, 2, 4, 8, 6}, {1, 1, 1}, {3, 1}, {30, 20, 10}};
boolean ok = true;
for (int t = 0; t < a.length; t++) {
int r1 = s.minProductSum(a[t], b[t]);
int r2 = s.minProductSumCount(a[t], b[t]);
ok &= (r1 == r2);
System.out.println(r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1875. Group Employees of the Same Salary(将工资相同的雇员分组)
题目类型:数据库(SQL)
题目描述
表:Employees
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| employee_id | int |
| name | varchar |
| salary | int |
+-------------+---------+
employee_id 是这张表具有唯一值的列。
这家公司想要将工资相同 的雇员划分到同一个组中:每个组需要由至少两个 雇员组成;同一组中所有雇员工资相同;工资相同的所有雇员必须被分到同一个组中;如果某位雇员的工资独一无二,那么它不 被分配到任何组中。组 ID 的设定基于这个组的工资相对于其他组的工资排名 ,即工资最低 的组满足 team_id = 1(排名时不需要考虑没有组的雇员的工资)。
编写解决方案来获取每一个被分配到组中的雇员的 team_id。返回的结果表按照 team_id 升序排列,若相同则按 employee_id 升序排列。
示例 1:
输入:
Employees 表:
+-------------+---------+--------+
| employee_id | name | salary |
+-------------+---------+--------+
| 2 | Meir | 3000 |
| 3 | Michael | 3000 |
| 7 | Addilyn | 7400 |
| 8 | Juan | 6100 |
| 9 | Kannon | 7400 |
+-------------+---------+--------+
输出:
+-------------+---------+--------+---------+
| employee_id | name | salary | team_id |
+-------------+---------+--------+---------+
| 2 | Meir | 3000 | 1 |
| 3 | Michael | 3000 | 1 |
| 7 | Addilyn | 7400 | 2 |
| 9 | Kannon | 7400 | 2 |
+-------------+---------+--------+---------+
解释:3000 和 7400 各有两名雇员,分别组成组 1、2;6100 独一无二,不分组。
解题思路
用 COUNT(*) OVER (PARTITION BY salary) 统计每个工资档次的人数,筛掉只出现 1 次的工资;
再用 RANK() OVER (ORDER BY salary) 按工资升序给组编号(工资最低者为 1),按要求排序输出。
代码实现
sql
WITH cnt AS (
SELECT *,
COUNT(*) OVER (PARTITION BY salary) AS c
FROM Employees
)
SELECT employee_id, name, salary,
RANK() OVER (ORDER BY salary) AS team_id
FROM cnt
WHERE c >= 2
ORDER BY team_id, employee_id;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度;考察重点为 JOIN / 窗口函数 / 自连接 / 聚合过滤等 SQL 语法与查询逻辑的正确性,执行效率由数据库优化器决定。
1876. Substrings of Size Three with Distinct Characters
题目描述
(长度为三且各字符不同的子字符串)
如果一个字符串不含有任何重复字符,我们称这个字符串为好字符串。
给你一个字符串 s,请你返回 s 中长度为 3 的好子字符串的数量。
注意,如果相同的好子字符串出现多次,每一次都应该被记入答案之中。
子字符串是一个字符串中连续的字符序列。
示例 1
- 输入 :
s= "xyzzaz" - 输出:1
- 解释 :总共有 4 个长度为 3 的子字符串:"
xyz"、"yzz"、"zza" 和 "zaz",唯一的好子字符串是 "xyz"。
示例 2
- 输入 :
s= "aababcabc" - 输出:4
- 解释 :好子字符串包括 "
abc"、"bca"、"cab" 和 "abc"。
提示: - 1 <= s.length <= 100-
s只包含小写英文字母。
解题思路
解法一:窗口字符计数,主提交版本
维护长度 3 窗口内各字符计数与"重复种类数",
每移动一次窗口更新计数并判断当前窗口三个字符是否互不相同(重复种类数为 0 即全不同)。
O(n) 时间、O(1) 空间(26 大小数组)。
解法二:直接取子串判唯一,对照
对每个起点截取长度 3 的子串,用 Set 判断是否含重复字符。
O(n*3)。实现直观,便于验证解法一。
代码实现
java
import java.util.HashSet;
import java.util.Set;
public class Solution {
public int countGoodSubstrings(String s) {
int n = s.length();
if (n < 3) {
return 0;
}
int[] cnt = new int[26];
int dup = 0; // 当前窗口内出现次数 > 1 的字符种类数
for (int i = 0; i < 3; i++) {
int c = s.charAt(i) - 'a';
cnt[c]++;
if (cnt[c] == 2) dup++;
}
int ans = (dup == 0) ? 1 : 0;
for (int i = 3; i < n; i++) {
int out = s.charAt(i - 3) - 'a';
cnt[out]--;
if (cnt[out] == 1) dup--; // 由 2 回到 1:少了一种重复
int in = s.charAt(i) - 'a';
cnt[in]++;
if (cnt[in] == 2) dup++;
if (dup == 0) ans++;
}
return ans;
}
public int countGoodSubstringsSet(String s) {
int ans = 0;
for (int i = 0; i + 3 <= s.length(); i++) {
Set<Character> set = new HashSet<>();
boolean good = true;
for (int j = i; j < i + 3; j++) {
if (!set.add(s.charAt(j))) {
good = false;
break;
}
}
if (good) ans++;
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
String[] cases = {"xyzzaz", "aababcabc", "abc", "aaa", "ab", "a", "xyz", "ababab"};
boolean ok = true;
for (String c : cases) {
int r1 = s.countGoodSubstrings(c);
int r2 = s.countGoodSubstringsSet(c);
ok &= (r1 == r2);
System.out.println(c + " -> " + r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1877. Minimize Maximum Pair Sum in Array(数组中最大数对和的最小值)
题目描述
一对元素 (a, b) 的数对和等于 a + b。最大数对和是所有数对中最大的数对和。
例如,如果有数对 (1,5)、(2,3) 和 (4,4),那么最大数对和为 max(6, 5, 8) = 8。
给你一个长度为偶数 n 的数组 nums,请你将 nums 中的元素分成 n / 2 个数对,使得:
nums中的每个元素恰好属于一个数对,并且- 最大数对和尽可能小。
请你返回最优配对方案下最大数对和的最小值。
示例 1
- 输入 :
nums= 3,5,2,3 - 输出:7
- 解释 :分成 (3,3) 和 (5,2),最大数对和为
max(6, 7) = 7。
示例 2
- 输入 :
nums= 3,5,4,2,4,6 - 输出 :8
提示: - n == nums.length- 2 <= n <= 10^5-
n是偶数
- 1 <= nums[i] <= 10^5
解题思路
经典贪心结论:排序后把最小值与最大值配对、次小值与次大值配对......可以最小化最大数对和。
(若存在一个配对方案使最大数对和更小,可由交换反证矛盾。)
解法一:排序 + 双向配对,主提交版本
升序排序,i 从 0、j 从 n-1 往中间走,
记录 max(numsi + numsj)。O(n log n)。
解法二:计数排序值域约束,对照
数值范围 1...10^5,可用计数桶 + 双端指针首尾配对;
O(n + U),不依赖比较排序,作为对照实现。
代码实现
java
import java.util.Arrays;
public class Solution {
public int minPairSum(int[] nums) {
Arrays.sort(nums);
int ans = 0, n = nums.length;
for (int i = 0, j = n - 1; i < j; i++, j--) {
ans = Math.max(ans, nums[i] + nums[j]);
}
return ans;
}
public int minPairSumCount(int[] nums) {
int max = 0;
for (int v : nums) {
if (v > max) max = v;
}
int[] cnt = new int[max + 1];
for (int v : nums) cnt[v]++;
int lo = 0, hi = max;
int ans = 0, paired = 0;
while (paired < nums.length / 2) {
// 找到当前最小可用值
while (lo <= max && cnt[lo] == 0) lo++;
// 找到当前最大可用值
while (hi >= 0 && cnt[hi] == 0) hi--;
int take = Math.min(cnt[lo], cnt[hi]);
ans = Math.max(ans, lo + hi);
cnt[lo] -= take;
cnt[hi] -= take;
paired += take;
}
return ans;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] cases = {{3, 5, 2, 3}, {3, 5, 4, 2, 4, 6}, {1, 2, 3, 4}, {1, 1, 1, 1}, {4, 4, 4, 5, 5, 5}};
boolean ok = true;
for (int[] c : cases) {
int r1 = s.minPairSum(c);
int r2 = s.minPairSumCount(c);
ok &= (r1 == r2);
System.out.println(Arrays.toString(c) + " -> " + r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1878. Get Biggest Three Rhombus Sums in a Grid(矩阵中最大的三个菱形和)
题目描述
给你一个 m x n 的整数矩阵 grid。菱形和指的是 grid 中一个正菱形边界上的元素之和。本题中的
菱形必须为正方形旋转 45 度,且四个角都在一个格子当中。菱形可以是一个面积为 0 的区域
(即单个格子本身)。
请你按照降序返回 grid 中三个最大的互不相同的菱形和。如果不同的和少于三个,则将它们全部返回。
示例 1
- 输入 :
grid= \[3,4,5,1,3,3,3,4,2,3,20,30,200,40,10,1,5,5,4,1,4,3,2,2,5] - 输出:228,216,211
示例 2
- 输入 :
grid= \[1,2,3,4,5,6,7,8,9] - 输出:20,9,8
示例 3
- 输入 :
grid= \[7,7,7] - 输出:7
- 解释 :所有三个可能的菱形和都相同,所以返回 7。
提示: - m == grid.length- n == grid[i].length- 1 <= m, n <= 50- 1 <= grid[i][j] <= 10^5
解题思路
解法一:两条对角线的二维前缀和 + 枚举中心与半径,主提交版本
定义 d1ij = gridij + d1i-1j-1(主对角线 ↘ 方向的累加和);
d2ij = gridij + d2i-1j+1(副对角线 ↙ 方向的累加和)。
对每个中心 (i, j) 与半径 d,菱形四个顶点分别为 (i-d,j)、(i,j+d)、(i+d,j)、(i,j-d),
四条边各是一条对角线段,利用 d1/d2 的差分 O(1) 求和,四段相加即该菱形和。
全部菱形和放入保持降序的小集合中只留前 3 大。O(mn min(m,n))。
解法二:暴力走边求和,对照
对每个中心与半径直接沿四条边逐格累加,
O(mnmin(m,n)^2),思路直观可验证解法一。
代码实现
java
import java.util.Collections;
import java.util.TreeSet;
public class Solution {
public int[] getBiggestThree(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[][] d1 = new int[m][n]; // 主对角线(↘)前缀和
int[][] d2 = new int[m][n]; // 副对角线(↙)前缀和
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
d1[i][j] = grid[i][j] + ((i - 1 >= 0 && j - 1 >= 0) ? d1[i - 1][j - 1] : 0);
d2[i][j] = grid[i][j] + ((i - 1 >= 0 && j + 1 < n) ? d2[i - 1][j + 1] : 0);
}
}
TreeSet<Integer> top = new TreeSet<>(Collections.reverseOrder()); // 降序且去重
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int maxD = Math.min(Math.min(i, m - 1 - i), Math.min(j, n - 1 - j));
for (int d = 0; d <= maxD; d++) {
int sum;
if (d == 0) {
sum = grid[i][j];
} else {
// 上顶点 (i-d,j) -> 右顶点 (i,j+d):主对角线 ↘
int seg1 = d1[i][j + d] - (i - d - 1 >= 0 && j - 1 >= 0 ? d1[i - d - 1][j - 1] : 0);
// 右顶点 (i,j+d) -> 下顶点 (i+d,j):副对角线 ↙
int seg2 = d2[i + d][j] - (i - 1 >= 0 && j + d + 1 < n ? d2[i - 1][j + d + 1] : 0);
// 下顶点 (i+d,j) -> 左顶点 (i,j-d):主对角线 ↘(反向段)
int seg3 = d1[i + d][j] - (i - 1 >= 0 && j - d - 1 >= 0 ? d1[i - 1][j - d - 1] : 0);
// 左顶点 (i,j-d) -> 上顶点 (i-d,j):副对角线 ↙(反向段)
int seg4 = d2[i][j - d] - (i - d - 1 >= 0 && j + 1 < n ? d2[i - d - 1][j + 1] : 0);
sum = seg1 + seg2 + seg3 + seg4;
// 四段前缀和差分均含两端端点,四条边的四个顶点各被计算了两次,减去一次
sum -= grid[i - d][j] + grid[i][j + d] + grid[i + d][j] + grid[i][j - d];
}
top.add(sum);
if (top.size() > 3) {
top.pollLast(); // 降序集合,pollLast 去掉最小的
}
}
}
}
int[] res = new int[top.size()];
int idx = 0;
for (int v : top) {
res[idx++] = v;
}
return res;
}
public int[] getBiggestThreeBrute(int[][] grid) {
int m = grid.length, n = grid[0].length;
TreeSet<Integer> top = new TreeSet<>(Collections.reverseOrder());
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int maxD = Math.min(Math.min(i, m - 1 - i), Math.min(j, n - 1 - j));
for (int d = 0; d <= maxD; d++) {
if (d == 0) {
top.add(grid[i][j]);
continue;
}
long s = 0;
// 四段,每段 d 步,含起点不含终点,共 4d 个边界格
int r = i - d, c = j;
for (int t = 0; t < d; t++) { s += grid[r][c]; r++; c++; }
r = i; c = j + d;
for (int t = 0; t < d; t++) { s += grid[r][c]; r++; c--; }
r = i + d; c = j;
for (int t = 0; t < d; t++) { s += grid[r][c]; r--; c--; }
r = i; c = j - d;
for (int t = 0; t < d; t++) { s += grid[r][c]; r--; c++; }
top.add((int) s);
}
while (top.size() > 3) {
top.pollLast();
}
}
}
int[] res = new int[top.size()];
int idx = 0;
for (int v : top) {
res[idx++] = v;
}
return res;
}
private static String str(int[] a) {
StringBuilder sb = new StringBuilder("[");
for (int v : a) sb.append(v).append(',');
if (a.length > 0) sb.deleteCharAt(sb.length() - 1);
sb.append(']');
return sb.toString();
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] g1 = {{3, 4, 5, 1, 3}, {3, 3, 4, 2, 3}, {20, 30, 200, 40, 10}, {1, 5, 5, 4, 1}, {4, 3, 2, 2, 5}};
int[][] g2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int[][] g3 = {{7, 7, 7}};
int[][] g4 = {{1, 1}, {1, 1}};
int[][][] cases = {g1, g2, g3, g4};
boolean ok = true;
for (int[][] c : cases) {
int[] r1 = s.getBiggestThree(c);
int[] r2 = s.getBiggestThreeBrute(c);
boolean same = java.util.Arrays.equals(r1, r2);
ok &= same;
System.out.println(str(r1) + " | " + str(r2) + (same ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1879. Minimum XOR Sum of Two Arrays(两个数组最小的异或值之和)
题目描述
给你两个整数数组 nums1 和 nums2,它们长度都为 n。两个数组的异或值之和为
(nums10 XOR nums20) + (nums11 XOR nums21) + ... + (nums1n-1 XOR nums2n-1)
(下标从 0 开始)。请你将 nums2 中的元素重新排列,使得异或值之和最小。
请你返回重新排列之后的异或值之和。
示例 1
- 输入 :
nums1= 1,2,nums2= 2,3 - 输出:2
- 解释 :将
nums2重排为 3,2,异或值之和为 (1XOR3) + (2XOR2) = 2 + 0 = 2。
示例 2
- 输入 :
nums1= 1,0,3,nums2= 5,3,4 - 输出 :8
提示: - n == nums1.length == nums2.length- 1 <= n <= 14- 0 <= nums1[i], nums2[i] <= 10^7
解题思路
解法一:状态压缩 DP,主提交版本
dpmask 表示 nums2 中已使用下标集合为 mask 时,
已经匹配了前 popcount(mask) 个 nums1 元素的最小异或值之和。转移时枚举下一个未使用的
j 加入 mask:dpmask \| (1\<\
其中 i = popcount(mask)。共 2^n 个状态,每状态转移 n,O(n * 2^n)(n <= 14 可过)。
解法二:最小费用最大流/匈牙利视角,对照
这是一般的"最小权完美匹配"问题,n 不大时
可用 DFS + 剪枝(回溯分配 nums2 元素)求解,供小规模验证解法一。
代码实现
java
import java.util.Arrays;
public class Solution {
public int minimumXORSum(int[] nums1, int[] nums2) {
int n = nums1.length;
int size = 1 << n;
int[] dp = new int[size];
Arrays.fill(dp, Integer.MAX_VALUE / 4);
dp[0] = 0;
for (int mask = 0; mask < size; mask++) {
int i = Integer.bitCount(mask); // 已匹配 nums1 前 i 个
if (i >= n) continue;
for (int j = 0; j < n; j++) {
if ((mask & (1 << j)) == 0) {
int nmask = mask | (1 << j);
dp[nmask] = Math.min(dp[nmask],
dp[mask] + (nums1[i] ^ nums2[j]));
}
}
}
return dp[size - 1];
}
public int minimumXORSumBacktrack(int[] nums1, int[] nums2) {
n = nums1.length;
used = new boolean[n];
best = Integer.MAX_VALUE / 4;
this.nums1 = nums1;
this.nums2 = nums2;
backtrack(nums1.length - 1, 0);
return best;
}
private int n;
private boolean[] used;
private int best;
private int[] nums1;
private int[] nums2;
private void backtrack(int idx, int cost) {
if (cost >= best) return; // 剪枝
if (idx < 0) {
best = cost;
return;
}
for (int j = 0; j < n; j++) {
if (!used[j]) {
used[j] = true;
backtrack(idx - 1, cost + (nums1[idx] ^ nums2[j]));
used[j] = false;
}
}
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] a = {{1, 2}, {1, 0, 3}, {2, 3, 4, 5}, {0, 0, 0}, {1, 1, 1, 1, 1}};
int[][] b = {{2, 3}, {5, 3, 4}, {1, 2, 3, 4}, {0, 0, 0}, {1, 1, 1, 1, 1}};
boolean ok = true;
for (int t = 0; t < a.length; t++) {
int r1 = s.minimumXORSum(a[t], b[t]);
int r2 = s.minimumXORSumBacktrack(a[t], b[t]);
ok &= (r1 == r2);
System.out.println(r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}
复杂度分析
1880. Check if Word Equals Summation of Two Words(检查某单词是否等于两单词之和)
题目描述
字母的字母值取决于字母在字母表中的位置,从 0 开始计数。即,'a' -> 0、'b' -> 1、'c' -> 2,
以此类推。对某个由小写字母组成的字符串 s 而言,其数值就等于将 s 中每个字母的字母值按顺序
连接并转换成对应整数。例如,s = "acb",依次连接每个字母的字母值可以得到 "021",转换为整数
得到 21。
给你三个字符串 firstWord、secondWord 和 targetWord,每个字符串都由从 'a' 到 'j'(含)的
小写英文字母组成。如果 firstWord 和 secondWord 的数值之和等于 targetWord 的数值,返回 true;
否则,返回 false。
示例 1
- 输入 :
firstWord= "acb",secondWord= "cba",targetWord= "cdb" - 输出 :
true - 解释:21 + 210 == 231。
示例 2
- 输入 :
firstWord= "aaa",secondWord= "a",targetWord= "aab" - 输出 :
false - 解释:0 + 0 != 1。
示例 3
- 输入 :
firstWord= "aaa",secondWord= "a",targetWord= "aaaa" - 输出 :
true - 解释 :0 + 0 == 0。
提示: - 1 <= firstWord.length, secondWord.length, targetWord.length <= 8-
- 三个字符串仅由从 '
a' 到 'j'(含)的小写英文字母组成。
- 三个字符串仅由从 '
解题思路
解法一:逐字母值迭代求整数,主提交版本
对每个单词,从左到右 v = v*10 + (c - 'a')。
由于首字母可能为 'a'(值为 0)会导致前导 0,用迭代累积的方式天然忽略前导 0。
O(len) 时间、O(1) 空间。
解法二:字符串拼接后解析,对照
先把每个字母的个位值拼成字符串,再用 Integer.parseInt
解析(前导 0 自动忽略),便于理解题目语义;实现直白。
代码实现
java
public class Solution {
public boolean isSumEqual(String firstWord, String secondWord, String targetWord) {
return val(firstWord) + val(secondWord) == val(targetWord);
}
private int val(String w) {
int v = 0;
for (char c : w.toCharArray()) {
v = v * 10 + (c - 'a');
}
return v;
}
public boolean isSumEqualConcat(String firstWord, String secondWord, String targetWord) {
return parse(firstWord) + parse(secondWord) == parse(targetWord);
}
private int parse(String w) {
StringBuilder sb = new StringBuilder();
for (char c : w.toCharArray()) {
sb.append((char) ('0' + (c - 'a')));
}
return Integer.parseInt(sb.toString());
}
public static void main(String[] args) {
Solution s = new Solution();
String[][] cases = {
{"acb", "cba", "cdb"},
{"aaa", "a", "aab"},
{"aaa", "a", "aaaa"},
{"b", "a", "ba"},
{"j", "j", "a"},
{"b", "b", "ba"}
};
boolean ok = true;
for (String[] c : cases) {
boolean r1 = s.isSumEqual(c[0], c[1], c[2]);
boolean r2 = s.isSumEqualConcat(c[0], c[1], c[2]);
ok &= (r1 == r2);
System.out.println(r1 + " | " + r2 + (r1 == r2 ? " OK" : " FAIL"));
}
System.out.println(ok ? "ALL OK" : "HAS FAIL");
}
}