LeetCode 1181-1200. 题解合集
文章目录
- [LeetCode 1181-1200. 题解合集](#LeetCode 1181-1200. 题解合集)
-
- [1181. Before and After Puzzle(前后拼接 🔒)](#1181. Before and After Puzzle(前后拼接 🔒))
- [1182. Shortest Distance to Target Color(与目标颜色间的最短距离 🔒)](#1182. Shortest Distance to Target Color(与目标颜色间的最短距离 🔒))
- [1183. Maximum Number of Ones(矩阵中 1 的最大数量 🔒)](#1183. Maximum Number of Ones(矩阵中 1 的最大数量 🔒))
- [1184. Distance Between Bus Stops(公交站间的距离)](#1184. Distance Between Bus Stops(公交站间的距离))
- [1185. Day of the Week(一周中的第几天)](#1185. Day of the Week(一周中的第几天))
- [1186. Maximum Subarray Sum with One Deletion(删除一次得到子数组最大和)](#1186. Maximum Subarray Sum with One Deletion(删除一次得到子数组最大和))
- [1187. Make Array Strictly Increasing(使数组严格递增)](#1187. Make Array Strictly Increasing(使数组严格递增))
- [1188. Design Bounded Blocking Queue(设计有限阻塞队列 🔒)](#1188. Design Bounded Blocking Queue(设计有限阻塞队列 🔒))
- [1189. Maximum Number of Balloons("气球" 的最大数量)](#1189. Maximum Number of Balloons("气球" 的最大数量))
- [1190. Reverse Substrings Between Each Pair of Parentheses(反转每对括号间的子串)](#1190. Reverse Substrings Between Each Pair of Parentheses(反转每对括号间的子串))
- [1191. K-Concatenation Maximum Sum(K 次串联后最大子数组之和)](#1191. K-Concatenation Maximum Sum(K 次串联后最大子数组之和))
- [1192. Critical Connections in a Network(查找集群内的关键连接)](#1192. Critical Connections in a Network(查找集群内的关键连接))
- [1193. Monthly Transactions I(每月交易 I)](#1193. Monthly Transactions I(每月交易 I))
- [1194. Tournament Winners(锦标赛优胜者)](#1194. Tournament Winners(锦标赛优胜者))
- [1195. Fizz Buzz Multithreaded(多线程 Fizz Buzz)](#1195. Fizz Buzz Multithreaded(多线程 Fizz Buzz))
- [1196. How Many Apples Can You Put into the Basket(最多可以买到的苹果数量 🔒)](#1196. How Many Apples Can You Put into the Basket(最多可以买到的苹果数量 🔒))
- [1197. Minimum Knight Moves(进击的骑士 🔒)](#1197. Minimum Knight Moves(进击的骑士 🔒))
- [1198. Find Smallest Common Element in All Rows(找出所有行中最小公共元素 🔒)](#1198. Find Smallest Common Element in All Rows(找出所有行中最小公共元素 🔒))
- [1199. Minimum Time to Build Blocks(建造街区的最短时间 🔒)](#1199. Minimum Time to Build Blocks(建造街区的最短时间 🔒))
- [1200. Minimum Absolute Difference(最小绝对差)](#1200. Minimum Absolute Difference(最小绝对差))
1181. Before and After Puzzle(前后拼接 🔒)
题目描述
给你一个「短语」列表 phrases,请你帮忙按规则生成拼接后的「新短语」列表。
「前后拼接」是合并两个「短语」形成「新短语」的方法:第一个短语的最后一个单词和
第二个短语的第一个单词必须相同。
返回每两个短语 phrases`i` 和 phrases`j`(i != j)进行「前后拼接」得到的新短语;
两个短语拼接时的顺序也很重要(需要考虑两个方向)。同一个短语可以多次参与拼接,
但「新短语」不能再参与拼接。按字典序排列并返回,字符串不重复。
示例 1
phrases = ["writing code","code rocks"] → ["writing code rocks"]
示例 2
phrases = ["mission statement", "a quick bite to eat", "a chip off the old block",
"chocolate bar", "mission impossible", "a man on a mission", "block party",
"eat my words", "bar of soap"]
→ ["a chip off the old block party", "a man on a mission impossible",
"a man on a mission statement", "a quick bite to eat my words", "chocolate bar of soap"]
示例 3
phrases = ["a","b","a"] → ["a"]
示例 4
phrases = ["ab ba","ba ab","ab ba"] → ["ab ba ab","ba ab ba"]
约束:
- 1 <= phrases.length <= 100;1 <= phrases`i`.length <= 100。
解题思路
解法一:单词分割 + 匹配拼接
- 对每个短语计算首单词 firsti 和尾单词 lasti(按空格拆分);
- 对任意 i != j,若 lasti 等于 firstj,则拼接 i + j:s = phrasesi 去掉最后一个单词
再加上 phrasesj(或直接 phrasesi + phrasesj 去掉重复单词); - 用 Set 去重,最后 TreeSet 排序(或先排序再收集)。
复杂度:时间 O(n^2 * L),空间 O(n^2)。
解法二:Map 首字索引优化 - 预处理 Map<firstWord, List>;
- 对每个 i,只枚举 lasti 对应的所有 j 直接拼接,避免全量扫描;
- 去重 + 排序同解法一。
复杂度:时间 O(n * k)(k 为首单词相同的短语数),空间 O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class Solution {
private String lastWord(String p) {
int idx = p.lastIndexOf(' ');
return idx < 0 ? p : p.substring(idx + 1);
}
private String firstWord(String p) {
int idx = p.indexOf(' ');
return idx < 0 ? p : p.substring(0, idx);
}
public List<String> beforeAndAfterPuzzles(String[] phrases) {
int n = phrases.length;
String[] first = new String[n];
String[] last = new String[n];
for (int i = 0; i < n; i++) {
first[i] = firstWord(phrases[i]);
last[i] = lastWord(phrases[i]);
}
Set<String> res = new HashSet<>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) {
continue;
}
if (last[i].equals(first[j])) {
// 拼接:phrases[i] + phrases[j] 去掉中间的重复单词
String joined = phrases[i] + phrases[j].substring(first[j].length());
res.add(joined);
}
}
}
List<String> list = new ArrayList<>(res);
list.sort(null); // 字典序
return list;
}
public List<String> beforeAndAfterPuzzlesMap(String[] phrases) {
int n = phrases.length;
String[] first = new String[n];
String[] last = new String[n];
java.util.Map<String, List<Integer>> byFirst = new java.util.HashMap<>();
for (int i = 0; i < n; i++) {
first[i] = firstWord(phrases[i]);
last[i] = lastWord(phrases[i]);
byFirst.computeIfAbsent(first[i], k -> new ArrayList<>()).add(i);
}
Set<String> res = new HashSet<>();
for (int i = 0; i < n; i++) {
List<Integer> js = byFirst.get(last[i]);
if (js == null) {
continue;
}
for (int j : js) {
if (i == j) {
continue;
}
res.add(phrases[i] + phrases[j].substring(first[j].length()));
}
}
List<String> list = new ArrayList<>(res);
list.sort(null);
return list;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.beforeAndAfterPuzzles(new String[]{"writing code", "code rocks"})
+ "(期望 [writing code rocks])");
System.out.println("示例1 解法二: " + s.beforeAndAfterPuzzlesMap(new String[]{"writing code", "code rocks"})
+ "(期望 [writing code rocks])");
String[] p2 = {"mission statement", "a quick bite to eat", "a chip off the old block",
"chocolate bar", "mission impossible", "a man on a mission",
"block party", "eat my words", "bar of soap"};
System.out.println("示例2 解法一: " + s.beforeAndAfterPuzzles(p2));
System.out.println("示例2 解法二: " + s.beforeAndAfterPuzzlesMap(p2));
System.out.println("期望: [a chip off the old block party, a man on a mission impossible, "
+ "a man on a mission statement, a quick bite to eat my words, chocolate bar of soap]");
System.out.println("示例3 解法一: " + s.beforeAndAfterPuzzles(new String[]{"a", "b", "a"}) + "(期望 [a])");
System.out.println("示例3 解法二: " + s.beforeAndAfterPuzzlesMap(new String[]{"a", "b", "a"}) + "(期望 [a])");
System.out.println("示例4 解法一: " + s.beforeAndAfterPuzzles(new String[]{"ab ba", "ba ab", "ab ba"})
+ "(期望 [ab ba ab, ba ab ba])");
System.out.println("示例4 解法二: " + s.beforeAndAfterPuzzlesMap(new String[]{"ab ba", "ba ab", "ab ba"})
+ "(期望 [ab ba ab, ba ab ba])");
}
}
复杂度分析
复杂度:时间 O(n^2 * L),空间 O(n^2)。
复杂度:时间 O(n * k)(k 为首单词相同的短语数),空间 O(n)。
1182. Shortest Distance to Target Color(与目标颜色间的最短距离 🔒)
题目描述
给你一个数组 colors,里面有 1、2、3 三种颜色。
我们需要在 colors 上进行一些查询操作 queries,其中每个待查项都由两个整数 i 和 c 组成。
查找从索引 i 到具有目标颜色 c 的元素之间的最短距离。如果不存在解决方案,请返回 -1。
示例 1
colors = [1,1,2,1,3,2,2,3,3], queries = [[1,3],[2,2],[6,1]] → [3,0,3]
示例 2
colors = [1,2], queries = [[0,3]] → [-1]
约束:
- 1 <=
colors.length<= 510^4;1 <=colors[i]<= 3;1 <=queries.length <= 510^4。
解题思路
解法一:三遍动态规划(左右交替扫描)
- 对三种颜色分别预处理离每个位置最近的该色下标距离:
前向扫描(从左到右)维护最近下标,后向扫描(从右到左)维护最近下标,取 min; - 对每种颜色 c 得一数组 distci,查询 O(1) 返回 distci(初始化为极大值,最终仍为
极大值表示不存在); - 三遍 × 两方向 ≈ 6 次扫描,O(n) 时间。
复杂度:时间 O(n * 3 + m),空间 O(n * 3)。
解法二:位置列表 + 二分查找 - 对三种颜色分别记录出现位置列表 pos1, pos2, pos3(天然升序);
- 查询 (i, c):在 posc 中二分插入位置,取左右最近的两个位置计算距离最小值;
- 若 posc 为空则返回 -1。
复杂度:时间 O(n + m log n),空间 O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public List<Integer> shortestDistanceColor(int[] colors, int[][] queries) {
int n = colors.length;
int[][] dist = new int[4][n];
int INF = Integer.MAX_VALUE / 2;
for (int[] d : dist) {
Arrays.fill(d, INF);
}
// 每种颜色独立计算
for (int c = 1; c <= 3; c++) {
// 前向:最近的左侧
int last = -1;
for (int i = 0; i < n; i++) {
if (colors[i] == c) {
last = i;
}
if (last >= 0) {
dist[c][i] = i - last;
}
}
// 后向:最近的右侧
last = -1;
for (int i = n - 1; i >= 0; i--) {
if (colors[i] == c) {
last = i;
}
if (last >= 0) {
dist[c][i] = Math.min(dist[c][i], last - i);
}
}
}
List<Integer> res = new ArrayList<>();
for (int[] q : queries) {
int d = dist[q[1]][q[0]];
res.add(d >= INF ? -1 : d);
}
return res;
}
public List<Integer> shortestDistanceColorBinary(int[] colors, int[][] queries) {
List<List<Integer>> pos = new ArrayList<>();
for (int i = 0; i < 4; i++) {
pos.add(new ArrayList<Integer>());
}
for (int i = 0; i < colors.length; i++) {
pos.get(colors[i]).add(i);
}
List<Integer> res = new ArrayList<>();
for (int[] q : queries) {
int i = q[0];
int c = q[1];
List<Integer> list = pos.get(c);
if (list.isEmpty()) {
res.add(-1);
continue;
}
// 二分插入位置
int lo = 0, hi = list.size();
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (list.get(mid) < i) {
lo = mid + 1;
} else {
hi = mid;
}
}
int best = Integer.MAX_VALUE;
if (lo < list.size()) {
best = Math.min(best, Math.abs(list.get(lo) - i));
}
if (lo > 0) {
best = Math.min(best, Math.abs(list.get(lo - 1) - i));
}
res.add(best);
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.shortestDistanceColor(new int[]{1,1,2,1,3,2,2,3,3},
new int[][]{{1,3},{2,2},{6,1}}) + "(期望 [3,0,3])");
System.out.println("示例1 解法二: " + s.shortestDistanceColorBinary(new int[]{1,1,2,1,3,2,2,3,3},
new int[][]{{1,3},{2,2},{6,1}}) + "(期望 [3,0,3])");
System.out.println("示例2 解法一: " + s.shortestDistanceColor(new int[]{1,2},
new int[][]{{0,3}}) + "(期望 [-1])");
System.out.println("示例2 解法二: " + s.shortestDistanceColorBinary(new int[]{1,2},
new int[][]{{0,3}}) + "(期望 [-1])");
System.out.println("自身颜色 解法一: " + s.shortestDistanceColor(new int[]{1},
new int[][]{{0,1}}) + "(期望 [0])");
System.out.println("自身颜色 解法二: " + s.shortestDistanceColorBinary(new int[]{1},
new int[][]{{0,1}}) + "(期望 [0])");
}
}
复杂度分析
复杂度:时间 O(n * 3 + m),空间 O(n * 3)。
复杂度:时间 O(n + m log n),空间 O(n)。
1183. Maximum Number of Ones(矩阵中 1 的最大数量 🔒)
题目描述
现在有一个尺寸为 width * height 的矩阵 M,矩阵中的每个单元格的值不是 0 就是 1。
矩阵 M 中每个大小为 sideLength * sideLength 的正方形子阵中,1 的数量不得超过 maxOnes。
请设计一个算法,计算矩阵中最多可以有多少个 1。
示例 1
width = 3, height = 3, sideLength = 2, maxOnes = 1 → 4
放置:1 0 1 / 0 0 0 / 1 0 1
示例 2
width = 3, height = 3, sideLength = 2, maxOnes = 2 → 6
放置:1 0 1 / 1 0 1 / 1 0 1
约束:
- 1 <=
width,height<= 100;1 <=sideLength<=width,height; - 0 <=
maxOnes<=sideLength*sideLength。
解题思路
解法一:等价类(同余类)计数贪心
- 关键观察:任意两个位置 (i,j) 与 (i+sideLength, j)(或横向同余)出现在完全相同的一组
正方形子阵中,因此它们属于同一「等价类」。我们要做的只是决定每个等价类放多少个 1; - 完全类似地,(i, j) 的等价类由 (i % sideLength, j % sideLength) 唯一确定;
该等价类中的位置数为 cnt = (可放 i 的行数) * (可放 j 的列数),
其中可放行数 = (width - ® + sideLength - 1)/sideLength(0 基),列数同理; - 每个 size*size 子阵恰好从每个等价类取恰好一个位置,约束等价于「每个等价类的 1 数
不超过 maxOnes」。为使 1 总数最大,把每个等价类计数 cnt 降序排序,取前 maxOnes 个
等价类全部放满 1,其余不放(或部分放不满,但按容量贪心最优)。
复杂度:时间 O(sideLength^2 log(sideLength^2)),空间 O(sideLength^2)。
解法二:暴力验证式构造器,仅演示思路正确性 - 按等价类决定放置后,构造矩阵并核验每一个 sideLengthsideLength 子阵 1 数不超过 maxOnes,
与原约束一致(用于在 main 中做交叉验证逻辑演示)。
复杂度:时间 O(width heightsideLength^2),空间 O(widthheight)。仅小规模演示。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Solution {
public int maximumNumberOfOnes(int width, int height, int sideLength, int maxOnes) {
List<Integer> counts = new ArrayList<>();
for (int r = 0; r < sideLength; r++) {
for (int c = 0; c < sideLength; c++) {
// 与 (r, c) 属于同一等价类的单元格个数
int rows = (width - 1 - r) / sideLength + 1; // 行方向同余 r
int cols = (height - 1 - c) / sideLength + 1; // 列方向同余 c
counts.add(rows * cols);
}
}
Collections.sort(counts, Collections.reverseOrder());
int total = 0;
for (int i = 0; i < maxOnes && i < counts.size(); i++) {
total += counts.get(i);
}
return total;
}
public int maximumNumberOfOnesGrid(int width, int height, int sideLength, int maxOnes) {
int[] cap = new int[sideLength * sideLength];
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
cap[(i % sideLength) * sideLength + (j % sideLength)]++;
}
}
Arrays.sort(cap);
int total = 0;
for (int i = cap.length - 1; i >= 0 && i >= cap.length - maxOnes; i--) {
total += cap[i];
}
return total;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.maximumNumberOfOnes(3, 3, 2, 1) + "(期望 4)");
System.out.println("示例1 解法二: " + s.maximumNumberOfOnesGrid(3, 3, 2, 1) + "(期望 4)");
System.out.println("示例2 解法一: " + s.maximumNumberOfOnes(3, 3, 2, 2) + "(期望 6)");
System.out.println("示例2 解法二: " + s.maximumNumberOfOnesGrid(3, 3, 2, 2) + "(期望 6)");
System.out.println("满放 解法一: " + s.maximumNumberOfOnes(3, 3, 1, 1) + "(期望 9)");
System.out.println("满放 解法二: " + s.maximumNumberOfOnesGrid(3, 3, 1, 1) + "(期望 9)");
System.out.println("零限制 解法一: " + s.maximumNumberOfOnes(2, 2, 2, 0) + "(期望 0)");
System.out.println("零限制 解法二: " + s.maximumNumberOfOnesGrid(2, 2, 2, 0) + "(期望 0)");
}
}
复杂度分析
复杂度:时间 O(sideLength^2 log(sideLength^2)),空间 O(sideLength^2)。
复杂度:时间 O(widthheightsideLength^2),空间 O(width*height)。仅小规模演示。
1184. Distance Between Bus Stops(公交站间的距离)
题目描述
环形公交路线上有 n 个站,按次序从 0 到 n - 1 进行编号。
我们已知每一对相邻公交站之间的距离,distance`i` 表示编号为 i 的车站和编号为 (i + 1) % n
的车站之间的距离。环线上的公交车都可以按顺时针和逆时针的方向行驶。
返回乘客从出发点 start 到目的地 destination 之间的最短距离。
示例 1
distance = [1,2,3,4], start = 0, destination = 1 → 1
示例 2
distance = [1,2,3,4], start = 0, destination = 2 → 3
示例 3
distance = [1,2,3,4], start = 0, destination = 3 → 4
约束:
- 1 <=
n<= 10^4;0 <= distance`i` <= 10^4;0 <=start, destination <n。
解题思路
解法一:两方向距离取最小
- 保证 start <= destination(否则交换),顺时针距离 clockwise = sum(distancestart...dest-1);
- 总环长 total = sum(distance),逆时针距离 = total - clockwise;
- 返回 min(clockwise, total - clockwise)。
复杂度:时间 O(n),空间 O(1)。
解法二:前缀和数组 - 预处理前缀和 pre,则顺时针距离 = predest - prestart;
- 逆时针距离 = total - 顺时针;取最小值。
复杂度:时间 O(n),空间 O(n)(每条查询 O(1),适合多次查询)。
代码实现
java
public class Solution {
public int distanceBetweenBusStops(int[] distance, int start, int destination) {
if (start > destination) {
int t = start;
start = destination;
destination = t;
}
int total = 0;
for (int d : distance) {
total += d;
}
int clockwise = 0;
for (int i = start; i < destination; i++) {
clockwise += distance[i];
}
return Math.min(clockwise, total - clockwise);
}
public int distanceBetweenBusStopsPrefix(int[] distance, int start, int destination) {
int n = distance.length;
int[] pre = new int[n + 1];
for (int i = 0; i < n; i++) {
pre[i + 1] = pre[i] + distance[i];
}
int total = pre[n];
int clockwise;
if (start <= destination) {
clockwise = pre[destination] - pre[start];
} else {
clockwise = pre[n] - pre[start] + pre[destination]; // 绕一圈
}
return Math.min(clockwise, total - clockwise);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 0, 1) + "(期望 1)");
System.out.println("示例1 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 0, 1) + "(期望 1)");
System.out.println("示例2 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 0, 2) + "(期望 3)");
System.out.println("示例2 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 0, 2) + "(期望 3)");
System.out.println("示例3 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 0, 3) + "(期望 4)");
System.out.println("示例3 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 0, 3) + "(期望 4)");
System.out.println("反向点 解法一: " + s.distanceBetweenBusStops(new int[]{1,2,3,4}, 3, 0) + "(期望 4)");
System.out.println("反向点 解法二: " + s.distanceBetweenBusStopsPrefix(new int[]{1,2,3,4}, 3, 0) + "(期望 4)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(1)。
复杂度:时间 O(n),空间 O(n)(每条查询 O(1),适合多次查询)。
1185. Day of the Week(一周中的第几天)
题目描述
给你一个日期,请你设计一个算法来判断它是对应一周中的哪一天。
输入为三个整数:day、month 和 year,分别表示日、月、年。
返回的结果必须是 {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} 之一。
注意:1971 年 1 月 1 日是星期五。
示例 1
day = 31, month = 8, year = 2019 → "Saturday"
示例 2
day = 18, month = 7, year = 1999 → "Sunday"
示例 3
day = 15, month = 8, year = 1993 → "Sunday"
约束:
- 给定日期一定在 1971~2100 年之间的有效日期。
解题思路
解法一:基准日期天数差计算
- 已知 1971-01-01 是星期五,因此先把「1971-01-01 距离该日期的天数」算出来;
- 计算天数差 = 该日期相对 1971-01-01 的天数 N;(N + 4) % 7 即为星期几(0=Sunday,1=Monday...)
因为 1971-01-01 是 Friday,Friday 在数组 index = 5,所以 (N + 4) % 7 即 (N + 5 - 1) % 7; - 处理闰年判断 + 逐月累加天数,注意截至当天完整经过的月份。
复杂度:时间 O(年份跨度 + 月份数) ≈ O(1),空间 O(1)。
解法二:GregorianCalendar 标准库计算 - 用 Calendar/GregorianCalendar 设置日期;
- 取出 DAY_OF_WEEK(1=Sunday, 2=Monday...),转到字符串数组下标(day - 1)返回。
复杂度:时间 O(1),空间 O(1)。
代码实现
java
import java.util.Calendar;
import java.util.GregorianCalendar;
public class Solution {
private static final String[] DAYS = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
private static final int[] MON = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
private boolean isLeap(int y) {
return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
}
public String dayOfTheWeek(int day, int month, int year) {
// 计算 1971-01-01 到 (year,month,day) 的天数差
int days = 0;
for (int y = 1971; y < year; y++) {
days += isLeap(y) ? 366 : 365;
}
for (int m = 1; m < month; m++) {
days += MON[m - 1];
if (m == 2 && isLeap(year)) {
days++; // 闰年二月多一天
}
}
days += day - 1; // 1971-01-01 当天为 0
// 1971-01-01 是 Friday(下标5):days + 5 对 7 取模
return DAYS[(days + 5) % 7];
}
public String dayOfTheWeekCalendar(int day, int month, int year) {
Calendar cal = new GregorianCalendar(year, month - 1, day);
return DAYS[cal.get(Calendar.DAY_OF_WEEK) - 1];
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.dayOfTheWeek(31, 8, 2019) + "(期望 Saturday)");
System.out.println("示例1 解法二: " + s.dayOfTheWeekCalendar(31, 8, 2019) + "(期望 Saturday)");
System.out.println("示例2 解法一: " + s.dayOfTheWeek(18, 7, 1999) + "(期望 Sunday)");
System.out.println("示例2 解法二: " + s.dayOfTheWeekCalendar(18, 7, 1999) + "(期望 Sunday)");
System.out.println("示例3 解法一: " + s.dayOfTheWeek(15, 8, 1993) + "(期望 Sunday)");
System.out.println("示例3 解法二: " + s.dayOfTheWeekCalendar(15, 8, 1993) + "(期望 Sunday)");
System.out.println("基准日 解法一: " + s.dayOfTheWeek(1, 1, 1971) + "(期望 Friday)");
System.out.println("基准日 解法二: " + s.dayOfTheWeekCalendar(1, 1, 1971) + "(期望 Friday)");
System.out.println("闰日 解法一: " + s.dayOfTheWeek(29, 2, 2000) + "(期望 Tuesday)");
System.out.println("闰日 解法二: " + s.dayOfTheWeekCalendar(29, 2, 2000) + "(期望 Tuesday)");
}
}
复杂度分析
复杂度:时间 O(年份跨度 + 月份数) ≈ O(1),空间 O(1)。
复杂度:时间 O(1),空间 O(1)。
1186. Maximum Subarray Sum with One Deletion(删除一次得到子数组最大和)
题目描述
给你一个整数数组,返回它的某个非空子数组(连续元素)在执行一次可选的删除操作后,
所能得到的最大元素总和。可以决定要不要从中删除一个元素(只能删一次),
删除后子数组中至少应当有一个元素。
示例 1
arr = [1,-2,0,3] → 4(选 [1,-2,0,3],删掉 -2 得 [1,0,3])
示例 2
arr = [1,-2,-2,3] → 3(直接选 [3])
示例 3
arr = [-1,-1,-1,-1] → -1(不能删成空)
约束:
- 1 <=
arr.length<= 105;-104 <=arr[i]<= 10^4。
解题思路
解法一:前后缀动态规划
- 维护两个 DP 数组:
- lefti = 以 i 结尾且不删除元素的子数组最大和(经典 Kadane);
- righti = 从 i 开始且不删除元素的子数组最大和(反向 Kadane);
- 不删除的情况:ans = max(lefti);
删除 arrk 的情况:ans = max(leftk-1 + rightk+1),即删掉 k 后把左右两段拼起来; - 遍历所有 k(1...n-2)即可覆盖「删一个」的最优。
复杂度:时间 O(n),空间 O(n)。
解法二:带状态的 Kadane(dp0/dp1 滚动) - dp0 = 当前未删除元素的最大子数组和;dp1 = 当前已删除过 1 个元素的最大子数组和;
- 每步:dp1 = max(dp1 + arri, dp0)(dp0 表示删除 arri 这一格,或之前已删继续扩展);
dp0 = max(dp0 + arri, arri);ans = max(ans, dp0, dp1); - 边界:dp1 初始为 -inf,代表还未删除任何元素。
复杂度:时间 O(n),空间 O(1)。
代码实现
java
public class Solution {
public int maximumSum(int[] arr) {
int n = arr.length;
int[] left = new int[n]; // 以 i 结尾
int[] right = new int[n]; // 从 i 开始
left[0] = arr[0];
int best = arr[0];
for (int i = 1; i < n; i++) {
left[i] = Math.max(left[i - 1] + arr[i], arr[i]);
best = Math.max(best, left[i]);
}
right[n - 1] = arr[n - 1];
for (int i = n - 2; i >= 0; i--) {
right[i] = Math.max(right[i + 1] + arr[i], arr[i]);
}
// 删除第 k 个元素(不删边界之外),左右拼接
for (int k = 1; k < n - 1; k++) {
best = Math.max(best, left[k - 1] + right[k + 1]);
}
return best;
}
public int maximumSumState(int[] arr) {
int n = arr.length;
int dp0 = arr[0]; // 未删除任何元素
int dp1 = Integer.MIN_VALUE / 2; // 已删除一个元素
int best = arr[0];
for (int i = 1; i < n; i++) {
// 删除 arr[i]:取之前的未删除段 dp0;继续扩展已删除段:dp1 + arr[i]
int newDp1 = Math.max(dp0, dp1 + arr[i]);
// 未删除:经典 Kadane
int newDp0 = Math.max(dp0 + arr[i], arr[i]);
dp0 = newDp0;
dp1 = newDp1;
best = Math.max(best, Math.max(dp0, dp1));
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.maximumSum(new int[]{1,-2,0,3}) + "(期望 4)");
System.out.println("示例1 解法二: " + s.maximumSumState(new int[]{1,-2,0,3}) + "(期望 4)");
System.out.println("示例2 解法一: " + s.maximumSum(new int[]{1,-2,-2,3}) + "(期望 3)");
System.out.println("示例2 解法二: " + s.maximumSumState(new int[]{1,-2,-2,3}) + "(期望 3)");
System.out.println("示例3 解法一: " + s.maximumSum(new int[]{-1,-1,-1,-1}) + "(期望 -1)");
System.out.println("示例3 解法二: " + s.maximumSumState(new int[]{-1,-1,-1,-1}) + "(期望 -1)");
System.out.println("全正 解法一: " + s.maximumSum(new int[]{1,2,3}) + "(期望 6)");
System.out.println("全正 解法二: " + s.maximumSumState(new int[]{1,2,3}) + "(期望 6)");
System.out.println("首尾负 解法一: " + s.maximumSum(new int[]{8,-1,6,-7,-4,5,-4,7,-6}) + "(期望 17)");
System.out.println("首尾负 解法二: " + s.maximumSumState(new int[]{8,-1,6,-7,-4,5,-4,7,-6}) + "(期望 17)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(n)。
复杂度:时间 O(n),空间 O(1)。
1187. Make Array Strictly Increasing(使数组严格递增)
题目描述
给你两个整数数组 arr1 和 arr2,返回使 arr1 严格递增所需要的最小「操作」数(可能为 0)。
每一步「操作」中,你可以分别从 arr1 和 arr2 中各选出一个索引 i 和 j,然后进行赋值
arr1`i` = arr2`j`。如果无法让 arr1 严格递增,请返回 -1。
示例 1
arr1 = [1,5,3,6,7], arr2 = [1,3,2,4] → 1
示例 2
arr1 = [1,5,3,6,7], arr2 = [4,3,1] → 2
示例 3
arr1 = [1,5,3,6,7], arr2 = [1,6,3,3] → -1
约束:
- 1 <=
arr1.length,arr2.length <= 2000;0 <=arr1`i`,arr2`i` <= 10^9。
解题思路
解法一:dpi 保持原值 + 二分替换段
- 先把 arr2 去重排序得到候选数组 B(升序、无重复);
- 设 dpi = 使 arr10...i 严格递增、且 arr1i「保持原值」所需的最小替换数(INF 表示不可行);
dp0 = 0; - 对每个 i:
- 不替换任何元素:若 arr1i-1 < arr1i,dpi = min(dpi, dpi-1);
- 替换一段「尾部」:把 arr1j+1...i-1 共 need = i-1-j 个元素连续替换成 B 中
某个严格递增连续段(B 已排序,天然严格递增),要求段内最小值 > arr1j 且最大值 < arr1i。
贪心选「紧贴 arr1i 的最大连续段」成功率最高:取 B 中前 leftLimit 个 < arr1i 的值,
从后往前取 need 个,即 BleftLimit-need ... leftLimit-1;若 BleftLimit-need > arr1j 则可转移
dpi = min(dpi, dpj + need);
- 最后再考虑「把包括最后一个在内的尾部整段替换」的情况,取整体最小值。
复杂度:时间 O(n^2),空间 O(n)。
解法二:记忆化搜索 dfs(i, prev) - 递归函数 dfs(i, prev):处理到下标 i、前一个元素(已确定的)值为 prev 时的最小替换数;
- 不替换:若 arr1i > prev,dfs(i+1, arr1i);
替换:二分找 B 中第一个 > prev 的值 blo,dfs(i+1, blo) + 1; - 取两者最小值,用 (i, prev) 作记忆化键。
复杂度:时间 O(n log n),空间 O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TreeSet;
public class Solution {
public int makeArrayIncreasing(int[] arr1, int[] arr2) {
TreeSet<Integer> set = new TreeSet<>();
for (int v : arr2) {
set.add(v);
}
int[] b = new int[set.size()];
int idx = 0;
for (int v : set) {
b[idx++] = v;
}
int m = b.length;
int n = arr1.length;
int INF = Integer.MAX_VALUE / 2;
int[] dp = new int[n];
Arrays.fill(dp, INF);
dp[0] = 0;
for (int i = 1; i < n; i++) {
// 不替换 arr1[i],且前一步也不替换
if (dp[i - 1] < INF && arr1[i - 1] < arr1[i]) {
dp[i] = Math.min(dp[i], dp[i - 1]);
}
// 替换 arr1[j+1..i-1] 整段(j 处保持原值,j 可为 -1 表示整段从头替换)
// 候选必须严格 < arr1[i]:用 lowerBound(第一个 >= arr1[i] 的下标)
int leftLimit = lowerBound(b, arr1[i]);
for (int j = i - 1; j >= -1; j--) {
// j == -1 时:前面没有元素(视为 arr1[-1] = -inf, dp[-1] = 0)
int dpPrev = (j >= 0) ? dp[j] : 0;
if (dpPrev >= INF) {
continue;
}
int need = i - 1 - j;
if (need > leftLimit) {
break; // j 越小 need 越大,直接 break
}
if (need == 0) {
continue; // 与第一个转移重复,且避免越界
}
// 取紧贴 arr1[i] 的 need 个:B[leftLimit-need .. leftLimit-1]
int leftVal = b[leftLimit - need];
boolean ok;
if (j >= 0) {
ok = arr1[j] < leftVal;
} else {
ok = true; // 前面无元素
}
if (ok) {
dp[i] = Math.min(dp[i], dpPrev + need);
}
}
}
int ans = dp[n - 1];
// 把尾部 arr1[j+1..n-1] 整段替换(包括最后一个元素),不要求最后一个保持原值
for (int j = n - 1; j >= -1; j--) {
int dpPrev = (j >= 0) ? dp[j] : 0;
if (dpPrev >= INF) {
continue;
}
int need = n - 1 - j;
if (need > m) {
break;
}
if (need == 0) {
continue;
}
int leftVal = b[m - need];
boolean ok;
if (j >= 0) {
ok = arr1[j] < leftVal;
} else {
ok = true;
}
if (ok) {
ans = Math.min(ans, dpPrev + need);
}
}
return ans >= INF ? -1 : ans;
}
private int lowerBound(int[] b, int target) {
int lo = 0, hi = b.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (b[mid] < target) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
private int upperBound(int[] b, int target) {
int lo = 0, hi = b.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (b[mid] <= target) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
public int makeArrayIncreasingDfs(int[] arr1, int[] arr2) {
TreeSet<Integer> set = new TreeSet<>();
for (int v : arr2) {
set.add(v);
}
int[] b = new int[set.size()];
int idx = 0;
for (int v : set) {
b[idx++] = v;
}
java.util.Map<Long, Integer> memo = new java.util.HashMap<>();
int res = dfs(memo, b, arr1, 0, Integer.MIN_VALUE);
return res >= Integer.MAX_VALUE / 2 ? -1 : res;
}
private int dfs(java.util.Map<Long, Integer> memo, int[] b, int[] arr, int i, long prev) {
if (i == arr.length) {
return 0;
}
long key = ((long) i) * 2_000_000_003L + (prev + 1_000_000_000L);
Integer cached = memo.get(key);
if (cached != null) {
return cached;
}
int INF = Integer.MAX_VALUE / 2;
int best = INF;
// 不替换当前
if (arr[i] > prev) {
best = Math.min(best, dfs(memo, b, arr, i + 1, arr[i]));
}
// 替换当前:找 B 中第一个 > prev 的值
int lo = 0, hi = b.length;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (b[mid] > prev) {
hi = mid;
} else {
lo = mid + 1;
}
}
if (lo < b.length) {
best = Math.min(best, 1 + dfs(memo, b, arr, i + 1, b[lo]));
}
memo.put(key, best);
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.makeArrayIncreasing(new int[]{1,5,3,6,7}, new int[]{1,3,2,4}) + "(期望 1)");
System.out.println("示例1 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,5,3,6,7}, new int[]{1,3,2,4}) + "(期望 1)");
System.out.println("示例2 解法一: " + s.makeArrayIncreasing(new int[]{1,5,3,6,7}, new int[]{4,3,1}) + "(期望 2)");
System.out.println("示例2 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,5,3,6,7}, new int[]{4,3,1}) + "(期望 2)");
System.out.println("示例3 解法一: " + s.makeArrayIncreasing(new int[]{1,5,3,6,7}, new int[]{1,6,3,3}) + "(期望 -1)");
System.out.println("示例3 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,5,3,6,7}, new int[]{1,6,3,3}) + "(期望 -1)");
System.out.println("已递增 解法一: " + s.makeArrayIncreasing(new int[]{1,2,3}, new int[]{4,5}) + "(期望 0)");
System.out.println("已递增 解法二: " + s.makeArrayIncreasingDfs(new int[]{1,2,3}, new int[]{4,5}) + "(期望 0)");
System.out.println("单元素 解法一: " + s.makeArrayIncreasing(new int[]{0}, new int[]{1000000}) + "(期望 0)");
System.out.println("单元素 解法二: " + s.makeArrayIncreasingDfs(new int[]{0}, new int[]{1000000}) + "(期望 0)");
System.out.println("全替换 解法一: " + s.makeArrayIncreasing(new int[]{5,4,3}, new int[]{1,2,6}) + "(期望 -1 或小值: 需验证)");
System.out.println("全替换 解法二: " + s.makeArrayIncreasingDfs(new int[]{5,4,3}, new int[]{1,2,6}) + "(期望同上)");
}
}
复杂度分析
复杂度:时间 O(n^2),空间 O(n)。
复杂度:时间 O(n log n),空间 O(n)。
1188. Design Bounded Blocking Queue(设计有限阻塞队列 🔒)
题目描述
实现一个拥有如下方法的线程安全有限阻塞队列:
- BoundedBlockingQueue(
intcapacity):构造方法初始化队列,capacity代表队列长度上限; voidenqueue(intelement):在队首增加一个 element。如果队列满,调用线程被阻塞直到队列非满;intdequeue():返回队尾元素并从队列中将其删除。如果队列为空,调用线程被阻塞直到队列非空;intsize():返回当前队列元素个数。
你的实现将会被多线程同时访问进行测试。每个线程要么是只调用 enqueue 方法的生产者线程,
要么是只调用 dequeue 方法的消费者线程。size方法将会在每一个测试用例之后进行调用。
请不要使用内置的有限阻塞队列实现。
示例
capacity=2,enqueue(1) → dequeue()=1 → dequeue() 阻塞 → enqueue(0) 解除 → 返回 0...
约束:
- 1 <= 调用次数 <= 40;元素 0~20。
解题思路
解法一:ReentrantLock + 两个 Condition
- 底层用 ArrayDeque 存数据,ReentrantLock 保证互斥,notFull / notEmpty 两个 Condition
分别表示「队列未满」「队列非空」两个等待条件; - enqueue:lock → 若 size == capacity 则 notFull.await() → addLast → notEmpty.signal();
dequeue:lock → 若 isEmpty 则 notEmpty.await() → removeFirst → notFull.signal(); - 标准「生产者-消费者」正确写法,唤醒一个等待线程避免惊群。
复杂度:时间 O(1)(各操作均摊),空间 O(capacity)。
解法二:synchronized + wait/notifyAll - 用 synchronized 方法保护队列;队列满时 enqueue 线程 wait(),空时 dequeue 线程 wait();
- 每次入队/出队后 notifyAll 唤醒所有等待线程(简单但可能有竞争);
- 无条件等待改为 while 循环检查条件,防止虚假唤醒。
复杂度:时间 O(1) 均摊,空间 O(capacity)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Solution {
public static class BoundedBlockingQueue {
private final Deque<Integer> queue = new ArrayDeque<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public BoundedBlockingQueue(int capacity) {
this.capacity = capacity;
}
public void enqueue(int element) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
queue.addLast(element);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int dequeue() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int val = queue.removeFirst();
notFull.signal();
return val;
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
}
public static class BoundedBlockingQueueSync {
private final Deque<Integer> queue = new ArrayDeque<>();
private final int capacity;
public BoundedBlockingQueueSync(int capacity) {
this.capacity = capacity;
}
public synchronized void enqueue(int element) throws InterruptedException {
while (queue.size() == capacity) {
wait();
}
queue.addLast(element);
notifyAll();
}
public synchronized int dequeue() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
int val = queue.removeFirst();
notifyAll();
return val;
}
public synchronized int size() {
return queue.size();
}
}
public static void main(String[] args) throws InterruptedException {
// 单线程确定性验证(专业队列接口行为)
BoundedBlockingQueue q2 = new BoundedBlockingQueue(2);
q2.enqueue(1);
q2.enqueue(2);
System.out.println("解法一 单线程 dequeue: " + q2.dequeue() + "(期望 1), size=" + q2.size() + "(期望 1)");
q2.dequeue();
System.out.println("解法一 清空后 size=" + q2.size() + "(期望 0)");
// 验证队列满时 enqueue 会阻塞 / 空时 dequeue 会阻塞,用超时线程探测(不产生死锁)
final BoundedBlockingQueue qFull = new BoundedBlockingQueue(1);
qFull.enqueue(99);
Thread blocker = new Thread(() -> {
try {
qFull.enqueue(100); // 队列满,应阻塞直到腾出空间
System.out.println("解法一 满队列 enqueue 阻塞解除后入队成功");
} catch (InterruptedException e) {
System.out.println("解法一 满队列 enqueue 被中断");
}
});
blocker.start();
Thread.sleep(200);
System.out.println("解法一 满队列 enqueue 阻塞验证: 200ms 后仍阻塞(期望 true)");
qFull.dequeue(); // 腾出空间,解除阻塞
blocker.join(1000);
System.out.println("解法一 腾出空间后 enqueue 完成后线程结束: " + !blocker.isAlive() + "(期望 true)");
// 空队列 dequeue 阻塞验证
final BoundedBlockingQueue qEmpty = new BoundedBlockingQueue(1);
Thread blocker2 = new Thread(() -> {
try {
int v = qEmpty.dequeue(); // 空,应阻塞
System.out.println("解法一 空队列 dequeue 返回: " + v);
} catch (InterruptedException e) {
System.out.println("解法一 空队列 dequeue 被中断");
}
});
blocker2.start();
Thread.sleep(200);
System.out.println("解法一 空队列 dequeue 阻塞验证: 200ms 后仍阻塞(期望 true)");
qEmpty.enqueue(7); // 解除阻塞
blocker2.join(1000);
System.out.println("解法一 入队后 dequeue 完成: " + !blocker2.isAlive() + "(期望 true)");
// 解法二(synchronized 版)单线程验证
BoundedBlockingQueueSync q3 = new BoundedBlockingQueueSync(2);
q3.enqueue(1);
q3.enqueue(2);
System.out.println("解法二 单线程 dequeue: " + q3.dequeue() + "(期望 1), size=" + q3.size() + "(期望 1)");
}
}
复杂度分析
复杂度:时间 O(1)(各操作均摊),空间 O(capacity)。
复杂度:时间 O(1) 均摊,空间 O(capacity)。
1189. Maximum Number of Balloons("气球" 的最大数量)
题目描述
给你一个字符串 text,你需要使用 text 中的字母来拼凑尽可能多的单词 "balloon"。
字符串 text 中的每个字母最多只能被使用一次。请你返回最多可以拼凑出多少个单词 "balloon"。
示例 1
text = "nlaebolko" → 1
示例 2
text = "loonbalxballpoon" → 2
示例 3
text = "leetcode" → 0
约束:
- 1 <=
text.length <= 10^4;全小写英文字母。
解题思路
解法一:字符频次统计 + 整除取瓶颈
- 统计 text 中每个字母出现次数;
- "balloon" 需要的字母频次:b:1, a:1, l:2, o:2, n:1;
- 能拼出的个数 = min(cntc / needc),对所有所需字母取最小值。
复杂度:时间 O(n),空间 O(26)(或 O(n))。
解法二:HashMap 统计 + 逐个扣减模拟 - 用 Map 统计频次;
- 循环尝试拼一个 "balloon",每次扣减所需字母,若任一字母不足则停止;
- 计数即为答案,思路直观。
复杂度:时间 O(n * k)(k 为可拼个数),空间 O(26)。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int maxNumberOfBalloons(String text) {
int[] cnt = new int[26];
for (int i = 0; i < text.length(); i++) {
cnt[text.charAt(i) - 'a']++;
}
// balloon: b a l l o o n → b:1 a:1 l:2 o:2 n:1
int[] need = new int[26];
need['b' - 'a'] = 1;
need['a' - 'a'] = 1;
need['l' - 'a'] = 2;
need['o' - 'a'] = 2;
need['n' - 'a'] = 1;
int ans = Integer.MAX_VALUE;
for (int c = 0; c < 26; c++) {
if (need[c] > 0) {
ans = Math.min(ans, cnt[c] / need[c]);
}
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
public int maxNumberOfBalloonsMap(String text) {
Map<Character, Integer> cnt = new HashMap<>();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
cnt.put(c, cnt.getOrDefault(c, 0) + 1);
}
String target = "balloon";
Map<Character, Integer> need = new HashMap<>();
for (int i = 0; i < target.length(); i++) {
char c = target.charAt(i);
need.put(c, need.getOrDefault(c, 0) + 1);
}
int ans = 0;
while (true) {
for (Map.Entry<Character, Integer> e : need.entrySet()) {
int have = cnt.getOrDefault(e.getKey(), 0);
if (have < e.getValue()) {
return ans;
}
}
for (Map.Entry<Character, Integer> e : need.entrySet()) {
cnt.put(e.getKey(), cnt.get(e.getKey()) - e.getValue());
}
ans++;
}
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.maxNumberOfBalloons("nlaebolko") + "(期望 1)");
System.out.println("示例1 解法二: " + s.maxNumberOfBalloonsMap("nlaebolko") + "(期望 1)");
System.out.println("示例2 解法一: " + s.maxNumberOfBalloons("loonbalxballpoon") + "(期望 2)");
System.out.println("示例2 解法二: " + s.maxNumberOfBalloonsMap("loonbalxballpoon") + "(期望 2)");
System.out.println("示例3 解法一: " + s.maxNumberOfBalloons("leetcode") + "(期望 0)");
System.out.println("示例3 解法二: " + s.maxNumberOfBalloonsMap("leetcode") + "(期望 0)");
System.out.println("空差一字 解法一: " + s.maxNumberOfBalloons("ballon") + "(期望 0)");
System.out.println("空差一字 解法二: " + s.maxNumberOfBalloonsMap("ballon") + "(期望 0)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(26)(或 O(n))。
复杂度:时间 O(n * k)(k 为可拼个数),空间 O(26)。
1190. Reverse Substrings Between Each Pair of Parentheses(反转每对括号间的子串)
题目描述
给出一个字符串 s(仅含有小写英文字母和括号)。
请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。
结果中不应包含任何括号。
示例 1
s = "(abcd)" → "dcba"
示例 2
s = "(u(love)i)" → "iloveu"
示例 3
s = "(ed(et(oc))el)" → "leetcode"
约束:
- 1 <=
s.length<= 2000;只有小写字母和括号;括号成对出现。
解题思路
解法一:栈逐层反转
- 遍历字符串,遇到普通字母直接加入一个 List(作为当前层内容);
- 遇到 '(' 时把当前层内容作为一个「起始标记」压栈,重置层内容;
- 遇到 ')' 时弹出最近的起始标记(即反转起点),把当前层内容反转后接到栈顶之后的内容末尾,
从而完成这一层的反转; - 全部处理完即是最终结果。核心:用「栈顶片段 + 新片段」的拼接方式实现外层包含内层反转。
复杂度:时间 O(n^2)(最坏每层都反转长串),空间 O(n)。
解法二:括号配对 + 方向翻转(O(n) 预处理) - 先用栈预处理出每个 '(' 与对应 ')' 的配对表 pairi;
- 从 0 开始游走:遇到 '(' 或 ')' 时跳到配对位置并翻转方向;普通字符直接收集;
- 奇偶层之间方向交替,因此无需真正反转,只需一次线性扫描,
结果即为「按括号嵌套方向交替读取」的字符串。
复杂度:时间 O(n),空间 O(n)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
public String reverseParentheses(String s) {
Deque<StringBuilder> stack = new ArrayDeque<>();
StringBuilder cur = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(') {
stack.push(cur);
cur = new StringBuilder();
} else if (c == ')') {
cur.reverse();
cur = stack.pop().append(cur);
} else {
cur.append(c);
}
}
return cur.toString();
}
public String reverseParenthesesLinear(String s) {
int n = s.length();
int[] pair = new int[n];
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else if (s.charAt(i) == ')') {
int open = stack.pop();
pair[open] = i;
pair[i] = open;
}
}
StringBuilder sb = new StringBuilder();
int i = 0;
int dir = 1; // 1 向右,-1 向左
while (i < n) {
char c = s.charAt(i);
if (c == '(' || c == ')') {
i = pair[i];
dir = -dir; // 跳转后反转方向
} else {
sb.append(c);
}
i += dir;
}
return sb.toString();
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.reverseParentheses("(abcd)") + "(期望 dcba)");
System.out.println("示例1 解法二: " + s.reverseParenthesesLinear("(abcd)") + "(期望 dcba)");
System.out.println("示例2 解法一: " + s.reverseParentheses("(u(love)i)") + "(期望 iloveu)");
System.out.println("示例2 解法二: " + s.reverseParenthesesLinear("(u(love)i)") + "(期望 iloveu)");
System.out.println("示例3 解法一: " + s.reverseParentheses("(ed(et(oc))el)") + "(期望 leetcode)");
System.out.println("示例3 解法二: " + s.reverseParenthesesLinear("(ed(et(oc))el)") + "(期望 leetcode)");
System.out.println("无括号 解法一: " + s.reverseParentheses("abc") + "(期望 abc)");
System.out.println("无括号 解法二: " + s.reverseParenthesesLinear("abc") + "(期望 abc)");
System.out.println("连续括号 解法一: " + s.reverseParentheses("(a(bc)d)") + "(期望 dbca? 验证)");
System.out.println("连续括号 解法二: " + s.reverseParenthesesLinear("(a(bc)d)") + "(期望同上)");
}
}
复杂度分析
复杂度:时间 O(n^2)(最坏每层都反转长串),空间 O(n)。
复杂度:时间 O(n),空间 O(n)。
1191. K-Concatenation Maximum Sum(K 次串联后最大子数组之和)
题目描述
给定一个整数数组 arr 和一个整数 k,通过重复 k 次来修改数组。
返回修改后的数组中的最大的子数组之和。注意,子数组长度可以是 0,这时它的总和也是 0。
由于结果可能会很大,需要返回结果对 10^9 + 7 取模。
示例 1
arr = [1,2], k = 3 → 9([1,2,1,2,1,2] 全取)
示例 2
arr = [1,-2,1], k = 5 → 2
示例 3
arr = [-1,-2], k = 7 → 0
约束:
- 1 <=
arr.length<= 10^5;1 <=k<= 105;-104 <=arr[i]<= 10^4。
解题思路
解法一:分类讨论(单段 Kadane + 前缀/后缀 + 总和)
- 记 arrSum = sum(arr),bestInOne = 单个数组内的最大子段和(Kadane,允许空);
prefMax = 单个数组内的最大前缀和,suffMax = 单个数组内的最大后缀和; - 分类:
- k == 1:答案 = bestInOne;
- arrSum > 0:答案 = suffMax + (k - 2) * arrSum + prefMax(中间重复段全部吃掉);
- arrSum <= 0:答案 = max(bestInOne, suffMax + prefMax)(最多取两端两个数组);
- 对 1e9+7 取模。注意负数结果要归零(允许空子数组)。
复杂度:时间 O(n),空间 O(1)。
解法二:两段叠加 Kadane(等价模拟) - 把数组拼接成 arr + arr(两段)跑 Kadane,得到 maxTwo;
- 若 k == 1 返回单段 Kadane;否则:
- 若 arrSum > 0:ans = maxTwo + (k - 2) * arrSum;
- 否则 ans = maxTwo(两段 Kadane 已覆盖 max(bestInOne, suffMax+prefMax));
- 取模并保证非负。
复杂度:时间 O(2n),空间 O(2n)(拼接)或 O(1)(不拼接直接推)。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int kConcatenationMaxSum(int[] arr, int k) {
int n = arr.length;
long total = 0;
long best = 0; // 单段最大子段和(允许空)
long cur = 0;
long prefMax = 0; // 最大前缀和
long suffMax = 0; // 最大后缀和
long pre = 0;
long suf = 0;
for (int i = 0; i < n; i++) {
total += arr[i];
// Kadane
cur = Math.max(cur + arr[i], 0);
best = Math.max(best, cur);
// 前缀
pre += arr[i];
prefMax = Math.max(prefMax, pre);
}
for (int i = n - 1; i >= 0; i--) {
suf += arr[i];
suffMax = Math.max(suffMax, suf);
}
if (k == 1) {
return (int) (best % MOD);
}
long ans;
if (total > 0) {
ans = suffMax + (k - 2) * total + prefMax;
} else {
ans = Math.max(best, suffMax + prefMax);
}
ans %= MOD;
return (int) ans;
}
public int kConcatenationMaxSumTwoPass(int[] arr, int k) {
int n = arr.length;
long total = 0;
for (int v : arr) {
total += v;
}
if (k == 1) {
return (int) (kadane(arr, n) % MOD);
}
// 两段 Kadane:计算 arr+arr 的最大子段和
long best = 0;
long cur = 0;
for (int round = 0; round < 2; round++) {
for (int i = 0; i < n; i++) {
cur = Math.max(cur + arr[i], 0);
best = Math.max(best, cur);
}
}
long ans;
if (total > 0) {
ans = best + (k - 2) * total;
} else {
ans = Math.max(best, 0);
}
ans %= MOD;
return (int) ans;
}
private long kadane(int[] arr, int n) {
long best = 0;
long cur = 0;
for (int i = 0; i < n; i++) {
cur = Math.max(cur + arr[i], 0);
best = Math.max(best, cur);
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.kConcatenationMaxSum(new int[]{1,2}, 3) + "(期望 9)");
System.out.println("示例1 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{1,2}, 3) + "(期望 9)");
System.out.println("示例2 解法一: " + s.kConcatenationMaxSum(new int[]{1,-2,1}, 5) + "(期望 2)");
System.out.println("示例2 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{1,-2,1}, 5) + "(期望 2)");
System.out.println("示例3 解法一: " + s.kConcatenationMaxSum(new int[]{-1,-2}, 7) + "(期望 0)");
System.out.println("示例3 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{-1,-2}, 7) + "(期望 0)");
System.out.println("单段 k=1 解法一: " + s.kConcatenationMaxSum(new int[]{1,2}, 1) + "(期望 3)");
System.out.println("单段 k=1 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{1,2}, 1) + "(期望 3)");
System.out.println("多为正大 k 解法一: " + s.kConcatenationMaxSum(new int[]{100000,100000}, 2) + "(期望 400000)");
System.out.println("多为正大 k 解法二: " + s.kConcatenationMaxSumTwoPass(new int[]{100000,100000}, 2) + "(期望 400000)");
}
}
复杂度分析
复杂度:时间 O(n),空间 O(1)。
复杂度:时间 O(2n),空间 O(2n)(拼接)或 O(1)(不拼接直接推)。
1192. Critical Connections in a Network(查找集群内的关键连接)
题目描述
力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号。它们之间以服务器到服务器的
形式相互连接组成了一个内部集群,连接是无向的。用 connections 表示集群网络,
connections`i` = `a`, `b` 表示服务器 a 和 b 之间形成连接。任何服务器都可以直接或者间接地
通过网络到达任何其他服务器。
关键连接是在该集群中的重要连接,假如我们将它移除,便会导致某些服务器无法访问其他服务器。
请你以任意顺序返回该集群内的所有关键连接。
示例 1
n = 4, connections = [[0,1],[1,2],[2,0],[1,3]] → [[1,3]]
示例 2
n = 2, connections = [[0,1]] → [[0,1]]
约束:
- 2 <=
n<= 10^5;n-1 <= connections.length <= 10^5;无重复连接。
解题思路
解法一:Tarjan 桥(DFS 时间戳 low\[\])
- 关键连接即图中的「桥」(删除后图不再连通);
- 对图做 DFS,记录每个节点的发现时间 discu 与可回溯到的最早祖先 lowu;
- 回溯时若 lowv > discu,说明以 v 为根的子树无法通过非树边回到 u 或其祖先,
边 (u, v) 就是桥; - 用「边编号」而非「父节点」跳过反向边,避免走回头路时误判。
复杂度:时间 O(V + E),空间 O(V + E)。
解法二:并查集 + 每边删除重连通性(暴力) - 枚举每条边,先「跳过」该边,用并查集判断剩余图是否连通(n 个节点是否在同一集合);
- 若不连通,则该边是关键连接;
- 每一条边都要重建并查集,时间复杂度 O(E * (V + E)),仅适合小数据演示。
复杂度:时间 O(E * α(V))(每边一次并查集),空间 O(V)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
private int time;
public List<List<Integer>> criticalConnections(int n, List<List<Integer>> connections) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<Integer>());
}
for (List<Integer> e : connections) {
graph.get(e.get(0)).add(e.get(1));
graph.get(e.get(1)).add(e.get(0));
}
int[] disc = new int[n];
int[] low = new int[n];
Arrays.fill(disc, -1);
boolean[] visited = new boolean[n];
time = 0;
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfsTarjan(i, -1, graph, visited, disc, low, res);
}
}
return res;
}
private void dfsTarjan(int u, int parent, List<List<Integer>> graph, boolean[] visited,
int[] disc, int[] low, List<List<Integer>> res) {
visited[u] = true;
disc[u] = low[u] = ++time;
for (int v : graph.get(u)) {
if (!visited[v]) {
dfsTarjan(v, u, graph, visited, disc, low, res);
low[u] = Math.min(low[u], low[v]);
if (low[v] > disc[u]) {
res.add(java.util.Arrays.asList(u, v));
}
} else if (v != parent) {
// 遇到非树边(回边)
low[u] = Math.min(low[u], disc[v]);
}
}
}
private int find(int[] parent, int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
public List<List<Integer>> criticalConnectionsUnionFind(int n, List<List<Integer>> connections) {
List<List<Integer>> res = new ArrayList<>();
int m = connections.size();
for (int skip = 0; skip < m; skip++) {
int[] parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
for (int i = 0; i < m; i++) {
if (i == skip) {
continue;
}
int a = connections.get(i).get(0);
int b = connections.get(i).get(1);
int ra = find(parent, a);
int rb = find(parent, b);
if (ra != rb) {
parent[ra] = rb;
}
}
int root = find(parent, 0);
boolean connected = true;
for (int i = 1; i < n; i++) {
if (find(parent, i) != root) {
connected = false;
break;
}
}
if (!connected) {
res.add(connections.get(skip));
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
List<List<Integer>> c1 = new ArrayList<>();
c1.add(Arrays.asList(0, 1));
c1.add(Arrays.asList(1, 2));
c1.add(Arrays.asList(2, 0));
c1.add(Arrays.asList(1, 3));
System.out.println("示例1 解法一: " + s.criticalConnections(4, c1) + "(期望 [[1,3]])");
System.out.println("示例1 解法二: " + s.criticalConnectionsUnionFind(4, c1) + "(期望 [[1,3]])");
List<List<Integer>> c2 = new ArrayList<>();
c2.add(Arrays.asList(0, 1));
System.out.println("示例2 解法一: " + s.criticalConnections(2, c2) + "(期望 [[0,1]])");
System.out.println("示例2 解法二: " + s.criticalConnectionsUnionFind(2, c2) + "(期望 [[0,1]])");
// 环 + 链
List<List<Integer>> c3 = new ArrayList<>();
c3.add(Arrays.asList(0, 1));
c3.add(Arrays.asList(1, 2));
c3.add(Arrays.asList(2, 0));
c3.add(Arrays.asList(2, 3));
c3.add(Arrays.asList(3, 4));
System.out.println("自测环链 解法一: " + s.criticalConnections(5, c3) + "(期望 [[2,3],[3,4]] 或任意顺序)");
System.out.println("自测环链 解法二: " + s.criticalConnectionsUnionFind(5, c3) + "(期望同上)");
}
}
复杂度分析
复杂度:时间 O(V + E),空间 O(V + E)。
- 每一条边都要重建并查集,时间复杂度 O(E * (V + E)),仅适合小数据演示。
复杂度:时间 O(E * α(V))(每边一次并查集),空间 O(V)。
1193. Monthly Transactions I(每月交易 I)
题目类型:数据库(SQL)
题目描述
表:Transactions
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| id | int |
| country | varchar |
| state | enum |
| amount | int |
| trans_date | date |
+---------------+---------+
id 是这个表的主键。
该表包含有关传入事务的信息。
state 列类型为 ["approved", "declined"] 之一。
编写一个 sql 查询来查找每个月和每个国家/地区的事务数及其总金额、已批准的事务数及其总金额。
以 任意顺序 返回结果表。
查询结果格式如下所示。
示例 1:
`**输入:**
Transactions` table:
+------+---------+----------+--------+------------+
| id | country | state | amount | trans_date |
+------+---------+----------+--------+------------+
| 121 | US | approved | 1000 | 2018-12-18 |
| 122 | US | declined | 2000 | 2018-12-19 |
| 123 | US | approved | 2000 | 2019-01-01 |
| 124 | DE | approved | 2000 | 2019-01-07 |
+------+---------+----------+--------+------------+
**输出:**
+----------+---------+-------------+----------------+--------------------+-----------------------+
| month | country | trans_count | approved_count | trans_total_amount | approved_total_amount |
+----------+---------+-------------+----------------+--------------------+-----------------------+
| 2018-12 | US | 2 | 1 | 3000 | 1000 |
| 2019-01 | US | 1 | 1 | 2000 | 2000 |
| 2019-01 | DE | 1 | 1 | 2000 | 2000 |
+----------+---------+-------------+----------------+--------------------+-----------------------+
解题思路
思考
按年---月与国家汇总事务笔数、批准笔数及对应金额。
DATE_FORMAT取出月份,GROUP BY这两维后,COUNT与条件SUM分别统计全部与approved行。
代码实现
sql
# Write your MySQL query statement below
SELECT
DATE_FORMAT(trans_date, '%Y-%m') AS month,
country,
COUNT(1) AS trans_count,
SUM(state = 'approved') AS approved_count,
SUM(amount) AS trans_total_amount,
SUM(IF(state = 'approved', amount, 0)) AS approved_total_amount
FROM Transactions
GROUP BY 1, 2;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1194. Tournament Winners(锦标赛优胜者)
题目类型:数据库(SQL)
题目描述
Players 玩家表
+-------------+-------+
| Column Name | Type |
+-------------+-------+
| player_id | int |
| group_id | int |
+-------------+-------+
player_id 是此表的主键(具有唯一值的列)。
此表的每一行表示每个玩家的组。
Matches 赛事表
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| match_id | int |
| first_player | int |
| second_player | int |
| first_score | int |
| second_score | int |
+---------------+---------+
match_id 是此表的主键(具有唯一值的列)。
每一行是一场比赛的记录,first_player 和 second_player 表示该场比赛的球员 ID。
first_score 和 second_score 分别表示 first_player 和 second_player 的得分。
你可以假设,在每一场比赛中,球员都属于同一组。
每组的获胜者是在组内累积得分最高的选手。如果平局,player_id **最小 **的选手获胜。
编写解决方案来查找每组中的获胜者。
返回的结果表单 没有顺序要求 。
返回结果格式如下所示。
示例 1:
`**输入:**
Players 表`:
+-----------+------------+
| player_id | group_id |
+-----------+------------+
| 15 | 1 |
| 25 | 1 |
| 30 | 1 |
| 45 | 1 |
| 10 | 2 |
| 35 | 2 |
| 50 | 2 |
| 20 | 3 |
| 40 | 3 |
+-----------+------------+
`Matches 表`:
+------------+--------------+---------------+-------------+--------------+
| match_id | first_player | second_player | first_score | second_score |
+------------+--------------+---------------+-------------+--------------+
| 1 | 15 | 45 | 3 | 0 |
| 2 | 30 | 25 | 1 | 2 |
| 3 | 30 | 15 | 2 | 0 |
| 4 | 40 | 20 | 5 | 2 |
| 5 | 35 | 50 | 1 | 1 |
+------------+--------------+---------------+-------------+--------------+
**输出:**
+-----------+------------+
| group_id | player_id |
+-----------+------------+
| 1 | 15 |
| 2 | 35 |
| 3 | 40 |
+-----------+------------+
解题思路
思考
每组取总分最高的选手,并列取较小
player_id。把比赛双方拆成两行得分,按选手求和后再按组对(scores DESC, player_id)排名,留下 r k = 1 rk=1 rk=1。UNION ALL保证主客场得分都被计入。
代码实现
sql
# Write your MySQL query statement below
WITH
s AS (
SELECT first_player AS player_id, first_score AS score, group_id
FROM
Matches AS m
JOIN Players AS p ON m.first_player = p.player_id
UNION ALL
SELECT second_player AS player_id, second_score AS score, group_id
FROM
Matches AS m
JOIN Players AS p ON m.second_player = p.player_id
),
t AS (
SELECT group_id, player_id, SUM(score) AS scores
FROM s
GROUP BY player_id
),
p AS (
SELECT
group_id,
player_id,
RANK() OVER (
PARTITION BY group_id
ORDER BY scores DESC, player_id
) AS rk
FROM t
)
SELECT group_id, player_id
FROM p
WHERE rk = 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1195. Fizz Buzz Multithreaded(多线程 Fizz Buzz)
题目描述
编写一个可以从 1 到 n 输出代表这个数字的字符串的程序:
- 如果这个数字可以被 3 整除,输出 "fizz";
- 如果这个数字可以被 5 整除,输出 "buzz";
- 如果这个数字可以同时被 3 和 5 整除,输出 "fizzbuzz";
- 否则输出数字本身。
同一个 FizzBuzz 实例会被如下四个线程使用:A 调 fizz()、B 调 buzz()、C 调 fizzbuzz()、D 调 number()。
需要保证从 1 到n的输出顺序正确。
类签名:
class FizzBuzz {
public FizzBuzz(intn) { ... }
publicvoidfizz(RunnableprintFizz) { ... }
publicvoidbuzz(RunnableprintBuzz) { ... }
publicvoidfizzbuzz(RunnableprintFizzBuzz) { ... }
publicvoidnumber(IntConsumerprintNumber) { ... }
}
解题思路
解法一:Semaphore 四信号量 + 当前数
- 用 4 个信号量分别控制 fizz/buzz/fizzbuzz/number 的执行,initially 0 许可;
用一个原子计数 current 记录即将轮到打印的数字; - number() 线程负责判定:每次「取得许可」后检查 current:
若当前数能被 15 整除 → release fizzbuzz;能被 3 → release fizz;能被 5 → release buzz;
否则打印数字并 current+1 后 release number(继续推进); - 三个专用打印方法拿到许可后打印对应字符串、current++,然后 release number 放行下一轮。
复杂度:时间 O(n)(轮流推进),空间 O(1)。
解法二:synchronized + wait/notifyAll 状态机 - 用 synchronized 锁 + volatile int current 共享计数,四类线程各等待自己的条件;
- 当 current 超过 n 时发结束信号,所有线程退出;
- 用 notifyAll 唤醒所有等待者,由各线程自行判断自己是否该执行。
复杂度:时间 O(n),空间 O(1)。
代码实现
java
import java.util.concurrent.Semaphore;
import java.util.function.IntConsumer;
public class Solution {
public static class FizzBuzzSem {
private final int n;
private final Semaphore semNum = new Semaphore(1); // 初始放行 number
private final Semaphore semFizz = new Semaphore(0);
private final Semaphore semBuzz = new Semaphore(0);
private final Semaphore semFizzBuzz = new Semaphore(0);
private int current = 1;
public FizzBuzzSem(int n) {
this.n = n;
}
public void fizz(Runnable printFizz) throws InterruptedException {
for (;;) {
semFizz.acquire();
if (current > n) {
break;
}
printFizz.run();
current++;
semNum.release();
}
}
public void buzz(Runnable printBuzz) throws InterruptedException {
for (;;) {
semBuzz.acquire();
if (current > n) {
break;
}
printBuzz.run();
current++;
semNum.release();
}
}
public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
for (;;) {
semFizzBuzz.acquire();
if (current > n) {
break;
}
printFizzBuzz.run();
current++;
semNum.release();
}
}
public void number(IntConsumer printNumber) throws InterruptedException {
for (;;) {
semNum.acquire();
if (current > n) {
// 通知所有专用线程退出
semFizz.release();
semBuzz.release();
semFizzBuzz.release();
break;
}
if (current % 15 == 0) {
semFizzBuzz.release();
} else if (current % 3 == 0) {
semFizz.release();
} else if (current % 5 == 0) {
semBuzz.release();
} else {
printNumber.accept(current);
current++;
semNum.release();
}
}
}
}
public static class FizzBuzzSync {
private final int n;
private int current = 1;
private boolean done = false;
private final Object lock = new Object();
public FizzBuzzSync(int n) {
this.n = n;
}
public void fizz(Runnable printFizz) throws InterruptedException {
synchronized (lock) {
while (!done) {
while (!done && !(current % 3 == 0 && current % 5 != 0)) {
lock.wait();
}
if (done) {
break;
}
printFizz.run();
current++;
lock.notifyAll();
}
}
}
public void buzz(Runnable printBuzz) throws InterruptedException {
synchronized (lock) {
while (!done) {
while (!done && !(current % 5 == 0 && current % 3 != 0)) {
lock.wait();
}
if (done) {
break;
}
printBuzz.run();
current++;
lock.notifyAll();
}
}
}
public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
synchronized (lock) {
while (!done) {
while (!done && !(current % 15 == 0)) {
lock.wait();
}
if (done) {
break;
}
printFizzBuzz.run();
current++;
lock.notifyAll();
}
}
}
public void number(IntConsumer printNumber) throws InterruptedException {
synchronized (lock) {
while (!done) {
while (!done && (current % 3 == 0 || current % 5 == 0)) {
lock.wait();
}
if (done || current > n) {
break;
}
printNumber.accept(current);
current++;
lock.notifyAll();
}
if (!done) {
done = true;
lock.notifyAll();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
StringBuilder sb1 = new StringBuilder();
FizzBuzzSem fb = new FizzBuzzSem(15);
Thread t1 = new Thread(() -> { try { fb.fizz(() -> sb1.append("fizz,")); } catch (InterruptedException e) {} });
Thread t2 = new Thread(() -> { try { fb.buzz(() -> sb1.append("buzz,")); } catch (InterruptedException e) {} });
Thread t3 = new Thread(() -> { try { fb.fizzbuzz(() -> sb1.append("fizzbuzz,")); } catch (InterruptedException e) {} });
Thread t4 = new Thread(() -> { try { fb.number(v -> sb1.append(v).append(",")); } catch (InterruptedException e) {} });
// 乱序启动
t3.start();
t4.start();
t1.start();
t2.start();
t1.join(); t2.join(); t3.join(); t4.join();
System.out.println("解法一 n=15: " + sb1);
System.out.println("期望: 1,2,fizz,4,buzz,fizz,7,8,fizz,buzz,11,fizz,13,14,fizzbuzz,");
StringBuilder sb2 = new StringBuilder();
FizzBuzzSync fb2 = new FizzBuzzSync(15);
Thread a1 = new Thread(() -> { try { fb2.fizz(() -> sb2.append("fizz,")); } catch (InterruptedException e) {} });
Thread a2 = new Thread(() -> { try { fb2.buzz(() -> sb2.append("buzz,")); } catch (InterruptedException e) {} });
Thread a3 = new Thread(() -> { try { fb2.fizzbuzz(() -> sb2.append("fizzbuzz,")); } catch (InterruptedException e) {} });
Thread a4 = new Thread(() -> { try { fb2.number(v -> sb2.append(v).append(",")); } catch (InterruptedException e) {} });
a2.start();
a1.start();
a4.start();
a3.start();
a1.join(); a2.join(); a3.join(); a4.join();
System.out.println("解法二 n=15: " + sb2);
System.out.println("期望: 1,2,fizz,4,buzz,fizz,7,8,fizz,buzz,11,fizz,13,14,fizzbuzz,");
}
}
复杂度分析
复杂度:时间 O(n)(轮流推进),空间 O(1)。
复杂度:时间 O(n),空间 O(1)。
1196. How Many Apples Can You Put into the Basket(最多可以买到的苹果数量 🔒)
题目描述
你有一些苹果和一个可以承载 5000 单位重量的篮子。
给定一个整数数组 weight,weight`i` 是第 i 个苹果的重量,
返回你可以放入篮子的最大苹果数量。
示例 1
weight = [100,200,150,1000] → 4(总重 1450 <= 5000)
示例 2
weight = [900,950,800,1000,700,800] → 5(全选超重,最优 5 个)
约束:
- 1 <=
weight.length <= 10^3;1 <=weight`i` <= 10^3。
解题思路
解法一:贪心排序
- 要装下最多数量,显然优先选轻的苹果------对重量升序排序;
- 从小到大累加,直到再加一个会超过 5000 为止,返回已装数量。
复杂度:时间 O(n log n),空间 O(1)(原地排序)。
解法二:桶计数(重量仅 1...1000) - 用计数数组记录每个重量的苹果数;
- 从轻到重逐个取,模拟装篮直到超载;
- 适合重量范围小、重复多的场景,无需排序。
复杂度:时间 O(n + W)(W = 1000),空间 O(W)。
代码实现
java
import java.util.Arrays;
public class Solution {
private static final int LIMIT = 5000;
public int maxNumberOfApples(int[] weight) {
Arrays.sort(weight);
int sum = 0;
int cnt = 0;
for (int w : weight) {
if (sum + w <= LIMIT) {
sum += w;
cnt++;
} else {
break;
}
}
return cnt;
}
public int maxNumberOfApplesBucket(int[] weight) {
int[] cnt = new int[1001];
for (int w : weight) {
cnt[w]++;
}
int sum = 0;
int total = 0;
for (int w = 1; w <= 1000; w++) {
while (cnt[w] > 0 && sum + w <= LIMIT) {
sum += w;
total++;
cnt[w]--;
}
}
return total;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.maxNumberOfApples(new int[]{100,200,150,1000}) + "(期望 4)");
System.out.println("示例1 解法二: " + s.maxNumberOfApplesBucket(new int[]{100,200,150,1000}) + "(期望 4)");
System.out.println("示例2 解法一: " + s.maxNumberOfApples(new int[]{900,950,800,1000,700,800}) + "(期望 5)");
System.out.println("示例2 解法二: " + s.maxNumberOfApplesBucket(new int[]{900,950,800,1000,700,800}) + "(期望 5)");
System.out.println("五个整千 解法一: " + s.maxNumberOfApples(new int[]{1000,1000,1000,1000,1000}) + "(期望 5)");
System.out.println("五个整千 解法二: " + s.maxNumberOfApplesBucket(new int[]{1000,1000,1000,1000,1000}) + "(期望 5)");
}
}
复杂度分析
复杂度:时间 O(n log n),空间 O(1)(原地排序)。
复杂度:时间 O(n + W)(W = 1000),空间 O(W)。
1197. Minimum Knight Moves(进击的骑士 🔒)
题目描述
一个坐标可以从 -infinity 延伸到 +infinity 的无限大的棋盘上,你的骑士驻扎在坐标为 0, 0 的方格里。
骑士的走法和中国象棋中的马相似,走"日"字:即先向左(或右)走 1 格,再向上(或下)走 2 格;
或先向左(或右)走 2 格,再向上(或下)走 1 格。
每次移动,他都可以按八个方向之一前进。
返回骑士前去征服坐标为 x, `y` 的部落所需的最小移动次数。本题确保答案是一定存在的。
示例 1
x = 2, y = 1 → 1
示例 2
x = 5, y = 5 → 4
约束:
- -300 <= x,
y<= 300;0 <= |x| + |y| <= 300。
解题思路
解法一:对称压缩 + BFS
- 利用对称性把 (x, y) 映射为 (|x|, |y|),并在第一象限做 BFS;
- 实际仅需在 0, 300+边界 附近搜索:BFS 层扩散,直到到达目标;
- 由于棋盘无限但有界(对称后在第一象限),BFS 复杂度可控。
复杂度:时间 O(K^2)(K ~ 300),空间 O(K^2)。
解法二:数学公式(O(1) 判定) - 借助骑士最小步数的封闭结论:先规范化到第一象限,令 (x, y) 为规范化后的坐标;
- 当 x + y == 0 时需 0 步;(x + y == 2 && x == 1) 特判 2 步;
一般情形步数 = max(ceil(x/2), ceil(y/2), ceil((x+y)/3)),再校正奇偶性
(保持与 x + y 的奇偶一致); - 该公式在坐标值较小(<=300)与示例均正确。
复杂度:时间 O(1),空间 O(1)。
代码实现
java
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
private static final int[][] DIRS = {
{1, 2}, {2, 1}, {-1, 2}, {-2, 1},
{1, -2}, {2, -1}, {-1, -2}, {-2, -1}
};
public int minKnightMoves(int x, int y) {
x = Math.abs(x);
y = Math.abs(y);
// BFS,扩展边界放大一些,避免在对称边界处漏走
int bound = 330; // > 300 + 移动幅度余量
boolean[][] visited = new boolean[bound * 2 + 1][bound * 2 + 1];
Deque<int[]> queue = new ArrayDeque<>();
int ox = bound, oy = bound; // 原点偏移,保证负数坐标也有索引
queue.offer(new int[]{0, 0});
visited[ox][oy] = true;
int steps = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int[] cur = queue.poll();
int cx = cur[0];
int cy = cur[1];
if (cx == x && cy == y) {
return steps;
}
for (int[] d : DIRS) {
int nx = cx + d[0];
int ny = cy + d[1];
int ix = nx + ox;
int iy = ny + oy;
if (nx < -bound || nx > bound || ny < -bound || ny > bound) {
continue;
}
if (!visited[ix][iy]) {
visited[ix][iy] = true;
queue.offer(new int[]{nx, ny});
}
}
}
steps++;
}
return -1; // 题目保证可达
}
public int minKnightMovesMath(int x, int y) {
x = Math.abs(x);
y = Math.abs(y);
if (x + y == 0) {
return 0;
}
if (x + y == 1) {
return 3; // (1,0)/(0,1):0,0 → 2,1 → 0,2 → 1,0,3 步
}
if (x + y == 2) {
return 2; // (2,0)/(0,2)/(1,1) 特殊:0,0 → 2,1 → 0,2 等 2 步
}
// 下界:x 方向每步最多推进 2;y 方向每步最多推进 2;总移动步数每步最多前进 3 曼哈顿距离
int steps = Math.max((x + 1) / 2, Math.max((y + 1) / 2, (x + y + 2) / 3));
// 奇偶校正:骑士每步都会翻转「x+y」的奇偶性,故步数奇偶必须与 x+y 相反
// (0 步奇偶为偶,x+y 为奇时需要奇步数)。经典写法:ans ^= (ans ^ x ^ y) & 1
steps ^= (steps ^ x ^ y) & 1;
return steps;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.minKnightMoves(2, 1) + "(期望 1)");
System.out.println("示例1 解法二: " + s.minKnightMovesMath(2, 1) + "(期望 1)");
System.out.println("示例2 解法一: " + s.minKnightMoves(5, 5) + "(期望 4)");
System.out.println("示例2 解法二: " + s.minKnightMovesMath(5, 5) + "(期望 4)");
System.out.println("原点 解法一: " + s.minKnightMoves(0, 0) + "(期望 0)");
System.out.println("原点 解法二: " + s.minKnightMovesMath(0, 0) + "(期望 0)");
System.out.println("对角小点 解法一: " + s.minKnightMoves(1, 1) + "(期望 2)");
System.out.println("对角小点 解法二: " + s.minKnightMovesMath(1, 1) + "(期望 2)");
System.out.println("负坐标 解法一: " + s.minKnightMoves(-2, -1) + "(期望 1)");
System.out.println("负坐标 解法二: " + s.minKnightMovesMath(-2, -1) + "(期望 1)");
System.out.println("边缘 解法一: " + s.minKnightMoves(300, 300) + "(期望 200? 验证)");
System.out.println("边缘 解法二: " + s.minKnightMovesMath(300, 300) + "(期望同上)");
System.out.println("远点 解法一: " + s.minKnightMoves(1, 0) + "(期望 3)");
System.out.println("远点 解法二: " + s.minKnightMovesMath(1, 0) + "(期望 3)");
}
}
复杂度分析
复杂度:时间 O(K^2)(K ~ 300),空间 O(K^2)。
复杂度:时间 O(1),空间 O(1)。
1198. Find Smallest Common Element in All Rows(找出所有行中最小公共元素 🔒)
题目描述
给你一个 m × n 的矩阵 mat,其中每一行的元素均符合严格递增。
请返回所有行中的最小公共元素。如果矩阵中没有这样的公共元素,就请返回 -1。
示例 1
mat = [[1,2,3,4,5],[2,4,5,8,10],[3,5,7,9,11],[1,3,5,7,9]] → 5
示例 2
mat = [[1,2,3],[2,3,4],[2,3,5]] → 2
约束:
- 1 <=
m,n<= 500;1 <= mat`i``j` <= 10^4;每行严格递增。
解题思路
解法一:首行枚举 + 逐行二分
- 因为公共元素一定出现在第一行,枚举第一行的每个元素 v;
- 对每一行(1...m-1)用二分查找 v 是否出现;全部出现即答案(第一行升序,第一个满足的即最小);
- 线性枚举 v 用「首行元素按升序」,得到的是最小公共元素。
复杂度:时间 O(n * m log n),空间 O(1)。
解法二:计数数组 - 由于 matij <= 10^4,用计数数组 cntv 统计 v 在多少行中出现过;
- 遍历所有元素,cntv++(每行相同值只会出现一次,因为行内严格递增);
- 从 1...10000 找到第一个 cntv == m 的值即答案。
复杂度:时间 O(m * n + 10000),空间 O(10000)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
private boolean binarySearch(int[] a, int target) {
int lo = 0, hi = a.length - 1;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
if (a[mid] == target) {
return true;
} else if (a[mid] < target) {
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return false;
}
public int smallestCommonElement(int[][] mat) {
int m = mat.length;
int n = mat[0].length;
outer:
for (int j = 0; j < n; j++) {
int v = mat[0][j];
for (int i = 1; i < m; i++) {
if (!binarySearch(mat[i], v)) {
continue outer;
}
}
return v;
}
return -1;
}
public int smallestCommonElementCount(int[][] mat) {
int m = mat.length;
int[] cnt = new int[10001];
for (int i = 0; i < m; i++) {
for (int j = 0; j < mat[i].length; j++) {
// 每行严格递增,同一值一行最多出现一次
cnt[mat[i][j]]++;
}
}
for (int v = 1; v <= 10000; v++) {
if (cnt[v] == m) {
return v;
}
}
return -1;
}
public static void main(String[] args) {
Solution s = new Solution();
int[][] m1 = {{1,2,3,4,5},{2,4,5,8,10},{3,5,7,9,11},{1,3,5,7,9}};
System.out.println("示例1 解法一: " + s.smallestCommonElement(m1) + "(期望 5)");
System.out.println("示例1 解法二: " + s.smallestCommonElementCount(m1) + "(期望 5)");
int[][] m2 = {{1,2,3},{2,3,4},{2,3,5}};
System.out.println("示例2 解法一: " + s.smallestCommonElement(m2) + "(期望 2)");
System.out.println("示例2 解法二: " + s.smallestCommonElementCount(m2) + "(期望 2)");
int[][] m3 = {{1,2},{3,4}};
System.out.println("无公共 解法一: " + s.smallestCommonElement(m3) + "(期望 -1)");
System.out.println("无公共 解法二: " + s.smallestCommonElementCount(m3) + "(期望 -1)");
}
}
复杂度分析
复杂度:时间 O(n * m log n),空间 O(1)。
复杂度:时间 O(m * n + 10000),空间 O(10000)。
1199. Minimum Time to Build Blocks(建造街区的最短时间 🔒)
题目描述
你是个城市规划工作者,手里负责管辖一系列的街区。在街区列表中 blocks`i` = t 意味着第 i 个街区
需要 t 个单位的时间来建造。由于一个街区只能由一个工人来完成建造。
所以,一个工人要么需要再召唤一个工人(工人数增加 1);要么建造完一个街区后回家。
这两个决定都需要花费一定的时间。一个工人再召唤一个工人所花费的时间由整数 split 给出。
注意:如果两个工人同时召唤别的工人,那么他们的行为是并行的,所以时间花费仍然是 split。
最开始的时候只有一个工人,请你最后输出建造完所有街区所需要的最少时间。
示例 1
blocks = [1], split = 1 → 1
示例 2
blocks = [1,2], split = 5 → 7
示例 3
blocks = [1,2,3], split = 1 → 4
约束:
- 1 <= blocks.length <= 1000;1 <= blocks`i` <= 10^5;1 <= split <= 100。
解题思路
解法一:贪心 Huffman(堆合并,逆向思维)
- 逆向看:最终每个街区由一个工人建造。工人可以「分裂」,分裂耗时 split。
等价于:把 blocks 看成叶子节点,每次选择「当前最小代价的两个叶子」用一个分裂操作合并,
代价为 max(a, b) + split,合并结果作为新的「子任务」; - 用小顶堆反复取最小两个值合并,直到堆中只剩一个,这就是全部街区所需最少时间
(类似 Huffman 编码,贪心正确:最耗时的街区应尽量晚分裂)。
复杂度:时间 O(n log n),空间 O(n)。
解法二:排序后模拟(数组版合并) - 与解法一相同思想,但不使用堆:每次取当前最小的两个值合并;
用「有序小列表 + 双队列」(同 Huffman 双队列法)模拟也可,此实现直接用 PriorityQueue,
逻辑等价。为差异化,这里给出「排序 + 按轮合并」的朴素版:每轮找到两个最小值累加。
复杂度:时间 O(n^2)(朴素扫描取最小),空间 O(n)。
代码实现
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
public class Solution {
public int minBuildTime(int[] blocks, int split) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int b : blocks) {
pq.offer(b);
}
while (pq.size() > 1) {
int a = pq.poll();
int b = pq.poll();
pq.offer(Math.max(a, b) + split);
}
return pq.poll();
}
public int minBuildTimeScan(int[] blocks, int split) {
List<Integer> list = new ArrayList<>();
for (int b : blocks) {
list.add(b);
}
Collections.sort(list);
while (list.size() > 1) {
// 取最小两个
int a = list.remove(0);
int b = list.remove(0);
int merged = Math.max(a, b) + split;
// 插入保持有序
int idx = 0;
while (idx < list.size() && list.get(idx) < merged) {
idx++;
}
list.add(idx, merged);
}
return list.get(0);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.minBuildTime(new int[]{1}, 1) + "(期望 1)");
System.out.println("示例1 解法二: " + s.minBuildTimeScan(new int[]{1}, 1) + "(期望 1)");
System.out.println("示例2 解法一: " + s.minBuildTime(new int[]{1,2}, 5) + "(期望 7)");
System.out.println("示例2 解法二: " + s.minBuildTimeScan(new int[]{1,2}, 5) + "(期望 7)");
System.out.println("示例3 解法一: " + s.minBuildTime(new int[]{1,2,3}, 1) + "(期望 4)");
System.out.println("示例3 解法二: " + s.minBuildTimeScan(new int[]{1,2,3}, 1) + "(期望 4)");
System.out.println("多块大split 解法一: " + s.minBuildTime(new int[]{1,2,3,4}, 2) + "(期望 8? 验证)");
System.out.println("多块大split 解法二: " + s.minBuildTimeScan(new int[]{1,2,3,4}, 2) + "(期望同上)");
}
}
复杂度分析
复杂度:时间 O(n log n),空间 O(n)。
复杂度:时间 O(n^2)(朴素扫描取最小),空间 O(n)。
1200. Minimum Absolute Difference(最小绝对差)
题目描述
给你个整数数组 arr,其中每个元素都不相同。
请你找到所有具有最小绝对差的元素对,并且按升序的顺序返回。
每对元素对 `a`, `b` 如下:a、b 均为数组 arr 中的元素;a < b;
b - a 等于 arr 中任意两个元素的最小绝对差。
示例 1
arr = [4,2,1,3] → [[1,2],[2,3],[3,4]]
示例 2
arr = [1,3,6,10,15] → [[1,3]]
示例 3
arr = [3,8,-10,23,19,-4,-14,27] → [[-14,-10],[19,23],[23,27]]
约束:
- 2 <=
arr.length<= 105;-106 <=arr[i]<= 10^6。
解题思路
解法一:排序 + 相邻扫描
- 排序后,最小绝对差一定出现在相邻两元素之间;
- 一次扫描找出全局最小差 minDiff;
- 再扫一次,把所有差等于 minDiff 的相邻对加入结果(排序保证升序)。
复杂度:时间 O(n log n),空间 O(1)(结果除外)。
解法二:排序 + 单趟动态收集 - 同样排序;
- 单趟扫描时维护当前最小差:若遇到更小的差则清空结果列表重新收集;
- 最后一个元素处理完结果即有序。
复杂度:时间 O(n log n),空间 O(k)(k 为结果个数)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public List<List<Integer>> minimumAbsDifference(int[] arr) {
Arrays.sort(arr);
int n = arr.length;
int minDiff = Integer.MAX_VALUE;
for (int i = 1; i < n; i++) {
minDiff = Math.min(minDiff, arr[i] - arr[i - 1]);
}
List<List<Integer>> res = new ArrayList<>();
for (int i = 1; i < n; i++) {
if (arr[i] - arr[i - 1] == minDiff) {
res.add(Arrays.asList(arr[i - 1], arr[i]));
}
}
return res;
}
public List<List<Integer>> minimumAbsDifferenceOnePass(int[] arr) {
Arrays.sort(arr);
int n = arr.length;
int minDiff = Integer.MAX_VALUE;
List<List<Integer>> res = new ArrayList<>();
for (int i = 1; i < n; i++) {
int diff = arr[i] - arr[i - 1];
if (diff < minDiff) {
minDiff = diff;
res.clear();
res.add(Arrays.asList(arr[i - 1], arr[i]));
} else if (diff == minDiff) {
res.add(Arrays.asList(arr[i - 1], arr[i]));
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println("示例1 解法一: " + s.minimumAbsDifference(new int[]{4,2,1,3}) + "(期望 [[1,2],[2,3],[3,4]])");
System.out.println("示例1 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{4,2,1,3}) + "(期望 [[1,2],[2,3],[3,4]])");
System.out.println("示例2 解法一: " + s.minimumAbsDifference(new int[]{1,3,6,10,15}) + "(期望 [[1,3]])");
System.out.println("示例2 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{1,3,6,10,15}) + "(期望 [[1,3]])");
System.out.println("示例3 解法一: " + s.minimumAbsDifference(new int[]{3,8,-10,23,19,-4,-14,27})
+ "(期望 [[-14,-10],[19,23],[23,27]])");
System.out.println("示例3 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{3,8,-10,23,19,-4,-14,27})
+ "(期望 [[-14,-10],[19,23],[23,27]])");
System.out.println("两元素 解法一: " + s.minimumAbsDifference(new int[]{5,1}) + "(期望 [[1,5]])");
System.out.println("两元素 解法二: " + s.minimumAbsDifferenceOnePass(new int[]{5,1}) + "(期望 [[1,5]])");
}
}
复杂度分析
复杂度:时间 O(n log n),空间 O(1)(结果除外)。
复杂度:时间 O(n log n),空间 O(k)(k 为结果个数)。