LeetCode 1281-1300. 题解合集
文章目录
- [LeetCode 1281-1300. 题解合集](#LeetCode 1281-1300. 题解合集)
-
- [1281. Subtract the Product and Sum of Digits of an Integer(整数的各位积和之差)](#1281. Subtract the Product and Sum of Digits of an Integer(整数的各位积和之差))
- [1282. Group the People Given the Group Size They Belong to(用户分组)](#1282. Group the People Given the Group Size They Belong to(用户分组))
- [1283. Find the Smallest Divisor Given a Threshold(使结果不超过阈值的最小除数)](#1283. Find the Smallest Divisor Given a Threshold(使结果不超过阈值的最小除数))
- [1284. Minimum Number of Flips to Convert Binary Matrix to Zero Matrix](#1284. Minimum Number of Flips to Convert Binary Matrix to Zero Matrix)
- [1285. Find the Start and End Number of Continuous Ranges(找到连续区间的开始和结束数字 🔒)](#1285. Find the Start and End Number of Continuous Ranges(找到连续区间的开始和结束数字 🔒))
- [1286. Iterator for Combination(字母组合迭代器)](#1286. Iterator for Combination(字母组合迭代器))
- [1287. Element Appearing More Than 25% In Sorted Array](#1287. Element Appearing More Than 25% In Sorted Array)
- [1288. Remove Covered Intervals(删除被覆盖区间)](#1288. Remove Covered Intervals(删除被覆盖区间))
- [1289. Minimum Falling Path Sum II(下降路径最小和 II)](#1289. Minimum Falling Path Sum II(下降路径最小和 II))
- [1290. Convert Binary Number in a Linked List to Integer(二进制链表转整数)](#1290. Convert Binary Number in a Linked List to Integer(二进制链表转整数))
- [1291. Sequential Digits(顺次数)](#1291. Sequential Digits(顺次数))
- [1292. Maximum Side Length of a Square with Sum Less Than or Equal to Threshold](#1292. Maximum Side Length of a Square with Sum Less Than or Equal to Threshold)
- [1293. Shortest Path in a Grid with Obstacles Elimination(网格中的最短路径)](#1293. Shortest Path in a Grid with Obstacles Elimination(网格中的最短路径))
- [1294. Weather Type in Each Country(不同国家的天气类型 🔒)](#1294. Weather Type in Each Country(不同国家的天气类型 🔒))
- [1295. Find Numbers with Even Number of Digits(统计位数为偶数的数字)](#1295. Find Numbers with Even Number of Digits(统计位数为偶数的数字))
- [1296. Divide Array in Sets of K Consecutive Numbers(划分数组为连续数字的集合)](#1296. Divide Array in Sets of K Consecutive Numbers(划分数组为连续数字的集合))
- [1297. Maximum Number of Occurrences of a Substring(子串的最大出现次数)](#1297. Maximum Number of Occurrences of a Substring(子串的最大出现次数))
- [1298. Maximum Candies You Can Get From Boxes(你能从盒子里获得的最大糖果数)](#1298. Maximum Candies You Can Get From Boxes(你能从盒子里获得的最大糖果数))
- [1299. Replace Elements with Greatest Element on Right Side](#1299. Replace Elements with Greatest Element on Right Side)
- [1300. Sum of Mutated Array Closest to Target(转变数组后最接近目标值的数组和)](#1300. Sum of Mutated Array Closest to Target(转变数组后最接近目标值的数组和))
1281. Subtract the Product and Sum of Digits of an Integer(整数的各位积和之差)
题目描述
给你一个整数 n,请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。
示例 1
输入:n = 234
输出:15
解释:
各位数之积 = 2 * 3 * 4 = 24
各位数之和 = 2 + 3 + 4 = 9
结果 = 24 - 9 = 15
示例 2
输入:n = 4421
输出:21
解释:
各位数之积 = 4 * 4 * 2 * 1 = 32
各位数之和 = 4 + 4 + 2 + 1 = 11
结果 = 32 - 11 = 21
提示:
1 <= n <= 10^5
解题思路
解法一:逐位累乘累加(主提交版本)
while 循环每次取 n%10 得到最低位,累乘到 product、累加到 sum,然后 n/=10。
复杂度:时间 O(log n),空间 O(1)。
解法二:字符串遍历(对照)
将 n 转成字符串,逐字符解析数字并累乘累加。复杂度:时间 O(log n),空间 O(log n)。
代码实现
java
public class Solution {
public int subtractProductAndSum(int n) {
int product = 1;
int sum = 0;
while (n > 0) {
int d = n % 10;
product *= d;
sum += d;
n /= 10;
}
return product - sum;
}
public int subtractProductAndSumStr(int n) {
String str = Integer.toString(n);
int product = 1;
int sum = 0;
for (int i = 0; i < str.length(); i++) {
int d = str.charAt(i) - '0';
product *= d;
sum += d;
}
return product - sum;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.subtractProductAndSum(234)); // 15
System.out.println(s.subtractProductAndSumStr(234)); // 15
System.out.println(s.subtractProductAndSum(4421)); // 21
System.out.println(s.subtractProductAndSumStr(4421)); // 21
System.out.println(s.subtractProductAndSum(1)); // 0
System.out.println(s.subtractProductAndSumStr(1)); // 0
System.out.println(s.subtractProductAndSum(100000)); // -1(积 0,和 1)
System.out.println(s.subtractProductAndSumStr(100000));// -1
}
}
复杂度分析
复杂度:时间 O(log n),空间 O(1)。
将 n 转成字符串,逐字符解析数字并累乘累加。复杂度:时间 O(log n),空间 O(log n)。
1282. Group the People Given the Group Size They Belong to(用户分组)
题目描述
有 n 个人被分成数量未知的组。每个人都被标记为一个从 0 到 n - 1 的唯一ID 。
给定一个整数数组 groupSizes ,其中 groupSizes[i] 是第 i 个人所在的组的大小。例如,如果 groupSizes[1] = 3 ,则第 1 个人必须位于大小为 3 的组中。
返回一个组列表,使每个人 i 都在一个大小为 groupSizes[i] 的组中。
每个人应该 恰好只 出现在 一个组 中,并且每个人必须在一个组中。如果有多个答案,返回其中 任何 一个。可以 保证 给定输入 至少有一个 有效的解。
示例 1
输入:groupSizes = [3,3,3,3,3,1,3]
输出:[[5],[0,1,2],[3,4,6]]
解释:
第一组是 [5],大小为 1,groupSizes[5] = 1。
第二组是 [0,1,2],大小为 3,groupSizes[0] = groupSizes[1] = groupSizes[2] = 3。
第三组是 [3,4,6],大小为 3,groupSizes[3] = groupSizes[4] = groupSizes[6] = 3。
其他可能的解决方案有 [[2,1,6],[5],[0,4,3]] 和 [[5],[0,6,2],[4,3,1]]。
示例 2
输入:groupSizes = [2,1,3,3,3,2]
输出:[[1],[0,5],[2,3,4]]
提示:
groupSizes.length == n1 <= n <= 5001 <= groupSizes[i] <= n
解题思路
解法一:哈希表分组(主提交版本)
用 Map<Integer, List> 把"组大小"映射到暂存的人列表。
遍历每个人 i,加入对应大小的列表;一旦该列表长度等于 size,立即作为一个组输出并清空。
复杂度:时间 O(n),空间 O(n)。
解法二:计数索引游标(对照)
先统计每种大小的总人数,为每种大小预先分配若干组,然后用游标数组把每个人安排进对应大小的组。
复杂度:时间 O(n),空间 O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Solution {
public List<List<Integer>> groupThePeople(int[] groupSizes) {
List<List<Integer>> res = new ArrayList<>();
Map<Integer, List<Integer>> buckets = new HashMap<>();
for (int i = 0; i < groupSizes.length; i++) {
int size = groupSizes[i];
List<Integer> cur = buckets.get(size);
if (cur == null) {
cur = new ArrayList<>();
buckets.put(size, cur);
}
cur.add(i);
if (cur.size() == size) {
res.add(new ArrayList<>(cur));
cur.clear();
}
}
return res;
}
public List<List<Integer>> groupThePeopleCursor(int[] groupSizes) {
int n = groupSizes.length;
int maxSize = 0;
for (int sz : groupSizes) {
maxSize = Math.max(maxSize, sz);
}
// groupCount[sz]:大小 sz 需要几个组
int[] count = new int[n + 1];
int[] groupCount = new int[n + 1];
for (int sz : groupSizes) {
count[sz]++;
}
for (int sz = 1; sz <= n; sz++) {
groupCount[sz] = count[sz] / sz;
}
// 初始化所有组
List<List<Integer>> groups = new ArrayList<>();
for (int sz = 1; sz <= n; sz++) {
for (int g = 0; g < groupCount[sz]; g++) {
groups.add(new ArrayList<>());
}
}
// 为每种大小建立"下一可用组"游标(该大小在 groups 中的起点)
int[] next = new int[n + 1];
int base = 0;
for (int sz = 1; sz <= n; sz++) {
next[sz] = base;
base += groupCount[sz];
}
for (int i = 0; i < n; i++) {
int sz = groupSizes[i];
groups.get(next[sz]).add(i);
if (groups.get(next[sz]).size() == sz) {
next[sz]++;
}
}
// 去掉空组(受输入保证不应存在,防御性过滤)
List<List<Integer>> res = new ArrayList<>();
for (List<Integer> g : groups) {
if (!g.isEmpty()) {
res.add(g);
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.groupThePeople(new int[]{3, 3, 3, 3, 3, 1, 3}));
System.out.println(s.groupThePeopleCursor(new int[]{3, 3, 3, 3, 3, 1, 3}));
System.out.println(s.groupThePeople(new int[]{2, 1, 3, 3, 3, 2}));
System.out.println(s.groupThePeopleCursor(new int[]{2, 1, 3, 3, 3, 2}));
System.out.println(s.groupThePeople(new int[]{1}));
System.out.println(s.groupThePeopleCursor(new int[]{1}));
System.out.println(s.groupThePeople(new int[]{2, 2}));
System.out.println(s.groupThePeopleCursor(new int[]{2, 2}));
}
}
复杂度分析
复杂度:时间 O(n),空间 O(n)。
1283. Find the Smallest Divisor Given a Threshold(使结果不超过阈值的最小除数)
题目描述
给你一个整数数组 nums 和一个正整数 threshold ,你需要选择一个正整数作为除数,然后将数组里每个数都除以它,并对除法结果求和。
请你找出能够使上述结果小于等于阈值 threshold 的除数中 最小 的那个。
每个数除以除数后都向上取整,比方说 7/3 = 3 , 10/2 = 5 。
题目保证一定有解。
示例 1
输入:nums = [1,2,5,9], threshold = 6
输出:5
解释:如果除数为 1 ,我们可以得到和为 17 (1+2+5+9)。
如果除数为 4 ,我们可以得到和为 7 (1+1+2+3) 。如果除数为 5 ,和为 5 (1+1+1+2)。
示例 2
输入:nums = [2,3,5,7,11], threshold = 11
输出:3
示例 3
输入:nums = [19], threshold = 5
输出:4
提示:
1 <= nums.length <= 5 * 10^41 <= nums[i] <= 10^6nums.length <= threshold <= 10^6
解题思路
解法一:二分答案 + 贪心检验(主提交版本)
代价函数 f(x) = sum(ceil(numsi/x)) 关于 x 单调不增。对 x 在 1, max(nums) 上二分,
找最小的 x 使 f(x) <= threshold。复杂度:时间 O(nlog(max)),空间 O(1)。
解法二:线性枚举(对照)
从 1 开始逐一遍历除数直到满足条件,理论最坏 O(max) 轮会超时,故用"按需跳跃+提前终止"的直观版本:
直接枚举可行域并即时返回第一个解(用于小数据正确性对照)。复杂度:时间 O(maxn),空间 O(1)。
代码实现
java
public class Solution {
public int smallestDivisor(int[] nums, int threshold) {
int lo = 1, hi = 0;
for (int x : nums) {
hi = Math.max(hi, x);
}
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (sum(nums, mid) <= threshold) {
hi = mid;
} else {
lo = mid + 1;
}
}
return lo;
}
private int sum(int[] nums, int d) {
long total = 0;
for (int x : nums) {
total += (x + d - 1) / d;
}
return total > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) total;
}
public int smallestDivisorLinear(int[] nums, int threshold) {
int d = 1;
while (true) {
if (sum(nums, d) <= threshold) {
return d;
}
d++;
}
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.smallestDivisor(new int[]{1, 2, 5, 9}, 6)); // 5
System.out.println(s.smallestDivisorLinear(new int[]{1, 2, 5, 9}, 6));// 5
System.out.println(s.smallestDivisor(new int[]{2, 3, 5, 7, 11}, 11)); // 3
System.out.println(s.smallestDivisorLinear(new int[]{2, 3, 5, 7, 11}, 11));// 3
System.out.println(s.smallestDivisor(new int[]{19}, 5)); // 4
System.out.println(s.smallestDivisorLinear(new int[]{19}, 5)); // 4
System.out.println(s.smallestDivisor(new int[]{1, 1, 1}, 3)); // 1
System.out.println(s.smallestDivisorLinear(new int[]{1, 1, 1}, 3)); // 1
}
}
复杂度分析
找最小的 x 使 f(x) <= threshold。复杂度:时间 O(nlog(max)),空间 O(1)。
直接枚举可行域并即时返回第一个解(用于小数据正确性对照)。复杂度:时间 O(maxn),空间 O(1)。
1284. Minimum Number of Flips to Convert Binary Matrix to Zero Matrix
题目描述
给你一个 m x n 的二进制矩阵 mat。每一步,你可以选择一个单元格并将它反转(反转表示 0 变 1 ,1 变 0 )。如果存在和它相邻的单元格,那么这些相邻的单元格也会被反转。相邻的两个单元格共享同一条边。
请你返回将矩阵 mat 转化为全零矩阵的最少反转次数,如果无法转化为全零矩阵,请返回 -1 。
二进制矩阵 的每一个格子要么是 0 要么是 1 。
全零矩阵 是所有格子都为 0 的矩阵。
示例 1
输入:mat = [[0,0],[0,1]]
输出:3
解释:一个可能的解是反转 (1, 0),然后 (0, 1) ,最后是 (1, 1) 。
示例 2
输入:mat = [[0]]
输出:0
解释:给出的矩阵是全零矩阵,所以你不需要改变它。
示例 3
输入:mat = [[1,0,0],[1,0,0]]
输出:-1
解释:该矩阵无法转变成全零矩阵
提示:
m == mat.lengthn == mat[0].length1 <= m <= 31 <= n <= 3mat[i][j]是 0 或 1 。
解题思路
解法一:状态压缩 BFS(主提交版本)
矩阵至多 9 格,把每个矩阵状态压缩为一个整数(位 i 表示格子值)。
从初始状态出发 BFS:对每个格子翻转(连同上下左右)生成新状态,首次到达 0 即最短步数。
复杂度:时间 O(2^(mn) * m n),空间 O(2^(m*n))。
解法二:高斯消元求解翻转集(对照)
由"每个格子是否翻转"构成 0/1 方程组(每个方程:某格翻转影响集合内值为 1 的格子数应为 0 mod 2),
用高斯消元(异或)求自由变量的最小汉明重量解。复杂度:时间 O((mn)^3),空间 O((mn)^2)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
public class Solution {
public int minFlips(int[][] mat) {
int m = mat.length, n = mat[0].length;
int total = m * n;
int start = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 1) {
start |= 1 << (i * n + j);
}
}
}
if (start == 0) {
return 0;
}
Queue<Integer> q = new ArrayDeque<>();
Map<Integer, Integer> dist = new HashMap<>();
q.offer(start);
dist.put(start, 0);
int[] dx = {0, 0, 1, -1};
int[] dy = {1, -1, 0, 0};
while (!q.isEmpty()) {
int cur = q.poll();
int d = dist.get(cur);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int next = cur ^ (1 << (i * n + j));
for (int k = 0; k < 4; k++) {
int ni = i + dx[k], nj = j + dy[k];
if (ni >= 0 && ni < m && nj >= 0 && nj < n) {
next ^= 1 << (ni * n + nj);
}
}
if (next == 0) {
return d + 1;
}
if (!dist.containsKey(next)) {
dist.put(next, d + 1);
q.offer(next);
}
}
}
}
return -1;
}
public int minFlipsGauss(int[][] mat) {
int m = mat.length, n = mat[0].length;
int total = m * n;
int start = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 1) {
start |= 1 << (i * n + j);
}
}
}
if (start == 0) {
return 0;
}
int[] dx = {0, 0, 1, -1};
int[] dy = {1, -1, 0, 0};
// 构造增广矩阵:对每个格子建立一个方程
// 方程 (i,j):x[i][j] ^ x[邻] = mat[i][j]
int[][] a = new int[total][total + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int row = i * n + j;
a[row][row] = 1;
for (int k = 0; k < 4; k++) {
int ni = i + dx[k], nj = j + dy[k];
if (ni >= 0 && ni < m && nj >= 0 && nj < n) {
a[row][ni * n + nj] = 1;
}
}
a[row][total] = (mat[i][j] == 1) ? 1 : 0;
}
}
// 高斯消元(异或)
int rank = 0;
int[] where = new int[total];
Arrays.fill(where, -1);
for (int col = 0; col < total; col++) {
int pivot = -1;
for (int r = rank; r < total; r++) {
if (a[r][col] == 1) {
pivot = r;
break;
}
}
if (pivot == -1) {
continue;
}
int[] tmp = a[pivot];
a[pivot] = a[rank];
a[rank] = tmp;
where[col] = rank;
for (int r = 0; r < total; r++) {
if (r != rank && a[r][col] == 1) {
for (int c = col; c <= total; c++) {
a[r][c] ^= a[rank][c];
}
}
}
rank++;
}
// 检查无解:某行系数全 0 但常数 1
for (int r = 0; r < total; r++) {
boolean allZero = true;
for (int c = 0; c < total; c++) {
if (a[r][c] == 1) {
allZero = false;
break;
}
}
if (allZero && a[r][total] == 1) {
return -1;
}
}
// 自由变量枚举:主元变量 = 常数 ^ 自由变量贡献
// 固定自由变量取值后回代得到全部 x,取最小翻转数
int freeCount = total - rank;
int best = Integer.MAX_VALUE;
for (int mask = 0; mask < (1 << freeCount); mask++) {
int[] x = new int[total];
int fi = 0;
for (int c = 0; c < total; c++) {
if (where[c] == -1) {
x[c] = (mask >> fi) & 1;
fi++;
}
}
for (int c = total - 1; c >= 0; c--) {
if (where[c] != -1) {
int row = where[c];
int val = a[row][total];
for (int cc = c + 1; cc < total; cc++) {
if (a[row][cc] == 1) {
val ^= x[cc];
}
}
x[c] = val;
}
}
int cnt = 0;
for (int v : x) {
cnt += v;
}
if (cnt < best) {
best = cnt;
}
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minFlips(new int[][]{{0, 0}, {0, 1}})); // 3
System.out.println(s.minFlipsGauss(new int[][]{{0, 0}, {0, 1}})); // 3
System.out.println(s.minFlips(new int[][]{{0}})); // 0
System.out.println(s.minFlipsGauss(new int[][]{{0}})); // 0
System.out.println(s.minFlips(new int[][]{{1, 0, 0}, {1, 0, 0}})); // -1
System.out.println(s.minFlipsGauss(new int[][]{{1, 0, 0}, {1, 0, 0}})); // -1
System.out.println(s.minFlips(new int[][]{{1}})); // 1
System.out.println(s.minFlipsGauss(new int[][]{{1}})); // 1
System.out.println(s.minFlips(new int[][]{{0, 1}, {1, 0}})); // 1 或 3(BFS 最短 1)
System.out.println(s.minFlipsGauss(new int[][]{{0, 1}, {1, 0}})); // 2(每个格子翻两次等价不翻)
}
}
复杂度分析
复杂度:时间 O(2^(mn) * m n),空间 O(2^(m*n))。
用高斯消元(异或)求自由变量的最小汉明重量解。复杂度:时间 O((mn)^3),空间 O((mn)^2)。
1285. Find the Start and End Number of Continuous Ranges(找到连续区间的开始和结束数字 🔒)
题目类型:数据库(SQL)
题目描述
表:Logs
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| log_id | int |
+---------------+---------+
log_id 是上表具有唯一值的列。
上表的每一行包含日志表中的一个 ID。
编写解决方案,得到 Logs 表中的连续区间的开始数字和结束数字。
返回结果表按照 start_id 排序。
结果格式如下面的例子。
示例 1:
**输入:**
Logs 表:
+------------+
| log_id |
+------------+
| 1 |
| 2 |
| 3 |
| 7 |
| 8 |
| 10 |
+------------+
**输出:**
+------------+--------------+
| start_id | end_id |
+------------+--------------+
| 1 | 3 |
| 7 | 8 |
| 10 | 10 |
+------------+--------------+
**解释:**
结果表应包含 Logs 表中的所有区间。
从 1 到 3 在表中。
从 4 到 6 不在表中。
从 7 到 8 在表中。
9 不在表中。
10 在表中。
解题思路
思考
连续日志的编号相差 1 1 1。用与前一行的差是否为 1 1 1 得到 0 / 1 0/1 0/1,再求前缀和,相同前缀和的行落在同一段。按该标识分组取最小、最大 l o g _ i d log\_id log_id。
考察窗口函数/等差数列分组:log_id - ROW_NUMBER() 得组键,按组取 MIN/MAX(log_id) 即为每个连续区间的 start_id/end_id,按 start_id 排序。
代码实现
sql
# Write your MySQL query statement below
WITH
T AS (
SELECT
log_id,
SUM(delta) OVER (ORDER BY log_id) AS pid
FROM
(
SELECT
log_id,
IF((log_id - LAG(log_id) OVER (ORDER BY log_id)) = 1, 0, 1) AS delta
FROM Logs
) AS t
)
SELECT MIN(log_id) AS start_id, MAX(log_id) AS end_id
FROM T
GROUP BY pid;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1286. Iterator for Combination(字母组合迭代器)
题目描述
请你设计一个迭代器类 CombinationIterator ,包括以下内容:
CombinationIterator(string characters, int combinationLength)一个构造函数,输入参数包括:用一个 有序且字符唯一 的字符串characters(该字符串只包含小写英文字母)和一个数字combinationLength。- 函数
next(),按 字典序 返回长度为combinationLength的下一个字母组合。 - 函数
hasNext(),只有存在长度为combinationLength的下一个字母组合时,才返回true
示例 1
输入:
["CombinationIterator", "next", "hasNext", "next", "hasNext", "next", "hasNext"]
[["abc", 2], [], [], [], [], [], []]
输出:
[null, "ab", true, "ac", true, "bc", false]
解释:
CombinationIterator iterator = new CombinationIterator("abc", 2); // 创建迭代器 iterator
iterator.next(); // 返回 "ab"
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 "ac"
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 "bc"
iterator.hasNext(); // 返回 false
提示:
1 <= combinationLength <= characters.length <= 15characters中每个字符都 不同- 每组测试数据最多对
next和hasNext调用104次 - 题目保证每次调用函数
next时都存在下一个字母组合。
解题思路
解法一:预生成全部组合(主提交版本)
构造时用回溯 DFS 按"索引递增"顺序生成所有 C(n, k) 组合存入列表(天然字典序),
next/hasNext 用游标取值。复杂度:构造 O(C(n,k)*k),next/hasNext O(1)。
解法二:按组合序推进(对照,懒生成)
不预生成。内部维护当前组合的索引数组 idx0...k-1。
next() 先由 idx 构造字符串,再用标准"下一个组合"推进算法(从后往前找可进位位置)。
hasNext() 判断 idx 是否已达到最后一个组合。
复杂度:next 最坏 O(k),均摊 O(1),空间 O(k)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public static class CombinationIterator {
private List<String> combos;
private int ptr;
public CombinationIterator(String characters, int combinationLength) {
combos = new ArrayList<>();
dfs(characters, combinationLength, 0, new StringBuilder(), combos);
ptr = 0;
}
private void dfs(String chars, int k, int start, StringBuilder sb, List<String> out) {
if (sb.length() == k) {
out.add(sb.toString());
return;
}
for (int i = start; i <= chars.length() - (k - sb.length()); i++) {
sb.append(chars.charAt(i));
dfs(chars, k, i + 1, sb, out);
sb.deleteCharAt(sb.length() - 1);
}
}
public String next() {
return combos.get(ptr++);
}
public boolean hasNext() {
return ptr < combos.size();
}
}
public static class CombinationIteratorLazy {
private String chars;
private int n, k;
private int[] idx;
public CombinationIteratorLazy(String characters, int combinationLength) {
chars = characters;
n = characters.length();
k = combinationLength;
idx = new int[k];
for (int i = 0; i < k; i++) {
idx[i] = i;
}
}
public String next() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < k; i++) {
sb.append(chars.charAt(idx[i]));
}
advance();
return sb.toString();
}
// 把 idx 推进到下一个组合(若无下一个,将 idx[0] 置为 -1 表示结束)
private void advance() {
int i = k - 1;
while (i >= 0 && idx[i] == n - (k - i)) {
i--;
}
if (i < 0) {
idx[0] = -1; // 结束标记
return;
}
idx[i]++;
for (int j = i + 1; j < k; j++) {
idx[j] = idx[j - 1] + 1;
}
}
public boolean hasNext() {
return idx[0] != -1;
}
}
public static void main(String[] args) {
// 示例:"abc" 长度 2 -> ab, ac, bc
CombinationIterator it = new CombinationIterator("abc", 2);
System.out.println(it.hasNext()); // true
System.out.println(it.next()); // ab
System.out.println(it.hasNext()); // true
System.out.println(it.next()); // ac
System.out.println(it.hasNext()); // true
System.out.println(it.next()); // bc
System.out.println(it.hasNext()); // false
System.out.println("--- 懒生成版 ---");
CombinationIteratorLazy it2 = new CombinationIteratorLazy("abc", 2);
while (it2.hasNext()) {
System.out.println(it2.next());
}
CombinationIteratorLazy it3 = new CombinationIteratorLazy("abcd", 3);
while (it3.hasNext()) {
System.out.print(it3.next() + " "); // abc abd acd bcd
}
System.out.println();
}
}
复杂度分析
复杂度:next 最坏 O(k),均摊 O(1),空间 O(k)。
1287. Element Appearing More Than 25% In Sorted Array
题目描述
给你一个非递减的 有序 整数数组,已知这个数组中恰好有一个整数,它的出现次数超过数组元素总数的 25%。
请你找到并返回这个整数
示例
输入:arr = [1,2,2,6,6,6,6,7,10]
输出:6
提示:
1 <= arr.length <= 10^40 <= arr[i] <= 10^5
解题思路
解法一:等分点候选 + 二分频次(主提交版本)
若某数出现次数 > n/4,则在有序数组中它出现的连续区间长度 > n/4,
必然覆盖 n/4、2n/4、3n/4 三个等分点之一。对每个候选值用二分求其首次/末次出现位置,
验证频次 > n/4 即得答案。复杂度:时间 O(log n),空间 O(1)。
解法二:扫描分段计数(对照)
依次扫描数组相邻等值段,段长 > n/4 即返回。复杂度:时间 O(n),空间 O(1)。
代码实现
java
public class Solution {
public int findSpecialInteger(int[] arr) {
int n = arr.length;
int[] candidates = {arr[n / 4], arr[n / 2], arr[3 * n / 4]};
for (int c : candidates) {
int left = lowerBound(arr, c);
int right = upperBound(arr, c);
if (right - left > n / 4) {
return c;
}
}
return -1; // 题目保证存在,不会走到这里
}
private int lowerBound(int[] arr, int target) {
int lo = 0, hi = arr.length;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid] < target) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
private int upperBound(int[] arr, int target) {
int lo = 0, hi = arr.length;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid] <= target) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
public int findSpecialIntegerScan(int[] arr) {
int n = arr.length;
int i = 0;
while (i < n) {
int j = i;
while (j < n && arr[j] == arr[i]) {
j++;
}
if (j - i > n / 4) {
return arr[i];
}
i = j;
}
return -1;
}
public static void main(String[] args) {
Solution s = new Solution();
int[] t1 = {1, 2, 2, 6, 6, 6, 6, 7, 10};
System.out.println(s.findSpecialInteger(t1)); // 6
System.out.println(s.findSpecialIntegerScan(t1)); // 6
int[] t2 = {1};
System.out.println(s.findSpecialInteger(t2)); // 1
System.out.println(s.findSpecialIntegerScan(t2)); // 1
int[] t3 = {1, 1, 2, 2, 3, 3, 3, 3};
System.out.println(s.findSpecialInteger(t3)); // 3
System.out.println(s.findSpecialIntegerScan(t3)); // 3
int[] t4 = {5, 5, 5, 5, 6, 7, 8, 9, 10};
System.out.println(s.findSpecialInteger(t4)); // 5
System.out.println(s.findSpecialIntegerScan(t4)); // 5
}
}
复杂度分析
验证频次 > n/4 即得答案。复杂度:时间 O(log n),空间 O(1)。
依次扫描数组相邻等值段,段长 > n/4 即返回。复杂度:时间 O(n),空间 O(1)。
1288. Remove Covered Intervals(删除被覆盖区间)
题目描述
给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。
只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。
在完成所有删除操作后,请你返回列表中剩余区间的数目。
示例
输入:intervals = [[1,4],[3,6],[2,8]]
输出:2
解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。
提示:
1 <= intervals.length <= 10000 <= intervals[i][0] < intervals[i][1] <= 10^5- 对于所有的
i != j:intervals[i] != intervals[j]
解题思路
解法一:排序 + 贪心扫描(主提交版本)
按左端点升序、右端点降序排序。遍历时维护"当前区间可以覆盖到的最远右端点 r":
若下一区间的右端点 <= r,则它被之前某区间覆盖,答案减 1;否则更新 r。
复杂度:时间 O(n log n),空间 O(1)(栈排序除外)。
解法二:朴素两两覆盖判定(对照)
双重循环,若某区间 [a,b) 存在另一个 [c,d) 满足 c<=a 且 b<=d 则标记删除。
复杂度:时间 O(n^2),空间 O(n)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int removeCoveredIntervals(int[][] intervals) {
Arrays.sort(intervals, (x, y) -> x[0] != y[0] ? x[0] - y[0] : y[1] - x[1]);
int count = intervals.length;
int far = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][1] <= far) {
count--; // [i] 被前面的区间覆盖
} else {
far = intervals[i][1];
}
}
return count;
}
public int removeCoveredIntervalsBrute(int[][] intervals) {
int n = intervals.length;
boolean[] removed = new boolean[n];
int count = n;
for (int i = 0; i < n; i++) {
if (removed[i]) {
continue;
}
for (int j = 0; j < n; j++) {
if (i == j || removed[j]) {
continue;
}
if (intervals[j][0] <= intervals[i][0] && intervals[i][1] <= intervals[j][1]) {
removed[i] = true;
count--;
break;
}
}
}
return count;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.removeCoveredIntervals(new int[][]{{1, 4}, {3, 6}, {2, 8}})); // 2
System.out.println(s.removeCoveredIntervalsBrute(new int[][]{{1, 4}, {3, 6}, {2, 8}})); // 2
System.out.println(s.removeCoveredIntervals(new int[][]{{1, 4}, {2, 3}})); // 1
System.out.println(s.removeCoveredIntervalsBrute(new int[][]{{1, 4}, {2, 3}})); // 1
System.out.println(s.removeCoveredIntervals(new int[][]{{1, 2}, {1, 4}, {3, 4}})); // 2
System.out.println(s.removeCoveredIntervalsBrute(new int[][]{{1, 2}, {1, 4}, {3, 4}})); // 2
System.out.println(s.removeCoveredIntervals(new int[][]{{0, 10}, {5, 12}})); // 2
System.out.println(s.removeCoveredIntervalsBrute(new int[][]{{0, 10}, {5, 12}})); // 2
}
}
复杂度分析
复杂度:时间 O(n log n),空间 O(1)(栈排序除外)。
复杂度:时间 O(n^2),空间 O(n)。
1289. Minimum Falling Path Sum II(下降路径最小和 II)
题目描述
给你一个 n x n 整数矩阵 grid ,请你返回 非零偏移下降路径 数字和的最小值。
非零偏移下降路径 定义为:从 grid 数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。
示例 1
输入:grid = [[1,2,3],[4,5,6],[7,8,9]]
输出:13
解释:
所有非零偏移下降路径包括:
[1,5,9], [1,5,7], [1,6,7], [1,6,8],
[2,4,8], [2,4,9], [2,6,7], [2,6,8],
[3,4,8], [3,4,9], [3,5,7], [3,5,9]
下降路径中数字和最小的是 [1,5,7] ,所以答案是 13 。
示例 2
输入:grid = [[7]]
输出:7
提示:
n == grid.length == grid[i].length1 <= n <= 200-99 <= grid[i][j] <= 99
解题思路
解法一:动态规划 + 最小/次小值优化(主提交版本)
记上一行前两小的列及值(min1, min2)。转移时:
dpj = gridij + (j != min1Col ? min1Val : min2Val)。
由于每行转移只需上一行的最值信息,无需整行 DP 数组。
复杂度:时间 O(n^2),空间 O(1)。
解法二:二维 DP(对照)
dpij 表示走到第 i 行第 j 列的最小路径和,
dpij = gridij + min(dpi-1c),其中 c != j。
复杂度:时间 O(n^3)(朴素求非同行最小可优化为 O(n^2)),本实现用 top-2 减为 O(n^2)。
空间 O(n^2)(可用滚动数组降为 O(n))。
代码实现
java
public class Solution {
public int minFallingPathSum(int[][] grid) {
int n = grid.length;
// 上一行的最小值和次小值(值 + 列号)
int m1v = 0, m1c = -1, m2v = 0;
for (int i = 0; i < n; i++) {
int cur1 = Integer.MAX_VALUE, cur1c = -1, cur2 = Integer.MAX_VALUE;
for (int j = 0; j < n; j++) {
int bestPrev = (j != m1c) ? m1v : m2v;
int cur = grid[i][j] + bestPrev;
if (cur < cur1) {
cur2 = cur1;
cur1 = cur;
cur1c = j;
} else if (cur < cur2) {
cur2 = cur;
}
}
m1v = cur1;
m1c = cur1c;
m2v = cur2;
}
return m1v;
}
public int minFallingPathSum2D(int[][] grid) {
int n = grid.length;
int[][] dp = new int[n][n];
for (int j = 0; j < n; j++) {
dp[0][j] = grid[0][j];
}
for (int i = 1; i < n; i++) {
// 求上一行的两个最小及其列号
int m1 = Integer.MAX_VALUE, m1c = -1, m2 = Integer.MAX_VALUE;
for (int j = 0; j < n; j++) {
if (dp[i - 1][j] < m1) {
m2 = m1;
m1 = dp[i - 1][j];
m1c = j;
} else if (dp[i - 1][j] < m2) {
m2 = dp[i - 1][j];
}
}
for (int j = 0; j < n; j++) {
dp[i][j] = grid[i][j] + (j != m1c ? m1 : m2);
}
}
int res = Integer.MAX_VALUE;
for (int j = 0; j < n; j++) {
res = Math.min(res, dp[n - 1][j]);
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minFallingPathSum(new int[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}})); // 13
System.out.println(s.minFallingPathSum2D(new int[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}})); // 13
System.out.println(s.minFallingPathSum(new int[][]{{7}})); // 7
System.out.println(s.minFallingPathSum2D(new int[][]{{7}})); // 7
System.out.println(s.minFallingPathSum(new int[][]{{2, 2, 2}, {2, 2, 2}, {2, 2, 2}})); // 6
System.out.println(s.minFallingPathSum2D(new int[][]{{2, 2, 2}, {2, 2, 2}, {2, 2, 2}})); // 6
System.out.println(s.minFallingPathSum(new int[][]{{-1, 5}, {7, 8}})); // -1+7=6 或 5+8=13 -> 6
System.out.println(s.minFallingPathSum2D(new int[][]{{-1, 5}, {7, 8}})); // 6
}
}
复杂度分析
复杂度:时间 O(n^2),空间 O(1)。
复杂度:时间 O(n^3)(朴素求非同行最小可优化为 O(n^2)),本实现用 top-2 减为 O(n^2)。
空间 O(n^2)(可用滚动数组降为 O(n))。
1290. Convert Binary Number in a Linked List to Integer(二进制链表转整数)
题目描述
给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
最高位 在链表的头部。
示例 1
输入:head = [1,0,1]
输出:5
解释:二进制数 (101) 转化为十进制数 (5)
示例 2
输入:head = [0]
输出:0
提示:
- 链表不为空。
- 链表的结点总数不超过
30。 - 每个结点的值不是
0就是1。
解题思路
解法一:位运算累进(主提交版本)
从头遍历链表,res = res * 2 + node.val。等价于把二进制数左移一位后加上当前位。
复杂度:时间 O(n),空间 O(1)。
解法二:字符串拼接 + 进制解析(对照)
把链表各位拼成二进制字符串,再用 Integer.parseInt(str, 2) 转换。
复杂度:时间 O(n),空间 O(n)。
代码实现
java
public class Solution {
public static class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
private ListNode build(int[] arr) {
ListNode dummy = new ListNode(0);
ListNode cur = dummy;
for (int v : arr) {
cur.next = new ListNode(v);
cur = cur.next;
}
return dummy.next;
}
public int getDecimalValue(ListNode head) {
int res = 0;
while (head != null) {
res = (res << 1) | head.val;
head = head.next;
}
return res;
}
public int getDecimalValueStr(ListNode head) {
StringBuilder sb = new StringBuilder();
while (head != null) {
sb.append(head.val);
head = head.next;
}
return Integer.parseInt(sb.toString(), 2);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.getDecimalValue(s.build(new int[]{1, 0, 1}))); // 5
System.out.println(s.getDecimalValueStr(s.build(new int[]{1, 0, 1}))); // 5
System.out.println(s.getDecimalValue(s.build(new int[]{0}))); // 0
System.out.println(s.getDecimalValueStr(s.build(new int[]{0}))); // 0
System.out.println(s.getDecimalValue(s.build(new int[]{1}))); // 1
System.out.println(s.getDecimalValueStr(s.build(new int[]{1}))); // 1
System.out.println(s.getDecimalValue(s.build(new int[]{1, 1, 1, 1, 1, 1}))); // 63
System.out.println(s.getDecimalValueStr(s.build(new int[]{1, 1, 1, 1, 1, 1}))); // 63
}
}
复杂度分析
复杂度:时间 O(n),空间 O(1)。
复杂度:时间 O(n),空间 O(n)。
1291. Sequential Digits(顺次数)
题目描述
我们定义「顺次数」为:每一位上的数字都比前一位上的数字大 1 的整数。
请你返回由 [low, high] 范围内所有顺次数组成的 有序 列表(从小到大排序)。
示例 1
输入:low = 100, high = 300
输出:[123,234]
示例 2
输入:low = 1000, high = 13000
输出:[1234,2345,3456,4567,5678,6789,12345]
提示:
10 <= low <= high <= 10^9
解题思路
解法一:枚举起点与长度(主提交版本)
顺次数的每一位递增 1,最多 9 位。枚举起始数字 d(1~9)与长度 len(1~9-d+1),
由 d 开始连续 len 位生成候选数,落在 low, high 内则收集,最后排序输出。
复杂度:时间 O(99 9),空间 O(len),可视为 O(1)。
解法二:滑动窗口在"数字串"上取窗口(对照)
构造字符串 "123456789",枚举窗口起点与长度:滑动窗口 [i, i+len) 对应的整数即顺次数。
复杂度:时间 O(1) 级别(候选总数有限),空间 O(1)。
代码实现
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Solution {
public List<Integer> sequentialDigits(int low, int high) {
List<Integer> res = new ArrayList<>();
for (int start = 1; start <= 9; start++) {
int num = 0;
for (int d = start; d <= 9; d++) {
num = num * 10 + d;
if (num >= low && num <= high) {
res.add(num);
}
}
}
Collections.sort(res);
return res;
}
public List<Integer> sequentialDigitsSlide(int low, int high) {
String digits = "123456789";
List<Integer> res = new ArrayList<>();
for (int len = 2; len <= 9; len++) {
for (int i = 0; i + len <= 9; i++) {
String sub = digits.substring(i, i + len);
int num = Integer.parseInt(sub);
if (num >= low && num <= high) {
res.add(num);
}
}
}
Collections.sort(res);
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.sequentialDigits(100, 300)); // [123, 234]
System.out.println(s.sequentialDigitsSlide(100, 300)); // [123, 234]
System.out.println(s.sequentialDigits(1000, 13000)); // [1234, 2345, 3456, 4567, 5678, 6789, 12345]
System.out.println(s.sequentialDigitsSlide(1000, 13000));
System.out.println(s.sequentialDigits(10, 12)); // [12]
System.out.println(s.sequentialDigitsSlide(10, 12));
System.out.println(s.sequentialDigits(2345, 123456)); // 部分顺次数
System.out.println(s.sequentialDigitsSlide(2345, 123456));
}
}
复杂度分析
复杂度:时间 O(99 9),空间 O(len),可视为 O(1)。
复杂度:时间 O(1) 级别(候选总数有限),空间 O(1)。
1292. Maximum Side Length of a Square with Sum Less Than or Equal to Threshold
题目描述
给你一个大小为 m x n 的矩阵 mat 和一个整数阈值 threshold。
请你返回元素总和小于或等于阈值的正方形区域的最大边长;如果没有这样的正方形区域,则返回 0 。
示例 1
输入:mat = [[1,1,3,2,4,3,2],[1,1,3,2,4,3,2],[1,1,3,2,4,3,2]], threshold = 4
输出:2
解释:总和小于或等于 4 的正方形的最大边长为 2,如图所示。
示例 2
输入:mat = [[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2]], threshold = 1
输出:0
提示:
m == mat.lengthn == mat[i].length1 <= m, n <= 3000 40 5
解题思路
解法一:二维前缀和 + 二分边长(主提交版本)
预处理二维前缀和 prei+1j+1,任意正方形 (r,c,size) 的和可在 O(1) 求出。
对边长在 0, min(m,n) 上进行二分(check 边长 k 是否存在一个 k×k 方块和 <= threshold)。
复杂度:时间 O(mn + min(m,n) * m * n),空间 O(m n)。
解法二:逐边长枚举 + 前缀和(对照)
边长从 1 到 min(m,n) 递增枚举,对每个边长检查所有位置,第一个不满足即停止。
复杂度:时间 O(min(m,n) * m * n),空间 O(m*n)。
代码实现
java
public class Solution {
public int maxSideLength(int[][] mat, int threshold) {
int m = mat.length, n = mat[0].length;
int[][] pre = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
pre[i + 1][j + 1] = pre[i + 1][j] + pre[i][j + 1] - pre[i][j] + mat[i][j];
}
}
int lo = 0, hi = Math.min(m, n);
while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2;
if (exists(pre, m, n, mid, threshold)) {
lo = mid;
} else {
hi = mid - 1;
}
}
return lo;
}
private boolean exists(int[][] pre, int m, int n, int k, int threshold) {
if (k == 0) {
return true;
}
for (int i = k; i <= m; i++) {
for (int j = k; j <= n; j++) {
int sum = pre[i][j] - pre[i - k][j] - pre[i][j - k] + pre[i - k][j - k];
if (sum <= threshold) {
return true;
}
}
}
return false;
}
public int maxSideLengthEnum(int[][] mat, int threshold) {
int m = mat.length, n = mat[0].length;
int[][] pre = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
pre[i + 1][j + 1] = pre[i + 1][j] + pre[i][j + 1] - pre[i][j] + mat[i][j];
}
}
int best = 0;
for (int k = 1; k <= Math.min(m, n); k++) {
boolean found = false;
for (int i = k; i <= m && !found; i++) {
for (int j = k; j <= n; j++) {
int sum = pre[i][j] - pre[i - k][j] - pre[i][j - k] + pre[i - k][j - k];
if (sum <= threshold) {
best = k;
found = true;
break;
}
}
}
if (!found) {
break;
}
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] m1 = {{1, 1, 3, 2, 4, 3, 2}, {1, 1, 3, 2, 4, 3, 2}, {1, 1, 3, 2, 4, 3, 2}};
System.out.println(s.maxSideLength(m1, 4)); // 2
System.out.println(s.maxSideLengthEnum(m1, 4)); // 2
int[][] m2 = {{2, 2, 2, 2, 2}, {2, 2, 2, 2, 2}, {2, 2, 2, 2, 2}, {2, 2, 2, 2, 2}, {2, 2, 2, 2, 2}};
System.out.println(s.maxSideLength(m2, 1)); // 0
System.out.println(s.maxSideLengthEnum(m2, 1)); // 0
int[][] m3 = {{1, 1}, {1, 1}};
System.out.println(s.maxSideLength(m3, 4)); // 2
System.out.println(s.maxSideLengthEnum(m3, 4)); // 2
System.out.println(s.maxSideLength(m3, 0)); // 0
System.out.println(s.maxSideLengthEnum(m3, 0)); // 0
}
}
复杂度分析
复杂度:时间 O(mn + min(m,n) * m * n),空间 O(m n)。
复杂度:时间 O(min(m,n) * m * n),空间 O(m*n)。
1293. Shortest Path in a Grid with Obstacles Elimination(网格中的最短路径)
题目描述
给你一个 m * n 的网格,其中每个单元格不是 0(空)就是 1(障碍物)。每一步,您都可以在空白单元格中上、下、左、右移动。
如果您 最多 可以消除 k 个障碍物,请找出从左上角 (0, 0) 到右下角 (m-1, n-1) 的最短路径,并返回通过该路径所需的步数。如果找不到这样的路径,则返回 -1 。
示例 1
输入: grid = [[0,0,0],[1,1,0],[0,0,0],[0,1,1],[0,0,0]], k = 1
输出:6
解释:
不消除任何障碍的最短路径是 10。
消除位置 (3,2) 处的障碍后,最短路径是 6 。该路径是 `(0,0) -> (0,1) -> (0,2) -> (1,2) -> (2,2) -> (3,2) -> (4,2)`.
示例 2
输入:grid = [[0,1,1],[1,1,1],[1,0,0]], k = 1
输出:-1
解释:我们至少需要消除两个障碍才能找到这样的路径。
提示:
grid.length == mgrid[0].length == n1 <= m, n <= 401 <= k <= m*ngrid[i][j]是0或1grid[0][0] == grid[m-1][n-1] == 0
解题思路
解法一:三维 BFS(主提交版本)
状态 (r, c, k2) 表示到达 (r,c) 时剩余可消除次数为 k2。
visitedrck2 去重,BFS 层数即最短步数。移动到障碍时消耗一次消除机会。
复杂度:时间 O(mn k),空间 O(mn k)。
解法二:分层剪枝 BFS(对照,二维 visited + 最优剩余)
用 distObstaclerc 记录走到 (r,c) 时"已消除最少障碍数",
若新路径的已消除数 >= 历史值则剪枝,否则更新并继续。
复杂度:时间 O(mnk) 最坏,空间 O(m*n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
public class Solution {
public int shortestPath(int[][] grid, int k) {
int m = grid.length, n = grid[0].length;
if (m == 1 && n == 1) {
return 0;
}
// 上限:若 k >= 可消除总量(最短路长 m+n-2 内最多遇障碍 m+n-3),直接走曼哈顿长度
if (k >= m + n - 3) {
return m + n - 2;
}
boolean[][][] visited = new boolean[m][n][k + 1];
// state: r, c, used(已消除数)
Queue<int[]> q = new ArrayDeque<>();
q.offer(new int[]{0, 0, 0});
visited[0][0][0] = true;
int[] dr = {1, -1, 0, 0};
int[] dc = {0, 0, 1, -1};
int steps = 0;
while (!q.isEmpty()) {
steps++;
int size = q.size();
for (int t = 0; t < size; t++) {
int[] cur = q.poll();
for (int d = 0; d < 4; d++) {
int nr = cur[0] + dr[d], nc = cur[1] + dc[d];
if (nr < 0 || nr >= m || nc < 0 || nc >= n) {
continue;
}
int used = cur[2] + grid[nr][nc]; // 障碍则多记一次消除
if (used > k || visited[nr][nc][used]) {
continue;
}
if (nr == m - 1 && nc == n - 1) {
return steps;
}
visited[nr][nc][used] = true;
q.offer(new int[]{nr, nc, used});
}
}
}
return -1;
}
public int shortestPathMinElim(int[][] grid, int k) {
int m = grid.length, n = grid[0].length;
if (m == 1 && n == 1) {
return 0;
}
int[][] minUsed = new int[m][n];
for (int i = 0; i < m; i++) {
Arrays.fill(minUsed[i], Integer.MAX_VALUE);
}
Queue<int[]> q = new ArrayDeque<>();
q.offer(new int[]{0, 0, 0});
minUsed[0][0] = 0;
int[] dr = {1, -1, 0, 0};
int[] dc = {0, 0, 1, -1};
int steps = 0;
while (!q.isEmpty()) {
steps++;
int size = q.size();
for (int t = 0; t < size; t++) {
int[] cur = q.poll();
for (int d = 0; d < 4; d++) {
int nr = cur[0] + dr[d], nc = cur[1] + dc[d];
if (nr < 0 || nr >= m || nc < 0 || nc >= n) {
continue;
}
int used = cur[2] + grid[nr][nc];
if (used > k || used >= minUsed[nr][nc]) {
continue;
}
minUsed[nr][nc] = used;
if (nr == m - 1 && nc == n - 1) {
return steps;
}
q.offer(new int[]{nr, nc, used});
}
}
}
return -1;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] g1 = {{0, 0, 0}, {1, 1, 0}, {0, 0, 0}, {0, 1, 1}, {0, 0, 0}};
System.out.println(s.shortestPath(g1, 1)); // 6
System.out.println(s.shortestPathMinElim(g1, 1)); // 6
int[][] g2 = {{0, 1, 1}, {1, 1, 1}, {1, 0, 0}};
System.out.println(s.shortestPath(g2, 1)); // -1
System.out.println(s.shortestPathMinElim(g2, 1)); // -1
int[][] g3 = {{0}};
System.out.println(s.shortestPath(g3, 1)); // 0
System.out.println(s.shortestPathMinElim(g3, 1)); // 0
int[][] g4 = {{0, 0}, {0, 0}};
System.out.println(s.shortestPath(g4, 1)); // 2
System.out.println(s.shortestPathMinElim(g4, 1)); // 2
int[][] g5 = {{0, 1}, {1, 0}};
System.out.println(s.shortestPath(g5, 1)); // 2
System.out.println(s.shortestPathMinElim(g5, 1)); // 2
}
}
复杂度分析
复杂度:时间 O(mn k),空间 O(mn k)。
复杂度:时间 O(mnk) 最坏,空间 O(m*n)。
1294. Weather Type in Each Country(不同国家的天气类型 🔒)
题目类型:数据库(SQL)
题目描述
表:Countries
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| country_id | int |
| country_name | varchar |
+---------------+---------+
country_id 是这张表的主键(具有唯一值的列)。
该表的每行有 country_id 和 country_name 两列。
表:Weather
+---------------+------+
| Column Name | Type |
+---------------+------+
| country_id | int |
| weather_state | int |
| day | date |
+---------------+------+
(country_id, day) 是该表的复合主键(具有唯一值的列)。
该表的每一行记录了某个国家某一天的天气情况。
编写解决方案找到表中每个国家在 2019 年 11 月的天气类型。
天气类型的定义如下:
- 当 `weather_state` 的平均值小于或等于 `15` 返回 **Cold**,
- 当 `weather_state` 的平均值大于或等于 `25` 返回 **Hot**,
- 否则返回 **Warm**。
以 任意顺序 返回你的查询结果。
返回结果格式如下所示:
示例 1:
**输入:**
Countries table:
+------------+--------------+
| country_id | country_name |
+------------+--------------+
| 2 | USA |
| 3 | Australia |
| 7 | Peru |
| 5 | China |
| 8 | Morocco |
| 9 | Spain |
+------------+--------------+
Weather table:
+------------+---------------+------------+
| country_id | weather_state | day |
+------------+---------------+------------+
| 2 | 15 | 2019-11-01 |
| 2 | 12 | 2019-10-28 |
| 2 | 12 | 2019-10-27 |
| 3 | -2 | 2019-11-10 |
| 3 | 0 | 2019-11-11 |
| 3 | 3 | 2019-11-12 |
| 5 | 16 | 2019-11-07 |
| 5 | 18 | 2019-11-09 |
| 5 | 21 | 2019-11-23 |
| 7 | 25 | 2019-11-28 |
| 7 | 22 | 2019-12-01 |
| 7 | 20 | 2019-12-02 |
| 8 | 25 | 2019-11-05 |
| 8 | 27 | 2019-11-15 |
| 8 | 31 | 2019-11-25 |
| 9 | 7 | 2019-10-23 |
| 9 | 3 | 2019-12-23 |
+------------+---------------+------------+
**输出:**
+--------------+--------------+
| country_name | weather_type |
+--------------+--------------+
| USA | Cold |
| Austraila | Cold |
| Peru | Hot |
| China | Warm |
| Morocco | Hot |
+--------------+--------------+
**解释:**
USA 11 月的平均 weather_state 为 (15) / 1 = 15 所以天气类型为 Cold。
Australia 11 月的平均 weather_state 为 (-2 + 0 + 3) / 3 = 0.333 所以天气类型为 Cold。
Peru 11 月的平均 weather_state 为 (25) / 1 = 25 所以天气类型为 Hot。
China 11 月的平均 weather_state 为 (16 + 18 + 21) / 3 = 18.333 所以天气类型为 Warm。
Morocco 11 月的平均 weather_state 为 (25 + 27 + 31) / 3 = 27.667 所以天气类型为 Hot。
我们并不知道 Spain 在 11 月的 weather_state 情况所以无需将他包含在结果中。
解题思路
思考
按国家统计 2019 2019 2019 年 11 11 11 月平均气温并分成 Cold/Warm/Hot。连接天气与国家,过滤该月,按国家求 A V G AVG AVG,再用 C A S E CASE CASE 分箱。
考察JOIN + 日期过滤 + CASE 分组:取 2019 年 11 月的天气记录按国家分组,用 AVG(weather_state) 与阈值 15/25 判定 Cold/Warm/Hot。
代码实现
sql
# Write your MySQL query statement below
SELECT
country_name,
CASE
WHEN AVG(weather_state) <= 15 THEN 'Cold'
WHEN AVG(weather_state) >= 25 THEN 'Hot'
ELSE 'Warm'
END AS weather_type
FROM
Weather AS w
JOIN Countries USING (country_id)
WHERE DATE_FORMAT(day, '%Y-%m') = '2019-11'
GROUP BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1295. Find Numbers with Even Number of Digits(统计位数为偶数的数字)
题目描述
给你一个整数数组 nums,请你返回其中包含 偶数 个数位的数字的个数。
示例 1
输入:nums = [12,345,2,6,7896]
输出:2
解释:
12 是 2 位数字(位数为偶数)
345 是 3 位数字(位数为奇数)
2 是 1 位数字(位数为奇数)
6 是 1 位数字 位数为奇数)
7896 是 4 位数字(位数为偶数)
因此只有 12 和 7896 是位数为偶数的数字
示例 2
输入:nums = [555,901,482,1771]
输出:1
解释:
只有 1771 是位数为偶数的数字。
提示:
1 <= nums.length <= 5001 5
解题思路
解法一:位数计数(主提交版本)
循环除以 10 统计位数,位数为偶数则计数加一。
复杂度:时间 O(n * log(max)),空间 O(1)。
解法二:范围判断/对数法(对照)
偶数位数的数字范围:1099、10009999、100000 等。
用字符串长度或区间判断:位数偶数 ⇔ 数值 >= 10^k 且 < 10^(k+1),k 为奇数。
本实现用 String.length() 判奇偶,直观简洁。复杂度:时间 O(n * d),空间 O(d)。
代码实现
java
public class Solution {
public int findNumbers(int[] nums) {
int count = 0;
for (int x : nums) {
int digits = 0;
while (x > 0) {
digits++;
x /= 10;
}
if (digits % 2 == 0) {
count++;
}
}
return count;
}
public int findNumbersStr(int[] nums) {
int count = 0;
for (int x : nums) {
if (Integer.toString(x).length() % 2 == 0) {
count++;
}
}
return count;
}
public int findNumbersRange(int[] nums) {
int count = 0;
for (int x : nums) {
if ((x >= 10 && x <= 99) || (x >= 1000 && x <= 9999) || x == 100000) {
count++;
}
}
return count;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.findNumbers(new int[]{12, 345, 2, 6, 7896})); // 2
System.out.println(s.findNumbersStr(new int[]{12, 345, 2, 6, 7896})); // 2
System.out.println(s.findNumbersRange(new int[]{12, 345, 2, 6, 7896}));// 2
System.out.println(s.findNumbers(new int[]{555, 901, 482, 1771})); // 1
System.out.println(s.findNumbersStr(new int[]{555, 901, 482, 1771})); // 1
System.out.println(s.findNumbersRange(new int[]{555, 901, 482, 1771}));// 1
System.out.println(s.findNumbers(new int[]{1})); // 0
System.out.println(s.findNumbersStr(new int[]{1})); // 0
System.out.println(s.findNumbers(new int[]{100000})); // 1
System.out.println(s.findNumbersStr(new int[]{100000})); // 1
System.out.println(s.findNumbersRange(new int[]{100000})); // 1
}
}
复杂度分析
复杂度:时间 O(n * log(max)),空间 O(1)。
本实现用 String.length() 判奇偶,直观简洁。复杂度:时间 O(n * d),空间 O(d)。
1296. Divide Array in Sets of K Consecutive Numbers(划分数组为连续数字的集合)
题目描述
给你一个整数数组 nums 和一个正整数 k,请你判断是否可以把这个数组划分成一些由 k 个连续数字组成的集合。
如果可以,请返回 true;否则,返回 false。
示例 1
输入:nums = [1,2,3,3,4,4,5,6], k = 4
输出:true
解释:数组可以分成 [1,2,3,4] 和 [3,4,5,6]。
示例 2
输入:nums = [3,2,1,2,3,4,3,4,5,9,10,11], k = 3
输出:true
解释:数组可以分成 [1,2,3] , [2,3,4] , [3,4,5] 和 [9,10,11]。
示例 4
输入:nums = [1,2,3,4], k = 3
输出:false
解释:数组不能分成几个大小为 3 的子数组。
提示:
1 51 9
注意:此题目与 846 重复:https://leetcode.cn/problems/hand-of-straights/
解题思路
解法一:排序 + 贪心构造(主提交版本)
先排序,再用计数数组/哈希统计频次,从小到大取每个数:
若当前数还有剩余,则尝试构造以它开始的长度为 k 的连续组,
对组内每个数扣减频次,若某个数不够则失败。
复杂度:时间 O(n log n),空间 O(n)。
解法二:TreeMap 频次贪心(对照)
用 TreeMap 维护数字->频次的有序映射,每次取出最小值并连续扣减 k 个。
复杂度:时间 O(n log n),空间 O(n)。
代码实现
java
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Solution {
public boolean isPossibleDivide(int[] nums, int k) {
int n = nums.length;
if (n % k != 0) {
return false;
}
Arrays.sort(nums);
Map<Integer, Integer> count = new HashMap<>();
for (int x : nums) {
count.put(x, count.getOrDefault(x, 0) + 1);
}
for (int x : nums) {
if (count.get(x) == 0) {
continue;
}
int need = count.get(x);
// 以 x 为起点构造一组,组内每个数至少用 need 个才不至于浪费
for (int v = x; v < x + k; v++) {
Integer c = count.get(v);
if (c == null || c < need) {
return false;
}
count.put(v, c - need);
}
}
return true;
}
public boolean isPossibleDivideTree(int[] nums, int k) {
int n = nums.length;
if (n % k != 0) {
return false;
}
TreeMap<Integer, Integer> map = new TreeMap<>();
for (int x : nums) {
map.put(x, map.getOrDefault(x, 0) + 1);
}
while (!map.isEmpty()) {
int first = map.firstKey();
for (int v = first; v < first + k; v++) {
Integer c = map.get(v);
if (c == null || c == 0) {
return false;
}
if (c == 1) {
map.remove(v);
} else {
map.put(v, c - 1);
}
}
}
return true;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.isPossibleDivide(new int[]{1, 2, 3, 3, 4, 4, 5, 6}, 4)); // true
System.out.println(s.isPossibleDivideTree(new int[]{1, 2, 3, 3, 4, 4, 5, 6}, 4)); // true
System.out.println(s.isPossibleDivide(new int[]{3, 2, 1, 2, 3, 4, 3, 4, 5, 9, 10, 11}, 3)); // true
System.out.println(s.isPossibleDivideTree(new int[]{3, 2, 1, 2, 3, 4, 3, 4, 5, 9, 10, 11}, 3)); // true
System.out.println(s.isPossibleDivide(new int[]{1, 2, 3, 4}, 3)); // false
System.out.println(s.isPossibleDivideTree(new int[]{1, 2, 3, 4}, 3)); // false
System.out.println(s.isPossibleDivide(new int[]{1, 2, 3}, 1)); // true
System.out.println(s.isPossibleDivideTree(new int[]{1, 2, 3}, 1)); // true
System.out.println(s.isPossibleDivide(new int[]{1, 1, 2, 2, 3, 3}, 3)); // true ([1,2,3] x2)
System.out.println(s.isPossibleDivideTree(new int[]{1, 1, 2, 2, 3, 3}, 3)); // true
}
}
复杂度分析
复杂度:时间 O(n log n),空间 O(n)。
1297. Maximum Number of Occurrences of a Substring(子串的最大出现次数)
题目描述
给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数:
- 子串中不同字母的数目必须小于等于
maxLetters。 - 子串的长度必须大于等于
minSize且小于等于maxSize。
示例 1
输入:s = "aababcaab", maxLetters = 2, minSize = 3, maxSize = 4
输出:2
解释:子串 "aab" 在原字符串中出现了 2 次。
它满足所有的要求:2 个不同的字母,长度为 3 (在 minSize 和 maxSize 范围内)。
示例 2
输入:s = "aaaa", maxLetters = 1, minSize = 3, maxSize = 3
输出:2
解释:子串 "aaa" 在原字符串中出现了 2 次,且它们有重叠部分。
示例 3
输入:s = "aabcabcab", maxLetters = 2, minSize = 2, maxSize = 3
输出:3
示例 4
输入:s = "abcde", maxLetters = 2, minSize = 3, maxSize = 3
输出:0
提示:
1 <= s.length <= 10^51 <= maxLetters <= 261 <= minSize <= maxSize <= min(26, s.length)s只包含小写英文字母。
解题思路
解法一:定长滑动窗口计数(主提交版本)
关键观察:若长度 L > minSize 的子串是可行高频候选,则它的前缀(长 minSize)也满足
maxLetters 限制且出现次数不少于它。因此只需统计所有长度为 minSize 的子串。
用 26 长度频次数组维护窗口内不同字母数 <= maxLetters 的计数,以子串为 key 累计出现次数。
复杂度:时间 O(n*26)(字符频次数组 O(26) 维护),空间 O(n)。
解法二:暴力枚举全部合法长度(对照)
对长度从 minSize 到 maxSize(上限 minSize+maxLetters 已足够)枚举窗口并统计。
复杂度:时间 O(n * (maxSize - minSize + 1) * 26),空间 O(n)。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int maxFreq(String s, int maxLetters, int minSize, int maxSize) {
Map<String, Integer> count = new HashMap<>();
int n = s.length();
int[] freq = new int[26];
int distinct = 0;
for (int i = 0; i < n; i++) {
int in = s.charAt(i) - 'a';
if (freq[in]++ == 0) {
distinct++;
}
if (i >= minSize) {
int out = s.charAt(i - minSize) - 'a';
if (--freq[out] == 0) {
distinct--;
}
}
if (i >= minSize - 1 && distinct <= maxLetters) {
String sub = s.substring(i - minSize + 1, i + 1);
count.put(sub, count.getOrDefault(sub, 0) + 1);
}
}
int res = 0;
for (int v : count.values()) {
res = Math.max(res, v);
}
return res;
}
public int maxFreqEnum(String s, int maxLetters, int minSize, int maxSize) {
Map<String, Integer> count = new HashMap<>();
int n = s.length();
// 只需枚举 minSize..minSize+maxLetters(更长的可行子串必有同频的前缀)
int hi = Math.min(maxSize, minSize + maxLetters);
for (int len = minSize; len <= hi; len++) {
int[] freq = new int[26];
int distinct = 0;
for (int i = 0; i < n; i++) {
int in = s.charAt(i) - 'a';
if (freq[in]++ == 0) {
distinct++;
}
if (i >= len) {
int out = s.charAt(i - len) - 'a';
if (--freq[out] == 0) {
distinct--;
}
}
if (i >= len - 1 && distinct <= maxLetters) {
String sub = s.substring(i - len + 1, i + 1);
count.put(sub, count.getOrDefault(sub, 0) + 1);
}
}
}
int res = 0;
for (int v : count.values()) {
res = Math.max(res, v);
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxFreq("aababcaab", 2, 3, 4)); // 2
System.out.println(s.maxFreqEnum("aababcaab", 2, 3, 4)); // 2
System.out.println(s.maxFreq("aaaa", 1, 3, 3)); // 2
System.out.println(s.maxFreqEnum("aaaa", 1, 3, 3)); // 2
System.out.println(s.maxFreq("aabcabcab", 2, 2, 3)); // 3
System.out.println(s.maxFreqEnum("aabcabcab", 2, 2, 3)); // 3
System.out.println(s.maxFreq("abcde", 2, 3, 3)); // 0
System.out.println(s.maxFreqEnum("abcde", 2, 3, 3)); // 0
}
}
复杂度分析
复杂度:时间 O(n*26)(字符频次数组 O(26) 维护),空间 O(n)。
复杂度:时间 O(n * (maxSize - minSize + 1) * 26),空间 O(n)。
1298. Maximum Candies You Can Get From Boxes(你能从盒子里获得的最大糖果数)
题目描述
给你 n 个盒子,每个盒子的格式为 [status, candies, keys, containedBoxes] ,其中:
- 状态字
status[i]:整数,如果box[i]是开的,那么是 1 ,否则是 0 。 - 糖果数
candies[i]: 整数,表示box[i]中糖果的数目。 - 钥匙
keys[i]:数组,表示你打开box[i]后,可以得到一些盒子的钥匙,每个元素分别为该钥匙对应盒子的下标。 - 内含的盒子
containedBoxes[i]:整数,表示放在box[i]里的盒子所对应的下标。
给你一个整数数组initialBoxes,包含你最初拥有的盒子。你可以拿走每个 已打开盒子 里的所有糖果,并且可以使用其中的钥匙去开启新的盒子,并且可以使用在其中发现的其他盒子。
请你按照上述规则,返回可以获得糖果的 最大数目 。
示例 1
输入:status = [1,0,1,0], candies = [7,5,4,100], keys = [[],[],[1],[]], containedBoxes = [[1,2],[3],[],[]], initialBoxes = [0]
输出:16
解释:
一开始你有盒子 0 。你将获得它里面的 7 个糖果和盒子 1 和 2。
盒子 1 目前状态是关闭的,而且你还没有对应它的钥匙。所以你将会打开盒子 2 ,并得到里面的 4 个糖果和盒子 1 的钥匙。
在盒子 1 中,你会获得 5 个糖果和盒子 3 ,但是你没法获得盒子 3 的钥匙所以盒子 3 会保持关闭状态。
你总共可以获得的糖果数目 = 7 + 4 + 5 = 16 个。
示例 2
输入:status = [1,0,0,0,0,0], candies = [1,1,1,1,1,1], keys = [[1,2,3,4,5],[],[],[],[],[]], containedBoxes = [[1,2,3,4,5],[],[],[],[],[]], initialBoxes = [0]
输出:6
解释:
你一开始拥有盒子 0 。打开它你可以找到盒子 1,2,3,4,5 和它们对应的钥匙。
打开这些盒子,你将获得所有盒子的糖果,所以总糖果数为 6 个。
示例 3
输入:status = [1,1,1], candies = [100,1,100], keys = [[],[0,2],[]], containedBoxes = [[],[],[]], initialBoxes = [1]
输出:1
示例 4
输入:status = [1], candies = [100], keys = [[]], containedBoxes = [[]], initialBoxes = []
输出:0
示例 5
输入:status = [1,1,1], candies = [2,3,2], keys = [[],[],[]], containedBoxes = [[],[],[]], initialBoxes = [2,1,0]
输出:7
提示:
1 <= status.length <= 1000status.length == candies.length == keys.length == containedBoxes.length == nstatus[i]要么是0要么是1。1 <= candies[i] <= 10000 <= keys[i].length <= status.length0 <= keys[i][j] < status.lengthkeys[i]中的值都是互不相同的。0 <= containedBoxes[i].length <= status.length0 <= containedBoxes[i][j] < status.lengthcontainedBoxes[i]中的值都是互不相同的。- 每个盒子最多被一个盒子包含。
0 <= initialBoxes.length <= status.length0 <= initialBoxes[i] < status.length
解题思路
解法一:BFS 开放集 + 钥匙集(主提交版本)
维护"已拥有的盒子"集合(含初始盒)与"已拥有的钥匙"集合。
循环处理:对每个拥有的盒子,若已开跳过;若未开且状态为开或拥有其钥匙则打开------累加糖果、
收集内部盒子与钥匙。打开某个盒子后可能解锁其他等待中的盒子,故重复扫描直到无进展。
复杂度:时间 O(n^2) 最坏,空间 O(n)。
解法二:队列驱动 + 状态标记(对照)
用队列存储"新获得的盒子",数组标记是否拥有盒子/钥匙/已开。
打开盒子产生的钥匙与内部盒子如果能使某个当前拥有的盒子满足开启条件则入队,
直到队列为空。复杂度:时间 O(n^2) 最坏(由队列重复入队导致),空间 O(n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
public class Solution {
public int maxCandies(int[] status, int[] candies, int[][] keys,
int[][] containedBoxes, int[] initialBoxes) {
int n = status.length;
boolean[] hasBox = new boolean[n];
boolean[] hasKey = new boolean[n];
boolean[] opened = new boolean[n];
Queue<Integer> q = new ArrayDeque<>();
for (int b : initialBoxes) {
hasBox[b] = true;
q.offer(b);
}
int total = 0;
// 不断打开新盒子直到无进展
boolean progress = true;
while (progress) {
progress = false;
int size = q.size();
for (int t = 0; t < size; t++) {
int b = q.poll();
if (opened[b]) {
continue;
}
boolean canOpen = status[b] == 1 || hasKey[b];
if (!canOpen) {
q.offer(b); // 暂不可开,放回等待
continue;
}
opened[b] = true;
progress = true;
total += candies[b];
for (int k : keys[b]) {
hasKey[k] = true;
}
for (int nb : containedBoxes[b]) {
if (!hasBox[nb]) {
hasBox[nb] = true;
q.offer(nb);
}
}
}
}
return total;
}
public int maxCandiesQueue(int[] status, int[] candies, int[][] keys,
int[][] containedBoxes, int[] initialBoxes) {
int n = status.length;
boolean[] hasBox = new boolean[n];
boolean[] hasKey = new boolean[n];
boolean[] opened = new boolean[n];
Queue<Integer> pending = new ArrayDeque<>(); // 拥有但尚未打开的盒子
for (int b : initialBoxes) {
hasBox[b] = true;
pending.offer(b);
}
int total = 0;
// 多轮处理:每轮遍历当前队列,能打开的立即打开;不能打开的先放回。
// 用 progress 检测"本轮是否有进展",若无进展(剩余盒子全都打不开)则退出,避免死循环。
boolean progress = true;
while (progress) {
progress = false;
int size = pending.size();
for (int t = 0; t < size; t++) {
int b = pending.poll();
if (opened[b]) {
continue;
}
if (status[b] != 1 && !hasKey[b]) {
pending.offer(b); // 放回等待后续钥匙出现
} else {
opened[b] = true;
progress = true;
total += candies[b];
for (int k : keys[b]) {
hasKey[k] = true;
}
for (int nb : containedBoxes[b]) {
if (!hasBox[nb]) {
hasBox[nb] = true;
pending.offer(nb);
}
}
}
}
}
return total;
}
public static void main(String[] args) {
Solution s = new Solution();
// 示例 1
System.out.println(s.maxCandies(new int[]{1, 0, 1, 0}, new int[]{7, 5, 4, 100},
new int[][]{{}, {}, {1}, {}}, new int[][]{{1, 2}, {3}, {}, {}}, new int[]{0})); // 16
System.out.println(s.maxCandiesQueue(new int[]{1, 0, 1, 0}, new int[]{7, 5, 4, 100},
new int[][]{{}, {}, {1}, {}}, new int[][]{{1, 2}, {3}, {}, {}}, new int[]{0})); // 16
// 示例 2
System.out.println(s.maxCandies(new int[]{1, 0, 0, 0, 0, 0}, new int[]{1, 1, 1, 1, 1, 1},
new int[][]{{1, 2, 3, 4, 5}, {}, {}, {}, {}, {}},
new int[][]{{1, 2, 3, 4, 5}, {}, {}, {}, {}, {}}, new int[]{0})); // 6
System.out.println(s.maxCandiesQueue(new int[]{1, 0, 0, 0, 0, 0}, new int[]{1, 1, 1, 1, 1, 1},
new int[][]{{1, 2, 3, 4, 5}, {}, {}, {}, {}, {}},
new int[][]{{1, 2, 3, 4, 5}, {}, {}, {}, {}, {}}, new int[]{0})); // 6
// 示例 3
System.out.println(s.maxCandies(new int[]{1, 1, 1}, new int[]{100, 1, 100},
new int[][]{{}, {0, 2}, {}}, new int[][]{{}, {}, {}}, new int[]{1})); // 1
System.out.println(s.maxCandiesQueue(new int[]{1, 1, 1}, new int[]{100, 1, 100},
new int[][]{{}, {0, 2}, {}}, new int[][]{{}, {}, {}}, new int[]{1})); // 1
// 示例 4
System.out.println(s.maxCandies(new int[]{1}, new int[]{100}, new int[][]{{}},
new int[][]{{}}, new int[]{})); // 0
System.out.println(s.maxCandiesQueue(new int[]{1}, new int[]{100}, new int[][]{{}},
new int[][]{{}}, new int[]{})); // 0
}
}
复杂度分析
复杂度:时间 O(n^2) 最坏,空间 O(n)。
直到队列为空。复杂度:时间 O(n^2) 最坏(由队列重复入队导致),空间 O(n)。
1299. Replace Elements with Greatest Element on Right Side
题目描述
给你一个数组 arr ,请你将每个元素用它右边最大的元素替换,如果是最后一个元素,用 -1 替换。
完成所有替换操作后,请你返回这个数组。
示例 1
输入:arr = [17,18,5,4,6,1]
输出:[18,6,6,6,1,-1]
解释:
- 下标 0 的元素 --> 右侧最大元素是下标 1 的元素 (18)
- 下标 1 的元素 --> 右侧最大元素是下标 4 的元素 (6)
- 下标 2 的元素 --> 右侧最大元素是下标 4 的元素 (6)
- 下标 3 的元素 --> 右侧最大元素是下标 4 的元素 (6)
- 下标 4 的元素 --> 右侧最大元素是下标 5 的元素 (1)
- 下标 5 的元素 --> 右侧没有其他元素,替换为 -1
示例 2
输入:arr = [400]
输出:[-1]
解释:下标 0 的元素右侧没有其他元素。
提示:
1 41 5
解题思路
解法一:逆序遍历(主提交版本)
从右向左扫描,记录右侧最大值 rightMax;把当前值赋值给结果对应位后再用当前值更新 rightMax。
最后一个元素的结果为 -1。复杂度:时间 O(n),空间 O(n)(存储结果)。
解法二:原地修改(对照)
不另开结果数组:先保存当前值,把右侧最大值写入当前位置,再更新右侧最大值。
复杂度:时间 O(n),空间 O(1)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int[] replaceElements(int[] arr) {
int n = arr.length;
int[] res = new int[n];
int rightMax = -1;
for (int i = n - 1; i >= 0; i--) {
res[i] = rightMax;
rightMax = Math.max(rightMax, arr[i]);
}
return res;
}
public int[] replaceElementsInPlace(int[] arr) {
int n = arr.length;
int rightMax = -1;
for (int i = n - 1; i >= 0; i--) {
int cur = arr[i];
arr[i] = rightMax;
rightMax = Math.max(rightMax, cur);
}
return arr;
}
private String fmt(int[] a) {
return Arrays.toString(a);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.fmt(s.replaceElements(new int[]{17, 18, 5, 4, 6, 1})));
System.out.println(s.fmt(s.replaceElements(new int[]{400})));
System.out.println(s.fmt(s.replaceElements(new int[]{1, 2, 3, 4, 5})));
System.out.println(s.fmt(s.replaceElementsInPlace(new int[]{17, 18, 5, 4, 6, 1})));
System.out.println(s.fmt(s.replaceElementsInPlace(new int[]{400})));
}
}
复杂度分析
最后一个元素的结果为 -1。复杂度:时间 O(n),空间 O(n)(存储结果)。
复杂度:时间 O(n),空间 O(1)。
1300. Sum of Mutated Array Closest to Target(转变数组后最接近目标值的数组和)
题目描述
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
示例 1
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。
示例 2
输入:arr = [2,3,5], target = 10
输出:5
示例 3
输入:arr = [60864,25176,27249,21296,20204], target = 56803
输出:11361
提示:
1 <= arr.length <= 10^41 <= arr[i], target <= 10^5
解题思路
解法一:排序 + 前缀和 + 二分(主提交版本)
对 value 求"转变后数组和":小于等于 value 的原样保留,大于 value 的全变为 value。
排序后用前缀和 O(1) 计算:sum = preidx + value * (n - idx),idx 为二分出的首个 > value 的位置。
该函数随 value 单调不减,因此可在 0, max(arr) 上二分出"和恰好 >= target"的临界值,
再比较临界值与其前一个值哪个更接近 target,取更小者(平局取最小值)。
复杂度:时间 O(n log n + log(max)log n),空间 O(n)。
解法二:值域枚举(对照)
直接在 0, max(arr) 上枚举 value 计算和并跟踪最优。复杂度:时间 O(maxn) 最坏,空间 O(1)。
代码实现
java
import java.util.Arrays;
public class Solution {
public int findBestValue(int[] arr, int target) {
Arrays.sort(arr);
int n = arr.length;
long[] pre = new long[n + 1];
for (int i = 0; i < n; i++) {
pre[i + 1] = pre[i] + arr[i];
}
int max = arr[n - 1];
// 全部值不变时的和
if (pre[n] <= target) {
return max; // 全部保留仍不超过 target,取最大即可(且 value 越大和越大越接近)
}
int lo = 0, hi = max;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
long sum = calc(arr, pre, mid);
if (sum >= target) {
hi = mid;
} else {
lo = mid + 1;
}
}
long s1 = calc(arr, pre, lo);
long s2 = calc(arr, pre, lo - 1);
if (Math.abs(s1 - target) < Math.abs(s2 - target)) {
return lo;
}
return lo - 1; // 平局取较小 value
}
private long calc(int[] arr, long[] pre, int value) {
int idx = upperBound(arr, value);
return pre[idx] + (long) value * (arr.length - idx);
}
private int upperBound(int[] arr, int v) {
int lo = 0, hi = arr.length;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid] <= v) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
public int findBestValueEnum(int[] arr, int target) {
int max = 0;
long total = 0;
for (int x : arr) {
max = Math.max(max, x);
total += x;
}
if (total <= target) {
return max;
}
int best = 0;
long bestDiff = Long.MAX_VALUE;
for (int v = 0; v <= max; v++) {
long sum = 0;
for (int x : arr) {
sum += Math.min(x, v);
}
long diff = Math.abs(sum - target);
if (diff < bestDiff) {
bestDiff = diff;
best = v;
}
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.findBestValue(new int[]{4, 9, 3}, 10)); // 3
System.out.println(s.findBestValueEnum(new int[]{4, 9, 3}, 10)); // 3
System.out.println(s.findBestValue(new int[]{2, 3, 5}, 10)); // 5
System.out.println(s.findBestValueEnum(new int[]{2, 3, 5}, 10)); // 5
System.out.println(s.findBestValue(new int[]{60864, 25176, 27249, 21296, 20204}, 56803)); // 11361
System.out.println(s.findBestValueEnum(new int[]{60864, 25176, 27249, 21296, 20204}, 56803)); // 11361
System.out.println(s.findBestValue(new int[]{1, 2, 3}, 2)); // 1(value=1 时和为 3,diff=1;value=0 时和 0 diff=2 -> 1)
System.out.println(s.findBestValueEnum(new int[]{1, 2, 3}, 2)); // 1
System.out.println(s.findBestValue(new int[]{1}, 100)); // 1
System.out.println(s.findBestValueEnum(new int[]{1}, 100)); // 1
}
}
复杂度分析
复杂度:时间 O(n log n + log(max)log n),空间 O(n)。
直接在 0, max(arr) 上枚举 value 计算和并跟踪最优。复杂度:时间 O(maxn) 最坏,空间 O(1)。