LeetCode 1401-1420. 题解合集
文章目录
- [LeetCode 1401-1420. 题解合集](#LeetCode 1401-1420. 题解合集)
-
- [1401. Circle and Rectangle Overlapping(圆和矩形是否有重叠)](#1401. Circle and Rectangle Overlapping(圆和矩形是否有重叠))
- [1402. Reducing Dishes(做菜顺序)](#1402. Reducing Dishes(做菜顺序))
- [1403. Minimum Subsequence in Non-Increasing Order(非递增顺序的最小子序列)](#1403. Minimum Subsequence in Non-Increasing Order(非递增顺序的最小子序列))
- [1404. Number of Steps to Reduce a Number in Binary Representation to One(将二进制表示减到 1 的步骤数)](#1404. Number of Steps to Reduce a Number in Binary Representation to One(将二进制表示减到 1 的步骤数))
- [1405. Longest Happy String(最长快乐字符串)](#1405. Longest Happy String(最长快乐字符串))
- [1406. Stone Game III(石子游戏 III)](#1406. Stone Game III(石子游戏 III))
- [1407. Top Travellers(排名靠前的旅行者)](#1407. Top Travellers(排名靠前的旅行者))
- [1408. String Matching in an Array(数组中的字符串匹配)](#1408. String Matching in an Array(数组中的字符串匹配))
- [1409. Queries on a Permutation With Key(查询带键的排列)](#1409. Queries on a Permutation With Key(查询带键的排列))
- [1410. HTML Entity Parser(HTML 实体解析器)](#1410. HTML Entity Parser(HTML 实体解析器))
- [1411. Number of Ways to Paint N × 3 Grid(给 N x 3 网格图涂色的方案数)](#1411. Number of Ways to Paint N × 3 Grid(给 N x 3 网格图涂色的方案数))
- [1412. Find the Quiet Students in All Exams(查找成绩处于中游的学生 🔒)](#1412. Find the Quiet Students in All Exams(查找成绩处于中游的学生 🔒))
- [1413. Minimum Value to Get Positive Step by Step Sum(逐步求和得到正数的最小值)](#1413. Minimum Value to Get Positive Step by Step Sum(逐步求和得到正数的最小值))
- [1414. Find the Minimum Number of Fibonacci Numbers Whose Sum Is K(和为 K 的最少斐波那契数字数目)](#1414. Find the Minimum Number of Fibonacci Numbers Whose Sum Is K(和为 K 的最少斐波那契数字数目))
- [1415. The k-th Lexicographical String of All Happy Strings of Length n(长度为 n 的开心字符串中字典序第 k 小的字符串)](#1415. The k-th Lexicographical String of All Happy Strings of Length n(长度为 n 的开心字符串中字典序第 k 小的字符串))
- [1416. Restore The Array(恢复数组)](#1416. Restore The Array(恢复数组))
- [1417. Reformat The String(重新格式化字符串)](#1417. Reformat The String(重新格式化字符串))
- [1418. Display Table of Food Orders in a Restaurant(点菜展示表)](#1418. Display Table of Food Orders in a Restaurant(点菜展示表))
- [1419. Minimum Number of Frogs Croaking(数青蛙)](#1419. Minimum Number of Frogs Croaking(数青蛙))
- [1420. Build Array Where You Can Find The Maximum Exactly K Comparisons(生成数组)](#1420. Build Array Where You Can Find The Maximum Exactly K Comparisons(生成数组))
1401. Circle and Rectangle Overlapping(圆和矩形是否有重叠)
题目描述
给你一个以 (radius, xCenter, yCenter) 表示的圆和一个与坐标轴平行的矩形 (x1, y1, x2, y2),
其中 (x1, y1) 是矩形左下角的坐标,而 (x2, y2) 是右上角的坐标。
如果圆和矩形有重叠的部分,请你返回 true,否则返回 false。
换句话说,请你检测是否存在点 (xi, yi),它既在圆上也在矩形上(两者都包括点落在边界上的情况)。
示例 1
radius = 1, xCenter = 0, yCenter = 0, x1 = 1, y1 = -1, x2 = 3, y2 = 1 -> true
示例 2
radius = 1, xCenter = 1, yCenter = 1, x1 = 1, y1 = -3, x2 = 2, y2 = -1 -> false
示例 3
radius = 1, xCenter = 0, yCenter = 0, x1 = -1, y1 = 0, x2 = 0, y2 = 1 -> true
约束:
- 1 <= radius <= 2000;-10^4 <=
xCenter,yCenter, x1, y1, x2, y2 <= 10^4。
解题思路
解法一:圆心到矩形的最近距离(主提交版本)
矩形内离圆心最近的点 P = (clamp(xCenter, x1, x2), clamp(yCenter, y1, y2))。
圆与矩形有交当且仅当圆心到 P 的距离 <= radius。
复杂度:O(1)。
解法二:圆心在矩形内 + 四边距离判定(对照)
若圆心在矩形内直接返回 true;否则分别计算圆心到矩形左/右/上/下四条边
(线段)的最近距离,取最小值判断是否 <= radius。
复杂度:O(1)。
代码实现
java
public class Solution {
public boolean checkOverlap(int radius, int xCenter, int yCenter,
int x1, int y1, int x2, int y2) {
long nx = clamp(xCenter, x1, x2);
long ny = clamp(yCenter, y1, y2);
long dx = xCenter - nx;
long dy = yCenter - ny;
return dx * dx + dy * dy <= (long) radius * radius;
}
private long clamp(int x, int lo, int hi) {
if (x < lo) {
return lo;
}
if (x > hi) {
return hi;
}
return x;
}
public boolean checkOverlapEdges(int radius, int xCenter, int yCenter,
int x1, int y1, int x2, int y2) {
if (xCenter >= x1 && xCenter <= x2 && yCenter >= y1 && yCenter <= y2) {
return true; // 圆心在矩形内
}
long r2 = (long) radius * radius;
// 左右两条竖边(线段):x=x1 或 x2,y 从 y1 到 y2
if (distToSegment(xCenter, yCenter, x1, y1, x1, y2) <= r2) {
return true;
}
if (distToSegment(xCenter, yCenter, x2, y1, x2, y2) <= r2) {
return true;
}
// 上下两条横边:y=y1 或 y2,x 从 x1 到 x2
if (distToSegment(xCenter, yCenter, x1, y1, x2, y1) <= r2) {
return true;
}
return distToSegment(xCenter, yCenter, x1, y2, x2, y2) <= r2;
}
private long distToSegment(long px, long py, long ax, long ay, long bx, long by) {
long dx = bx - ax;
long dy = by - ay;
// 线段长度平方
long len2 = dx * dx + dy * dy;
long t;
if (len2 == 0) {
t = 0;
} else {
long dot = (px - ax) * dx + (py - ay) * dy;
t = dot;
if (t < 0) {
t = 0;
} else if (t > len2) {
t = len2;
}
}
long cx = ax + dx * t / (len2 == 0 ? 1 : len2);
long cy = ay + dy * t / (len2 == 0 ? 1 : len2);
long ex = px - cx;
long ey = py - cy;
return ex * ex + ey * ey;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.checkOverlap(1, 0, 0, 1, -1, 3, 1)); // true
System.out.println(s.checkOverlap(1, 1, 1, 1, -3, 2, -1)); // false
System.out.println(s.checkOverlap(1, 0, 0, -1, 0, 0, 1)); // true
System.out.println(s.checkOverlap(5, 0, 5, 1, 2, 3, 4)); // true
System.out.println(s.checkOverlapEdges(1, 0, 0, 1, -1, 3, 1)); // true
System.out.println(s.checkOverlapEdges(1, 1, 1, 1, -3, 2, -1)); // false
System.out.println(s.checkOverlapEdges(1, 0, 0, -1, 0, 0, 1)); // true
System.out.println(s.checkOverlapEdges(5, 0, 5, 1, 2, 3, 4)); // true
}
}
复杂度分析
复杂度:O(1)。
1402. Reducing Dishes(做菜顺序)
题目描述
一个厨师收集了他 n 道菜的满意程度 satisfaction,这个厨师做出每道菜的时间都是 1 单位时间。
一道菜的「like-time 系数」定义为烹饪这道菜结束的时间(包含之前每道菜所花费的时间)乘以
这道菜的满意程度,也就是 timei * satisfactioni。
返回厨师在准备了一定数量的菜肴后可以获得的最大 like-time 系数总和。
你可以按任意顺序安排做菜的顺序,你也可以选择放弃做某些菜来获得更大的总和。
示例 1
satisfaction = [-1,-8,0,5,-9] -> 14
示例 2
satisfaction = [4,3,2] -> 20
示例 3
satisfaction = [-1,-4,-5] -> 0
约束:
- 1 <= satisfaction.length <= 500;-10^3 <= satisfactioni <= 10^3。
解题思路
解法一:贪心排序 + 后缀选择(主提交版本)
做菜顺序一定是 satisfaction 升序排列后选一个后缀(从某一位置开始连续做到末尾)。
对升序排序后的数组,从末尾往前累加,若当前总贡献增加则保留,否则停止。
因为一旦加入一个负数使前缀和变负,再往左只会更差。
复杂度:O(n log n),空间 O(1)。
解法二:枚举起点(对照)
升序排序后枚举每个起点 i,计算从 i 做到末尾的 like-time 系数总和,取最大值。
复杂度:O(n^2),仅作正确性对照。
代码实现
java
import java.util.Arrays;
public class Solution {
public int maxSatisfaction(int[] satisfaction) {
Arrays.sort(satisfaction);
int n = satisfaction.length;
int ans = 0;
int sum = 0; // 当前后缀的元素和(决定加入左边的菜是否值得)
int total = 0; // 当前 like-time 系数总和
for (int i = n - 1; i >= 0; i--) {
sum += satisfaction[i];
if (sum <= 0) {
break; // 加入该菜后整体贡献不再增加
}
total += sum;
}
return total;
}
public int maxSatisfactionBrute(int[] satisfaction) {
Arrays.sort(satisfaction);
int n = satisfaction.length;
int best = 0;
for (int i = 0; i < n; i++) {
int cur = 0;
int time = 1;
for (int j = i; j < n; j++) {
cur += time * satisfaction[j];
time++;
}
best = Math.max(best, cur);
}
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxSatisfaction(new int[]{-1, -8, 0, 5, -9})); // 14
System.out.println(s.maxSatisfaction(new int[]{4, 3, 2})); // 20
System.out.println(s.maxSatisfaction(new int[]{-1, -4, -5})); // 0
System.out.println(s.maxSatisfactionBrute(new int[]{-1, -8, 0, 5, -9})); // 14
System.out.println(s.maxSatisfactionBrute(new int[]{4, 3, 2})); // 20
System.out.println(s.maxSatisfactionBrute(new int[]{-1, -4, -5})); // 0
}
}
复杂度分析
复杂度:O(n log n),空间 O(1)。
复杂度:O(n^2),仅作正确性对照。
1403. Minimum Subsequence in Non-Increasing Order(非递增顺序的最小子序列)
题目描述
给你一个数组 nums,请你从中抽取一个子序列,满足该子序列的元素之和严格大于未包含在
该子序列中的各元素之和。
如果存在多个解决方案,只需返回长度最小的子序列。如果仍然有多个解决方案,则返回
元素之和最大的子序列。
注意:题目数据保证满足所有约束条件的解决方案是唯一的。同时,返回的答案应当按非递增顺序排列。
示例 1
nums = [4,3,10,9,8] -> [10,9]
示例 2
nums = [4,4,7,6,7] -> [7,7,6]
示例 3
nums = [6] -> [6]
约束:
- 1 <=
nums.length<= 500;1 <=nums[i]<= 100。
解题思路
解法一:降序贪心(主提交版本)
先把 nums 降序排序,从最大的元素开始累加,当累加和第一次 > 总和/2 时停止。
此时取出的元素数量最少,且因为从大到小取,元素和也最大,返回前缀即可。
复杂度:O(n log n),空间 O(n)。
解法二:计数排序 + 贪心(对照)
值域 ≤ 100,用桶计数,从大到小累加直到过半,逻辑一致但更贴近值域特性。
复杂度:O(n + V),空间 O(V)。
代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public List<Integer> minSubsequence(int[] nums) {
Arrays.sort(nums);
int total = 0;
for (int v : nums) {
total += v;
}
List<Integer> res = new ArrayList<>();
int sum = 0;
for (int i = nums.length - 1; i >= 0; i--) {
sum += nums[i];
res.add(nums[i]);
if (sum * 2 > total) {
break;
}
}
return res;
}
public List<Integer> minSubsequenceCount(int[] nums) {
int[] cnt = new int[101];
int total = 0;
for (int v : nums) {
cnt[v]++;
total += v;
}
List<Integer> res = new ArrayList<>();
int sum = 0;
for (int v = 100; v >= 1; v--) {
while (cnt[v] > 0 && sum * 2 <= total) {
sum += v;
res.add(v);
cnt[v]--;
}
if (sum * 2 > total) {
break;
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minSubsequence(new int[]{4, 3, 10, 9, 8})); // [10, 9]
System.out.println(s.minSubsequence(new int[]{4, 4, 7, 6, 7})); // [7, 7, 6]
System.out.println(s.minSubsequence(new int[]{6})); // [6]
System.out.println(s.minSubsequenceCount(new int[]{4, 3, 10, 9, 8})); // [10, 9]
System.out.println(s.minSubsequenceCount(new int[]{4, 4, 7, 6, 7})); // [7, 7, 6]
System.out.println(s.minSubsequenceCount(new int[]{6})); // [6]
}
}
复杂度分析
复杂度:O(n log n),空间 O(n)。
复杂度:O(n + V),空间 O(V)。
1404. Number of Steps to Reduce a Number in Binary Representation to One(将二进制表示减到 1 的步骤数)
题目描述
给你一个以二进制形式表示整数 s 的字符串,返回将其按下列规则减到 1 所需要的步骤数:
- 如果当前数为偶数,则将其除以 2。
- 如果当前数为奇数,则将其加 1。
题目保证所有测试用例都可以到达 1。
示例 1
s = "1101" -> 6(13 -> 14 -> 7 -> 8 -> 4 -> 2 -> 1)
示例 2
s = "10" -> 1
示例 3
s = "1" -> 0
约束:
- 1 <=
s.length<= 500;s不含前导零。
解题思路
解法一:模拟二进制位操作(主提交版本)
从字符串最后一位开始模拟:
- 若末尾为 '0'(偶数):除以 2 即去掉末尾,steps++;
- 若末尾为 '1'(奇数):加 1 会产生进位------从当前位向前找到第一个 '0' 置 1、其后的
低位全变 0(实际是字符串进位);若全为 1 则变成 100...0,steps++ 并继续。
处理到只剩 "1" 为止。复杂度:O(n^2) 最坏(进位引起复制),n ≤ 500 可接受。
解法二:位长 + 统计规律(对照)
观察规律:从字符串高位到低位,遇到 '1' 都会额外产生一次「加 1」进位操作。
答案 = 字符串长度(减 1 的除法次数)+ 1 的个数 - 首位的 1 需要特判。
更精确公式:ans = len - 1 + count1 - (last 位为 0 时不需要额外处理)。
采用逐位分析:steps = (n - 1) + (# of '1' after first) + (是否有进位时需要多一步)。
代码实现
java
public class Solution {
public int numSteps(String s) {
StringBuilder sb = new StringBuilder(s);
int steps = 0;
while (sb.length() > 1) {
int last = sb.length() - 1;
if (sb.charAt(last) == '0') {
// 偶数:除以 2
sb.deleteCharAt(last);
} else {
// 奇数:加 1(模拟进位)
int i = last;
while (i >= 0 && sb.charAt(i) == '1') {
sb.setCharAt(i, '0');
i--;
}
if (i < 0) {
sb.insert(0, '1');
} else {
sb.setCharAt(i, '1');
}
}
steps++;
}
return steps;
}
public int numStepsFormula(String s) {
int n = s.length();
int steps = 0;
int carry = 0; // 进位标记:若低位曾经触发过加 1,则高位处理会受影响
for (int i = n - 1; i >= 1; i--) {
int digit = (s.charAt(i) - '0') + carry;
if (digit % 2 == 0) {
// 偶数:除 2
steps++;
} else {
// 奇数:加 1 再除 2(两步操作计 2)
steps += 2;
carry = 1;
}
}
// 处理最高位:若 s[0]=1 且 carry=1,则最后一次加 1 使长度 +1 后再除 2 共 1 步
return steps + (s.charAt(0) == '1' && carry == 1 ? 1 : 0);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.numSteps("1101")); // 6
System.out.println(s.numSteps("10")); // 1
System.out.println(s.numSteps("1")); // 0
System.out.println(s.numSteps("111")); // 4? 校验:7->8->4->2->1 = 4
System.out.println(s.numStepsFormula("1101")); // 6
System.out.println(s.numStepsFormula("10")); // 1
System.out.println(s.numStepsFormula("1")); // 0
System.out.println(s.numStepsFormula("111")); // 4
}
}
复杂度分析
1405. Longest Happy String(最长快乐字符串)
题目描述
如果字符串中不含有任何 'aaa'、'bbb' 或 'ccc' 这样的字符串作为子串,那么该字符串就是一个
「快乐字符串」。给你三个整数 a、b、c,请你返回任意一个满足下列全部条件的字符串 s:
s是一个尽可能长的快乐字符串;s中最多有a个字母 'a'、b个字母 'b'、c个字母 'c';s中只含有 'a'、'b'、'c' 三种字母。
如果不存在这样的字符串s,请返回一个空字符串 ""。
示例 1
a = 1, b = 1, c = 7 -> "ccaccbcc"
示例 2
a = 7, b = 1, c = 0 -> "aabaa"
约束:
- 0 <=
a,b,c<= 100。
解题思路
解法一:贪心 + 优先队列(主提交版本)
每次选择剩余数量最多的字母,但若它已经连续出现两次,则改选次多的字母。
用最大堆维护 (剩余数量, 字母)。循环直到无法添加。
复杂度:O((a+b+c) * log 3) = O(n)。
解法二:朴素贪心交替选取(对照)
同样的「最多优先、避免三连」逻辑,用三个计数器手动实现,不借助堆。
复杂度:O(n)。
代码实现
java
import java.util.PriorityQueue;
public class Solution {
public String longestDiverseString(int a, int b, int c) {
PriorityQueue<int[]> pq = new PriorityQueue<>((x, y) -> y[0] - x[0]);
if (a > 0) {
pq.offer(new int[]{a, 0}); // 0 -> 'a'
}
if (b > 0) {
pq.offer(new int[]{b, 1}); // 1 -> 'b'
}
if (c > 0) {
pq.offer(new int[]{c, 2}); // 2 -> 'c'
}
StringBuilder sb = new StringBuilder();
while (!pq.isEmpty()) {
int[] top = pq.poll();
int n = sb.length();
// 若与前两个字符相同,则改用次多的字母
if (n >= 2 && sb.charAt(n - 1) - 'a' == top[1] && sb.charAt(n - 2) - 'a' == top[1]) {
if (pq.isEmpty()) {
break; // 无次多字母可选
}
int[] second = pq.poll();
sb.append((char) ('a' + second[1]));
second[0]--;
if (second[0] > 0) {
pq.offer(second);
}
pq.offer(top); // 把最多的放回
} else {
sb.append((char) ('a' + top[1]));
top[0]--;
if (top[0] > 0) {
pq.offer(top);
}
}
}
return sb.toString();
}
public String longestDiverseStringManual(int a, int b, int c) {
StringBuilder sb = new StringBuilder();
int[] cnt = {a, b, c};
while (true) {
int prev = sb.length() > 0 ? sb.charAt(sb.length() - 1) - 'a' : -1;
// 选剩余最多且不与前两个相同的字符
int pick = -1;
int best = 0;
for (int i = 0; i < 3; i++) {
if (cnt[i] <= 0) {
continue;
}
if (sb.length() >= 2
&& sb.charAt(sb.length() - 1) - 'a' == i
&& sb.charAt(sb.length() - 2) - 'a' == i) {
continue; // 会形成三连
}
if (cnt[i] > best) {
best = cnt[i];
pick = i;
}
}
if (pick == -1) {
break;
}
sb.append((char) ('a' + pick));
cnt[pick]--;
}
return sb.toString();
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.longestDiverseString(1, 1, 7)); // ccaccbcc 类
System.out.println(s.longestDiverseString(7, 1, 0)); // aabaa 类
System.out.println(s.longestDiverseString(0, 0, 0)); // ""
System.out.println(s.longestDiverseString(2, 2, 1)); // ababc 类
System.out.println(s.longestDiverseStringManual(1, 1, 7));
System.out.println(s.longestDiverseStringManual(7, 1, 0));
System.out.println(s.longestDiverseStringManual(0, 0, 0));
}
}
复杂度分析
复杂度:O((a+b+c) * log 3) = O(n)。
复杂度:O(n)。
1406. Stone Game III(石子游戏 III)
题目描述
Alice 和 Bob 继续他们的石子游戏。几堆石子排成一行,每堆石子都对应一个得分,由数组
stoneValue 给出。Alice 和 Bob 轮流取石子,Alice 总是先开始。在每个玩家的回合中,
该玩家可以拿走剩下石子中的前 1、2 或 3 堆石子。比赛一直持续到所有石头都被拿走。
每个玩家的最终得分为他所拿到的每堆石子的对应得分之和。每个玩家的初始分数都是 0。
比赛的目标是决出最高分,得分最高的选手将会赢得比赛,比赛也可能会出现平局。
假设 Alice 和 Bob 都采取最优策略。
如果 Alice 赢了就返回 "Alice",Bob 赢了就返回 "Bob",分数相同返回 "Tie"。
示例 1
values = [1,2,3,7] -> "Bob"
示例 2
values = [1,2,3,-9] -> "Alice"
示例 3
values = [1,2,3,6] -> "Tie"
约束:
- 1 <= values.length <= 5 * 10^4;-1000 <= valuesi <= 1000。
解题思路
解法一:倒序 DP + 差值(主提交版本)
dpi 表示「从 i 开始到末尾」当前行动者能获得的最大(得分差)。
dpi = max over k∈{1,2,3} of (prefixi+k - prefixi) - dpi+k。
即取走前 k 堆的分差 减去 对手在剩余部分的最大分差。
用后缀和避免重复计算。复杂度 O(n),空间 O(n)(可滚动优化到 O(1))。
解法二:正序记忆化递归(对照)
dfs(i) 返回从 i 开始的最大分差,带记忆化数组。逻辑同解法一,方向相反。
复杂度 O(n)。
代码实现
java
public class Solution {
public String stoneGameIII(int[] stoneValue) {
int n = stoneValue.length;
int[] suffix = new int[n + 1];
for (int i = n - 1; i >= 0; i--) {
suffix[i] = suffix[i + 1] + stoneValue[i];
}
int[] dp = new int[n + 2]; // dp[n+1] = 0
for (int i = n - 1; i >= 0; i--) {
int best = Integer.MIN_VALUE;
int take = 0;
for (int k = 1; k <= 3 && i + k <= n; k++) {
take += stoneValue[i + k - 1];
// 取 k 堆后对手的最大分差
best = Math.max(best, take - dp[i + k]);
}
dp[i] = best;
}
if (dp[0] > 0) {
return "Alice";
}
if (dp[0] < 0) {
return "Bob";
}
return "Tie";
}
public String stoneGameIIIMemo(int[] stoneValue) {
int n = stoneValue.length;
int[] suffix = new int[n + 1];
for (int i = n - 1; i >= 0; i--) {
suffix[i] = suffix[i + 1] + stoneValue[i];
}
int[] memo = new int[n + 1];
java.util.Arrays.fill(memo, Integer.MIN_VALUE);
int diff = dfs(0, stoneValue, suffix, memo);
if (diff > 0) {
return "Alice";
}
if (diff < 0) {
return "Bob";
}
return "Tie";
}
private int dfs(int i, int[] stoneValue, int[] suffix, int[] memo) {
if (i >= stoneValue.length) {
return 0;
}
if (memo[i] != Integer.MIN_VALUE) {
return memo[i];
}
int best = Integer.MIN_VALUE;
int take = 0;
for (int k = 1; k <= 3 && i + k <= stoneValue.length; k++) {
take += stoneValue[i + k - 1];
best = Math.max(best, take - dfs(i + k, stoneValue, suffix, memo));
}
memo[i] = best;
return best;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.stoneGameIII(new int[]{1, 2, 3, 7})); // Bob
System.out.println(s.stoneGameIII(new int[]{1, 2, 3, -9})); // Alice
System.out.println(s.stoneGameIII(new int[]{1, 2, 3, 6})); // Tie
System.out.println(s.stoneGameIII(new int[]{-1, -2, -3})); // Tie? 校验:-1-2-3 先手
// 校验:[-1,-2,-3],dp[0]: 取1个 -1 - dp[1]; 取2个 -3-dp[2]; 取3个 -6-dp[3]
// dp[3]=0... 逐步计算得 dp[0]=-2 双方最优会平?实际输出以运行为准
System.out.println(s.stoneGameIIIMemo(new int[]{1, 2, 3, 7})); // Bob
System.out.println(s.stoneGameIIIMemo(new int[]{1, 2, 3, -9})); // Alice
System.out.println(s.stoneGameIIIMemo(new int[]{1, 2, 3, 6})); // Tie
}
}
复杂度分析
用后缀和避免重复计算。复杂度 O(n),空间 O(n)(可滚动优化到 O(1))。
1407. Top Travellers(排名靠前的旅行者)
题目类型:数据库(SQL)
题目描述
表:Users
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| id | int |
| name | varchar |
+---------------+---------+
id 是该表中具有唯一值的列。
name 是用户名字。
表:Rides
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| id | int |
| user_id | int |
| distance | int |
+---------------+---------+
id 是该表中具有唯一值的列。
user_id 是本次行程的用户的 id, 而该用户此次行程距离为 distance 。
编写解决方案,报告每个用户的旅行距离。
返回的结果表单,以 travelled_distance 降序排列 ,如果有两个或者更多的用户旅行了相同的距离, 那么再以 name 升序排列 。
返回结果格式如下例所示。
示例 1:
**输入:**
Users 表:
+------+-----------+
| id | name |
+------+-----------+
| 1 | Alice |
| 2 | Bob |
| 3 | Alex |
| 4 | Donald |
| 7 | Lee |
| 13 | Jonathan |
| 19 | Elvis |
+------+-----------+
Rides 表:
+------+----------+----------+
| id | user_id | distance |
+------+----------+----------+
| 1 | 1 | 120 |
| 2 | 2 | 317 |
| 3 | 3 | 222 |
| 4 | 7 | 100 |
| 5 | 13 | 312 |
| 6 | 19 | 50 |
| 7 | 7 | 120 |
| 8 | 19 | 400 |
| 9 | 7 | 230 |
+------+----------+----------+
**输出:**
+----------+--------------------+
| name | travelled_distance |
+----------+--------------------+
| Elvis | 450 |
| Lee | 450 |
| Bob | 317 |
| Jonathan | 312 |
| Alex | 222 |
| Alice | 120 |
| Donald | 0 |
+----------+--------------------+
**解释:**
Elvis 和 Lee 旅行了 450 英里,Elvis 是排名靠前的旅行者,因为他的名字在字母表上的排序比 Lee 更小。
Bob, Jonathan, Alex 和 Alice 只有一次行程,我们只按此次行程的全部距离对他们排序。
Donald 没有任何行程, 他的旅行距离为 0。
解题思路
思考
需要输出每一位用户的累计里程,包括从未乘车的用户。内连接会丢掉无行程的用户,因此以
Users为左表连接Rides。按用户
id分组后对distance求和,无行程时SUM为NULL,用IFNULL转为 0 0 0。再按里程降序、姓名升序排序。
考察LEFT JOIN + GROUP BY + 排序:用户与行程左连接,COALESCE(SUM(距离),0) 补 0,先按距离降序再按姓名升序。
代码实现
sql
# Write your MySQL query statement below
SELECT name, IFNULL(SUM(distance), 0) AS travelled_distance
FROM
Users AS u
LEFT JOIN Rides AS r ON u.id = r.user_id
GROUP BY u.id
ORDER BY 2 DESC, 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1408. String Matching in an Array(数组中的字符串匹配)
题目描述
给你一个字符串数组 words,数组中的每个字符串都可以看作是一个单词。请你按任意顺序返回 words
中是其他单词的子字符串的所有单词。
如果你可以删除 words[j] 最左侧和最右侧的若干字符得到 word[i] ,那么字符串 words[i] 就是
words[j] 的一个子字符串。
示例 1
words = ["mass","as","hero","superhero"] -> ["as","hero"]
示例 2
words = ["leetcode","et","code"] -> ["et","code"]
示例 3
words = ["blue","green","bu"] -> []
约束:
- 1 <=
words.length<= 100;1 <=words[i].length <= 30。
解题思路
解法一:两两检查 + contains(主提交版本)
对每个单词 i,检查其余单词 j 中是否有 contains(wordsi) 的,若有则加入结果。
复杂度:O(n^2 * L^2),n ≤ 100、L ≤ 30 完全可接受。
解法二:按长度排序 + 前缀检查(对照)
把单词按长度升序排序,只检查比它长的单词是否包含它,减少比较次数。
复杂度:O(n^2 * L^2)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public List<String> stringMatching(String[] words) {
int n = words.length;
List<String> res = new ArrayList<>();
for (int i = 0; i < n; i++) {
boolean sub = false;
for (int j = 0; j < n; j++) {
if (i == j) {
continue;
}
if (words[j].contains(words[i])) {
sub = true;
break;
}
}
if (sub) {
res.add(words[i]);
}
}
return res;
}
public List<String> stringMatchingSort(String[] words) {
int n = words.length;
Integer[] idx = new Integer[n];
for (int i = 0; i < n; i++) {
idx[i] = i;
}
java.util.Arrays.sort(idx, (a, b) -> words[a].length() - words[b].length());
List<String> res = new ArrayList<>();
for (int p = 0; p < n; p++) {
int i = idx[p];
boolean sub = false;
for (int q = p + 1; q < n; q++) {
int j = idx[q];
if (words[j].contains(words[i])) {
sub = true;
break;
}
}
if (sub) {
res.add(words[i]);
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.stringMatching(new String[]{"mass", "as", "hero", "superhero"})); // [as, hero]
System.out.println(s.stringMatching(new String[]{"leetcode", "et", "code"})); // [et, code]
System.out.println(s.stringMatching(new String[]{"blue", "green", "bu"})); // []
System.out.println(s.stringMatchingSort(new String[]{"mass", "as", "hero", "superhero"})); // [as, hero]
System.out.println(s.stringMatchingSort(new String[]{"leetcode", "et", "code"})); // [et, code]
System.out.println(s.stringMatchingSort(new String[]{"blue", "green", "bu"})); // []
}
}
复杂度分析
复杂度:O(n^2 * L^2),n ≤ 100、L ≤ 30 完全可接受。
复杂度:O(n^2 * L^2)。
1409. Queries on a Permutation With Key(查询带键的排列)
题目描述
给定一个正整数数组 queries,其取值范围在 1 到 m 之间。请你根据以下规则按顺序处理所有
queriesi(从 i=0 到 i=queries.length-1):
- 首先,你有一个排列 P=1,2,3,...,`m`。
- 对于当前的
i,找到 queriesi 在排列 P 中的位置(从 0 开始索引),然后将它移到排列 P 的
开头(即下标为 0 处)。注意,queriesi 的查询结果是 queriesi 在 P 中移动前的位置。
返回一个数组,包含从给定 queries 中查询到的结果。
示例 1
queries = [3,1,2,1], m = 5 -> [2,1,2,1]
示例 2
queries = [4,1,2,2], m = 4 -> [3,1,2,0]
约束:
- 1 <=
m<= 10^3;1 <= queries.length <=m。
解题思路
解法一:列表模拟 + indexOf(主提交版本)
用 ArrayList 维护 P,每次 indexOf 找到位置 pos,然后 remove(pos) 再 add(0, v)。
复杂度:O(q * m),q、m ≤ 1000。
解法二:树状数组/数组位移(对照)
用 int\[\] p 直接模拟,找到位置后把该位置之前的元素整体右移一位再放入头部。
复杂度:O(q * m),不依赖容器开销。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public int[] processQueries(int[] queries, int m) {
List<Integer> p = new ArrayList<>();
for (int i = 1; i <= m; i++) {
p.add(i);
}
int[] res = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
int pos = p.indexOf(queries[i]);
res[i] = pos;
p.remove(pos);
p.add(0, queries[i]);
}
return res;
}
public int[] processQueriesArray(int[] queries, int m) {
int[] p = new int[m];
for (int i = 1; i <= m; i++) {
p[i - 1] = i;
}
int[] res = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
int pos = -1;
for (int j = 0; j < m; j++) {
if (p[j] == queries[i]) {
pos = j;
break;
}
}
res[i] = pos;
// 前移:把 pos 之前的元素整体右移一位
System.arraycopy(p, 0, p, 1, pos);
p[0] = queries[i];
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
int[] r1 = s.processQueries(new int[]{3, 1, 2, 1}, 5);
System.out.println(java.util.Arrays.toString(r1)); // [2, 1, 2, 1]
int[] r2 = s.processQueries(new int[]{4, 1, 2, 2}, 4);
System.out.println(java.util.Arrays.toString(r2)); // [3, 1, 2, 0]
int[] r3 = s.processQueriesArray(new int[]{3, 1, 2, 1}, 5);
System.out.println(java.util.Arrays.toString(r3)); // [2, 1, 2, 1]
int[] r4 = s.processQueriesArray(new int[]{4, 1, 2, 2}, 4);
System.out.println(java.util.Arrays.toString(r4)); // [3, 1, 2, 0]
}
}
复杂度分析
复杂度:O(q * m),q、m ≤ 1000。
复杂度:O(q * m),不依赖容器开销。
1410. HTML Entity Parser(HTML 实体解析器)
题目描述
「HTML 实体解析器」是一种特殊的解析器,它将 HTML 代码作为输入,并用字符本身替换掉这些
特殊的字符实体。HTML 里这些特殊字符和它们对应的字符实体包括:
- 双引号:字符实体为 ",对应的字符是 "。
- 单引号:字符实体为 ',对应的字符是 '。
- 与符号:字符实体为 &,对应的字符是 &。
- 大于号:字符实体为 >,对应的字符是 >。
- 小于号:字符实体为 <,对应的字符是 <。
- 斜线号:字符实体为 ⁄,对应的字符是 /。
给你输入字符串 text,请你实现一个 HTML 实体解析器,返回解析器解析后的结果。
示例 1
text = "& is an HTML entity but &ambassador; is not." -> "& is an HTML entity but &ambassador; is not."
示例 2
text = "and I quote: "..."" -> "and I quote: \"...\""
示例 3
text = "Stay home! Practice on Leetcode :)"
约束:
- 1 <= text.length <= 10^5;字符串可能包含任意 ASCII 字符。
解题思路
解法一:单遍扫描 + 预置实体表(主提交版本)
用 StringBuilder 扫描;遇到 '&' 时尝试匹配 6 种实体中最长匹配(或按字典序试探),
匹配成功则追加替换字符并把指针跳到 ';' 后,否则原样保留。
复杂度:O(n * 6),空间 O(n)。
解法二:String.replaceAll 顺序替换(对照)
依次用 replaceAll 替换每种实体。注意 & 必须先最后替换以避免重复替换。
复杂度:O(n)(每次替换 O(n) * 常数)。
代码实现
java
public class Solution {
public String entityParser(String text) {
StringBuilder sb = new StringBuilder();
int n = text.length();
int i = 0;
while (i < n) {
char c = text.charAt(i);
if (c != '&') {
sb.append(c);
i++;
continue;
}
boolean matched = false;
// 按从长到短尝试匹配(避免 & 与 & 混淆;这里实体都不互为前缀,直接逐个试)
if (i + 5 < n) {
String six = text.substring(i, i + 6);
if (six.equals(""")) {
sb.append('"');
i += 6;
matched = true;
} else if (six.equals("'")) {
sb.append('\'');
i += 6;
matched = true;
}
}
if (!matched && i + 4 < n) {
String five = text.substring(i, i + 5);
if (five.equals("&")) {
sb.append('&');
i += 5;
matched = true;
} else if (five.equals(">")) {
sb.append('>');
i += 5;
matched = true;
} else if (five.equals("<")) {
sb.append('<');
i += 5;
matched = true;
}
}
if (!matched && i + 6 < n) {
String seven = text.substring(i, i + 7);
if (seven.equals("⁄")) {
sb.append('/');
i += 7;
matched = true;
}
}
if (!matched) {
sb.append('&');
i++;
}
}
return sb.toString();
}
public String entityParserReplace(String text) {
String s = text;
s = s.replaceAll(""", "\"");
s = s.replaceAll("'", "'");
s = s.replaceAll(">", ">");
s = s.replaceAll("<", "<");
s = s.replaceAll("⁄", "/");
s = s.replaceAll("&", "&");
return s;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.entityParser("& is an HTML entity but &ambassador; is not."));
// "& is an HTML entity but &ambassador; is not."
System.out.println(s.entityParser("and I quote: "...""));
// and I quote: "..."
System.out.println(s.entityParser("Stay home! Practice on Leetcode :)"));
System.out.println(s.entityParser("x > y < z ⁄ a 'b'"));
System.out.println(s.entityParserReplace("& is an HTML entity but &ambassador; is not."));
System.out.println(s.entityParserReplace("and I quote: "...""));
System.out.println(s.entityParserReplace("x > y < z ⁄ a 'b'"));
}
}
复杂度分析
复杂度:O(n * 6),空间 O(n)。
复杂度:O(n)(每次替换 O(n) * 常数)。
1411. Number of Ways to Paint N × 3 Grid(给 N x 3 网格图涂色的方案数)
题目描述
你有一个 n x 3 的网格图 grid,你需要用红、黄、绿三种颜色之一给每一个格子上色,且确保
相邻格子颜色不同(也就是有相同水平边或者垂直边的格子颜色不同)。给你网格图的行数 n。
请你返回给 grid 涂色的方案数。由于答案可能会非常大,请你返回答案对 10^9 + 7 取余的结果。
示例 1
n = 1 -> 12
示例 2
n = 2 -> 54
示例 3
n = 3 -> 246
示例 4
n = 7 -> 106494
示例 5
n = 5000 -> 30228214
约束:
- 1 <=
n<= 5000。
解题思路
解法一:状态归纳 DP(主提交版本)
每行只可能属于两种类型:
- ABA 型(三格中第一与第三同色):如 121;合法转移自 ABA 型(2 条边各 2 种)或 ABC 型。
- ABC 型(三格两两不同色):如 123。
设 an = 第 n 行为 ABA 型的方案数,bn = ABC 型。
转移:an = 3an-1 + 2 bn-1;bn = 2an-1 + 2 bn-1。
边界:a1 = 6(从 3 色中选 2 色排列:3*2),b1 = 6(3 的全排列)。
答案 = an + bn。复杂度 O(n),空间 O(1)。
解法二:矩阵快速幂(对照)
把转移写成 2x2 矩阵 \[3,2,2,2],用矩阵快速幂直接求 n 次幂,
适合 n 极大(如 10^9)的情形。复杂度 O(log n)。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int numOfWays(int n) {
long a = 6; // 第一行为 ABA 型
long b = 6; // 第一行为 ABC 型
for (int i = 2; i <= n; i++) {
long na = (3 * a + 2 * b) % MOD;
long nb = (2 * a + 2 * b) % MOD;
a = na;
b = nb;
}
return (int) ((a + b) % MOD);
}
public int numOfWaysMatrix(int n) {
if (n == 1) {
return 12;
}
// 转移矩阵 M = [[3,2],[2,2]],初始向量 v = [6,6]
long[][] m = {{3, 2}, {2, 2}};
long[][] mp = matPow(m, n - 1);
long a = (mp[0][0] * 6 + mp[0][1] * 6) % MOD;
long b = (mp[1][0] * 6 + mp[1][1] * 6) % MOD;
return (int) ((a + b) % MOD);
}
private long[][] matMul(long[][] x, long[][] y) {
long[][] r = new long[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
long sum = 0;
for (int k = 0; k < 2; k++) {
sum = (sum + x[i][k] * y[k][j]) % MOD;
}
r[i][j] = sum;
}
}
return r;
}
private long[][] matPow(long[][] m, int p) {
long[][] res = {{1, 0}, {0, 1}};
while (p > 0) {
if ((p & 1) == 1) {
res = matMul(res, m);
}
m = matMul(m, m);
p >>= 1;
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.numOfWays(1)); // 12
System.out.println(s.numOfWays(2)); // 54
System.out.println(s.numOfWays(3)); // 246
System.out.println(s.numOfWays(7)); // 106494
System.out.println(s.numOfWays(5000)); // 30228214
System.out.println(s.numOfWaysMatrix(1)); // 12
System.out.println(s.numOfWaysMatrix(2)); // 54
System.out.println(s.numOfWaysMatrix(3)); // 246
System.out.println(s.numOfWaysMatrix(7)); // 106494
}
}
复杂度分析
答案 = an + bn。复杂度 O(n),空间 O(1)。
1412. Find the Quiet Students in All Exams(查找成绩处于中游的学生 🔒)
题目类型:数据库(SQL)
题目描述
表: Student
+---------------------+---------+
| Column Name | Type |
+---------------------+---------+
| student_id | int |
| student_name | varchar |
+---------------------+---------+
student_id 是该表主键(具有唯一值的列)。
student_name 学生名字。
表: Exam
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| exam_id | int |
| student_id | int |
| score | int |
+---------------+---------+
(exam_id, student_id) 是该表主键(具有唯一值的列的组合)。
学生 student_id 在测验 exam_id 中得分为 score。
成绩处于中游的学生是指至少参加了一次测验, 且得分既不是最高分也不是最低分的学生。
编写解决方案,找出在 所有 测验中都处于中游的学生 (student_id, student_name)。不要返回从来没有参加过测验的学生。
返回结果表按照 student_id 排序。
返回结果格式如下。
示例 1:
**输入:**
Student 表:
+-------------+---------------+
| student_id | student_name |
+-------------+---------------+
| 1 | Daniel |
| 2 | Jade |
| 3 | Stella |
| 4 | Jonathan |
| 5 | Will |
+-------------+---------------+
Exam 表:
+------------+--------------+-----------+
| exam_id | student_id | score |
+------------+--------------+-----------+
| 10 | 1 | 70 |
| 10 | 2 | 80 |
| 10 | 3 | 90 |
| 20 | 1 | 80 |
| 30 | 1 | 70 |
| 30 | 3 | 80 |
| 30 | 4 | 90 |
| 40 | 1 | 60 |
| 40 | 2 | 70 |
| 40 | 4 | 80 |
+------------+--------------+-----------+
**输出:**
+-------------+---------------+
| student_id | student_name |
+-------------+---------------+
| 2 | Jade |
+-------------+---------------+
**解释:**
对于测验 1: 学生 1 和 3 分别获得了最低分和最高分。
对于测验 2: 学生 1 既获得了最高分, 也获得了最低分。
对于测验 3 和 4: 学生 1 和 4 分别获得了最低分和最高分。
学生 2 和 5 没有在任一场测验中获得了最高分或者最低分。
因为学生 5 从来没有参加过任何测验, 所以他被排除于结果表。
由此, 我们仅仅返回学生 2 的信息。
解题思路
思考
「安静」指该生参加的每一场考试都不是当场最高分也不是最低分。逐场手工筛选极值再反连接,SQL 中不如窗口函数直接标出两端。
对每场考试分别按成绩升序、降序做
RANK(),得到最低、最高标记。与学生表连接后按人分组,要求从未出现过 r k = 1 rk=1 rk=1,即两端次数均为 0 0 0。
考察两次排名比较:对每科考试按分数正序、倒序各取一次排名,判断学生是否为该科中间名次(非第一非最后)。
代码实现
sql
# Write your MySQL query statement below
WITH
T AS (
SELECT
student_id,
RANK() OVER (
PARTITION BY exam_id
ORDER BY score
) AS rk1,
RANK() OVER (
PARTITION BY exam_id
ORDER BY score DESC
) AS rk2
FROM Exam
)
SELECT student_id, student_name
FROM
T
JOIN Student USING (student_id)
GROUP BY 1
HAVING SUM(rk1 = 1) = 0 AND SUM(rk2 = 1) = 0
ORDER BY 1;
复杂度分析
本题为数据库查询题,不涉及传统算法的时间/空间复杂度,执行效率由数据库优化器决定;建议对查询涉及的过滤列与连接列建立索引以加速。
1413. Minimum Value to Get Positive Step by Step Sum(逐步求和得到正数的最小值)
题目描述
给你一个整数数组 nums。你可以选定任意的正数 startValue 作为初始值。
你需要从左到右遍历 nums 数组,并将 startValue 依次累加上 nums 数组中的值。
请你在确保累加和始终大于等于 1 的前提下,选出一个最小的正数作为 startValue。
示例 1
nums = [-3,2,-3,4,2] -> 5
示例 2
nums = [1,2] -> 1
示例 3
nums = [1,-2,-3] -> 5
约束:
- 1 <=
nums.length<= 100;-100 <=nums[i]<= 100。
解题思路
解法一:前缀和最小值(主提交版本)
从头累加,记录过程中出现的最小前缀和 minSum;startValue 需满足
startValue + minSum >= 1,即 startValue = max(1, 1 - minSum)。
复杂度:O(n),空间 O(1)。
解法二:二分答案(对照)
startValue 越大越容易满足条件,对 1, 10\^6 二分最小的可行值。
复杂度:O(n * log R),R 为二分上界。
代码实现
java
public class Solution {
public int minStartValue(int[] nums) {
int sum = 0;
int minSum = 0;
for (int v : nums) {
sum += v;
minSum = Math.min(minSum, sum);
}
return Math.max(1, 1 - minSum);
}
public int minStartValueBinary(int[] nums) {
int lo = 1, hi = 10000;
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (feasible(nums, mid)) {
hi = mid;
} else {
lo = mid + 1;
}
}
return lo;
}
private boolean feasible(int[] nums, int start) {
long cur = start;
for (int v : nums) {
cur += v;
if (cur < 1) {
return false;
}
}
return true;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minStartValue(new int[]{-3, 2, -3, 4, 2})); // 5
System.out.println(s.minStartValue(new int[]{1, 2})); // 1
System.out.println(s.minStartValue(new int[]{1, -2, -3})); // 5
System.out.println(s.minStartValueBinary(new int[]{-3, 2, -3, 4, 2})); // 5
System.out.println(s.minStartValueBinary(new int[]{1, 2})); // 1
System.out.println(s.minStartValueBinary(new int[]{1, -2, -3})); // 5
}
}
复杂度分析
复杂度:O(n),空间 O(1)。
复杂度:O(n * log R),R 为二分上界。
1414. Find the Minimum Number of Fibonacci Numbers Whose Sum Is K(和为 K 的最少斐波那契数字数目)
题目描述
给你数字 k,请你返回和为 k 的斐波那契数字的最少数目,其中,每个斐波那契数字都可以被使用多次。
斐波那契数字定义为:
F1 = 1F2 = 1- Fn = Fn-1 + Fn-2,其中
n> 2。
数据保证对于给定的k,一定能找到可行解。
示例 1
k = 7 -> 2(2 + 5)
示例 2
k = 10 -> 2(2 + 8)
示例 3
k = 19 -> 3(1 + 5 + 13 或 1+8+10?校验:19 = 13+5+1 = 3 个)
约束:
- 1 <=
k<= 10^9。
解题思路
解法一:贪心选最大可用的斐波那契数(主提交版本)
斐波那契数列增长极快(约黄金比例),先预处理出所有 <= k 的斐波那契数。
每次尽量减去当前最大可行的斐波那契数(该数 <= 剩余值)。因为任意整数都能写成
若干斐波那契数之和,且贪心选择最大数一定最优(Zeckendorf 定理系的推广,本题允许
重复使用,故贪心成立)。复杂度 O(log k) 个数字。
解法二:完全背包 DP(对照)
把斐波那契数看作物品(无限使用),dpx = 凑成 x 的最少个数。
复杂度 O(k * log k),仅适用于小 k 对照(k 大时不可行)。
代码实现
java
import java.util.ArrayList;
import java.util.List;
public class Solution {
public int findMinFibonacciNumbers(int k) {
List<Integer> fib = new ArrayList<>();
int a = 1, b = 1;
fib.add(1);
fib.add(1);
while (a + b <= k) {
int c = a + b;
fib.add(c);
a = b;
b = c;
}
int ans = 0;
for (int i = fib.size() - 1; i >= 0 && k > 0; i--) {
int f = fib.get(i);
if (f <= k) {
ans += k / f;
k %= f;
}
}
return ans;
}
public int findMinFibonacciNumbersDp(int k) {
List<Integer> fib = new ArrayList<>();
int a = 1, b = 1;
fib.add(1);
fib.add(1);
while (a + b <= k) {
int c = a + b;
fib.add(c);
a = b;
b = c;
}
int[] dp = new int[k + 1];
java.util.Arrays.fill(dp, Integer.MAX_VALUE / 2);
dp[0] = 0;
for (int f : fib) {
for (int x = f; x <= k; x++) {
dp[x] = Math.min(dp[x], dp[x - f] + 1);
}
}
return dp[k];
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.findMinFibonacciNumbers(7)); // 2
System.out.println(s.findMinFibonacciNumbers(10)); // 2
System.out.println(s.findMinFibonacciNumbers(19)); // 3
System.out.println(s.findMinFibonacciNumbers(1)); // 1
System.out.println(s.findMinFibonacciNumbers(2)); // 1
System.out.println(s.findMinFibonacciNumbersDp(7)); // 2
System.out.println(s.findMinFibonacciNumbersDp(10)); // 2
System.out.println(s.findMinFibonacciNumbersDp(19)); // 3
}
}
复杂度分析
1415. The k-th Lexicographical String of All Happy Strings of Length n(长度为 n 的开心字符串中字典序第 k 小的字符串)
题目描述
一个「开心字符串」定义为:
- 仅包含小写字母 '`a`','`b`','`c`'。
- 对所有在 1 到
s.length- 1 之间的i,满足s[i]!=s[i + 1](字符串的下标从 1 开始)。
给你两个整数n和k,你需要将长度为n的所有开心字符串按字典序排序。
请你返回排序后的第k个开心字符串,如果长度为n的开心字符串少于k个,那么请你返回空字符串。
示例 1
n = 1, k = 3 -> "c"
示例 2
n = 1, k = 4 -> ""
示例 3
n = 3, k = 9 -> "cab"
约束:
- 1 <=
n<= 10;1 <=k<= 100。
解题思路
解法一:位计数 + 逐位构造(主提交版本)
长度为 n 的开心字符串总数为 3 * 2^(n-1)。第 k 个字符串按字典序构造:
第一个字符可以是 a/b/c,之后每个位置可选的字符是「不等于前一个字符」的两个;
用块大小 2^(n-i) 决定当前位取第几个候选字典序字符。
复杂度:O(n)。
解法二:回溯枚举排序(对照)
DFS 生成所有开心字符串(按字典序自然有序),取第 k 个。
复杂度:O(3 * 2^(n-1) * n),n ≤ 10 时可行。
代码实现
java
public class Solution {
public String getHappyString(int n, int k) {
int total = 3 * (1 << (n - 1));
if (k > total) {
return "";
}
StringBuilder sb = new StringBuilder();
k--; // 转 0 基
// 选首字符:以 2^(n-1) 为一组
int block = 1 << (n - 1);
char start = (char) ('a' + k / block);
sb.append(start);
k %= block;
char last = start;
for (int i = 1; i < n; i++) {
block >>= 1;
char[] options = getOptions(last);
// 按字典序:选取第 t 个候选(t = k / block)
char c;
if (k < block) {
c = options[0];
} else {
c = options[1];
k -= block;
}
sb.append(c);
last = c;
}
return sb.toString();
}
// 返回不等于 last 的两个字符,按字典序升序
private char[] getOptions(char last) {
char[] res = new char[2];
int idx = 0;
for (char c = 'a'; c <= 'c'; c++) {
if (c != last) {
res[idx++] = c;
}
}
return res;
}
public String getHappyStringDfs(int n, int k) {
StringBuilder sb = new StringBuilder();
int[] cnt = new int[]{k};
for (char c = 'a'; c <= 'c'; c++) {
sb.append(c);
String res = dfs(n, 1, c, cnt);
if (res != null) {
return res;
}
sb.deleteCharAt(sb.length() - 1);
}
return "";
}
private String dfs(int n, int len, char last, int[] cnt) {
if (len == n) {
cnt[0]--;
if (cnt[0] == 0) {
return String.valueOf(last);
}
return null;
}
for (char c = 'a'; c <= 'c'; c++) {
if (c == last) {
continue;
}
String res = dfs(n, len + 1, c, cnt);
if (res != null) {
return last + res;
}
}
return null;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.getHappyString(1, 3)); // c
System.out.println(s.getHappyString(1, 4)); // ""
System.out.println(s.getHappyString(3, 9)); // cab
System.out.println(s.getHappyString(2, 5)); // bc? 校验:aa.. 开心串按字典序:
// ab,ac,ba,bc,ca,cb -> 第5个是 ca? 排序: ab,ac,ba,bc,ca,cb => 第5=ca
System.out.println(s.getHappyStringDfs(1, 3)); // c
System.out.println(s.getHappyStringDfs(1, 4)); // ""
System.out.println(s.getHappyStringDfs(3, 9)); // cab
System.out.println(s.getHappyStringDfs(2, 5)); // ca
}
}
复杂度分析
复杂度:O(n)。
复杂度:O(3 * 2^(n-1) * n),n ≤ 10 时可行。
1416. Restore The Array(恢复数组)
题目描述
某个程序本来应该输出一个整数数组。但是这个程序忘记输出空格了以致输出了一个数字字符串,
我们所知道的信息只有:数组中所有整数都在 1, `k` 之间,且数组中的数字都没有前导 0。
给你字符串 s 和整数 k。可能会有多种不同的数组恢复结果。
按照上述程序,请你返回所有可能输出字符串 s 的数组方案数。
由于数组方案数可能会很大,请你返回它对 10^9 + 7 取余后的结果。
示例 1
s = "1000", k = 10000 -> 1
示例 2
s = "1000", k = 10 -> 0
示例 3
s = "1317", k = 2000 -> 8
约束:
- 1 <=
s.length<= 10^5;1 <=k<= 10^9。
解题思路
解法一:DP + 数字大小剪枝(主提交版本)
dpi = si... 的恢复方案数。dpn = 1。
对每个 i,枚举 j 从 i 开始累加数字 val = val*10 + digit;
若 val > k 则停止(后续更长只会更大);若 si=='0' 则 break(不能有前导 0)。
dpi = sum(dpj+1)。复杂度:每轮最多枚举 10 位(val 很快超 k)或 O(len * 10)。
解法二:逆序区间和优化(对照)
dpi 依赖一段连续区间 dpi+1...j+1 的和,可用后缀和 O(1) 转移。
复杂度 O(n * 10)。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int numberOfArrays(String s, int k) {
int n = s.length();
long[] dp = new long[n + 1];
dp[n] = 1;
for (int i = n - 1; i >= 0; i--) {
if (s.charAt(i) == '0') {
dp[i] = 0;
continue;
}
long val = 0;
for (int j = i; j < n; j++) {
val = val * 10 + (s.charAt(j) - '0');
if (val > k) {
break;
}
dp[i] = (dp[i] + dp[j + 1]) % MOD;
}
}
return (int) dp[0];
}
public int numberOfArraysSuffix(String s, int k) {
int n = s.length();
long[] dp = new long[n + 1];
long[] suffix = new long[n + 2];
dp[n] = 1;
suffix[n] = 1;
for (int i = n - 1; i >= 0; i--) {
if (s.charAt(i) == '0') {
dp[i] = 0;
} else {
long val = 0;
int j = i;
while (j < n) {
long next = val * 10 + (s.charAt(j) - '0');
if (next > k) {
break;
}
val = next;
j++;
}
// [i, j-1] 为合法分段终点;dp[i] = sum(dp[i+1..j])
if (j > n) {
j = n;
}
long sum = suffix[i + 1] - (j + 1 <= n ? suffix[j + 1] : 0);
dp[i] = (sum % MOD + MOD) % MOD;
}
suffix[i] = (suffix[i + 1] + dp[i]) % MOD;
}
return (int) dp[0];
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.numberOfArrays("1000", 10000)); // 1
System.out.println(s.numberOfArrays("1000", 10)); // 0
System.out.println(s.numberOfArrays("1317", 2000)); // 8
System.out.println(s.numberOfArrays("123", 100)); // 3([1,23],[12,3],[1,2,3];[123] 超 k 不计)
System.out.println(s.numberOfArraysSuffix("1000", 10000)); // 1
System.out.println(s.numberOfArraysSuffix("1000", 10)); // 0
System.out.println(s.numberOfArraysSuffix("1317", 2000)); // 8
System.out.println(s.numberOfArraysSuffix("123", 100)); // 3
}
}
复杂度分析
1417. Reformat The String(重新格式化字符串)
题目描述
给你一个混合了数字和字母的字符串 s,其中的字母均为小写英文字母。
请你将该字符串重新格式化,使得任意两个相邻字符的类型都不同。也就是说,字母后面应该跟着数字,
而数字后面应该跟着字母。
请你返回重新格式化后的字符串;如果无法按要求重新格式化,则返回一个空字符串。
示例 1
s = "a0b1c2" -> "0a1b2c"
示例 2
s = "leetcode" -> ""
示例 3
s = "1229857369" -> ""
示例 4
s = "covid2019" -> "c2o0v1i9d"
约束:
- 1 <=
s.length<= 500;s仅由小写英文字母和/或数字组成。
解题思路
解法一:分桶穿插(主提交版本)
统计字母与数字数量,若 |cntL - cntD| > 1 则不可能,返回 ""。
让数量多的一方作为「首尾较多」的一类,交替拼接。
复杂度:O(n),空间 O(n)。
解法二:双指针交叉填充(对照)
收集两类字符到两个数组,用指针分别取,多的一方先放。
复杂度:O(n)。
代码实现
java
public class Solution {
public String reformat(String s) {
StringBuilder letters = new StringBuilder();
StringBuilder digits = new StringBuilder();
for (char c : s.toCharArray()) {
if (c >= '0' && c <= '9') {
digits.append(c);
} else {
letters.append(c);
}
}
int l = letters.length();
int d = digits.length();
if (Math.abs(l - d) > 1) {
return "";
}
StringBuilder sb = new StringBuilder();
int i = 0, j = 0;
boolean letterFirst = l >= d;
while (i < l || j < d) {
if (letterFirst) {
if (i < l) {
sb.append(letters.charAt(i++));
}
if (j < d) {
sb.append(digits.charAt(j++));
}
} else {
if (j < d) {
sb.append(digits.charAt(j++));
}
if (i < l) {
sb.append(letters.charAt(i++));
}
}
}
return sb.toString();
}
public String reformatPtr(String s) {
StringBuilder letters = new StringBuilder();
StringBuilder digits = new StringBuilder();
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
digits.append(c);
} else {
letters.append(c);
}
}
int l = letters.length();
int d = digits.length();
if (Math.abs(l - d) > 1) {
return "";
}
char[] res = new char[s.length()];
int li = 0, di = 0, idx = 0;
if (l >= d) {
while (li < l || di < d) {
if (li < l) {
res[idx++] = letters.charAt(li++);
}
if (di < d) {
res[idx++] = digits.charAt(di++);
}
}
} else {
while (li < l || di < d) {
if (di < d) {
res[idx++] = digits.charAt(di++);
}
if (li < l) {
res[idx++] = letters.charAt(li++);
}
}
}
return new String(res);
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.reformat("a0b1c2")); // 0a1b2c 等
System.out.println(s.reformat("leetcode")); // ""
System.out.println(s.reformat("1229857369")); // ""
System.out.println(s.reformat("covid2019")); // c2o0v1i9d 类
System.out.println(s.reformat("ab123")); // 1a2b3 类
System.out.println(s.reformatPtr("a0b1c2"));
System.out.println(s.reformatPtr("leetcode"));
System.out.println(s.reformatPtr("covid2019"));
}
}
复杂度分析
复杂度:O(n),空间 O(n)。
复杂度:O(n)。
1418. Display Table of Food Orders in a Restaurant(点菜展示表)
题目描述
给你一个数组 orders,表示客户在餐厅中完成的订单,确切地说,ordersi=[customerNamei,
tableNumberi, foodItemi],其中 customerNamei 是客户的姓名,tableNumberi 是客户所在餐桌的
桌号,而 foodItemi 是客户点的餐品名称。
请你返回该餐厅的点菜展示表。在这张表中,表中第一行为标题,其第一列为餐桌桌号 "Table",
后面每一列都是按字母顺序排列的餐品名称。接下来每一行中的项则表示每张餐桌订购的相应餐品数量,
第一列应当填对应的桌号,后面依次填写下单的餐品数量。
注意:客户姓名不是点菜展示表的一部分。此外,表中的数据行应该按餐桌桌号升序排列。
示例 1
见题面 -> [["Table","Beef Burrito","Ceviche","Fried Chicken","Water"],
["3","0","2","1","0"],["5","0","1","0","1"],["10","1","0","0","0"]]
约束:
- 1 <= orders.length <= 5 * 10^4。
解题思路
解法一:TreeMap 双层计数(主提交版本)
table->(food->count) 用 TreeMap 保证桌号升序;food 集合用 TreeSet 保证字典序。
首行 = "Table" + 所有 food;之后逐桌输出计数。
复杂度:O(n log n)。
解法二:HashMap + 排序(对照)
用 HashMap 收集后,把桌号排序、餐品排序,再生成表。
复杂度:O(n log n)。
代码实现
java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
public class Solution {
public List<List<String>> displayTable(List<List<String>> orders) {
TreeMap<Integer, Map<String, Integer>> tableMap = new TreeMap<>();
TreeSet<String> foods = new TreeSet<>();
for (List<String> o : orders) {
int table = Integer.parseInt(o.get(1));
String food = o.get(2);
foods.add(food);
tableMap.computeIfAbsent(table, k -> new HashMap<>())
.merge(food, 1, Integer::sum);
}
List<List<String>> res = new ArrayList<>();
List<String> header = new ArrayList<>();
header.add("Table");
header.addAll(foods);
res.add(header);
for (Map.Entry<Integer, Map<String, Integer>> e : tableMap.entrySet()) {
List<String> row = new ArrayList<>();
row.add(String.valueOf(e.getKey()));
Map<String, Integer> cnt = e.getValue();
for (String f : foods) {
row.add(String.valueOf(cnt.getOrDefault(f, 0)));
}
res.add(row);
}
return res;
}
public List<List<String>> displayTableMap(List<List<String>> orders) {
Map<Integer, Map<String, Integer>> map = new HashMap<>();
Map<String, Integer> foodSet = new HashMap<>();
for (List<String> o : orders) {
int table = Integer.parseInt(o.get(1));
String food = o.get(2);
foodSet.put(food, 1);
map.computeIfAbsent(table, k -> new HashMap<>())
.merge(food, 1, Integer::sum);
}
List<String> foods = new ArrayList<>(foodSet.keySet());
java.util.Collections.sort(foods);
List<Integer> tables = new ArrayList<>(map.keySet());
java.util.Collections.sort(tables);
List<List<String>> res = new ArrayList<>();
List<String> header = new ArrayList<>();
header.add("Table");
header.addAll(foods);
res.add(header);
for (int t : tables) {
List<String> row = new ArrayList<>();
row.add(String.valueOf(t));
Map<String, Integer> cnt = map.get(t);
for (String f : foods) {
row.add(String.valueOf(cnt.getOrDefault(f, 0)));
}
res.add(row);
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
List<List<String>> orders1 = new ArrayList<>();
orders1.add(java.util.Arrays.asList("David", "3", "Ceviche"));
orders1.add(java.util.Arrays.asList("Corina", "10", "Beef Burrito"));
orders1.add(java.util.Arrays.asList("David", "3", "Fried Chicken"));
orders1.add(java.util.Arrays.asList("Carla", "5", "Water"));
orders1.add(java.util.Arrays.asList("Carla", "5", "Ceviche"));
orders1.add(java.util.Arrays.asList("Rous", "3", "Ceviche"));
System.out.println(s.displayTable(orders1));
List<List<String>> orders2 = new ArrayList<>();
orders2.add(java.util.Arrays.asList("James", "12", "Fried Chicken"));
orders2.add(java.util.Arrays.asList("Ratesh", "12", "Fried Chicken"));
orders2.add(java.util.Arrays.asList("Amadeus", "12", "Fried Chicken"));
orders2.add(java.util.Arrays.asList("Adam", "1", "Canadian Waffles"));
orders2.add(java.util.Arrays.asList("Brianna", "1", "Canadian Waffles"));
System.out.println(s.displayTable(orders2));
System.out.println(s.displayTableMap(orders1));
System.out.println(s.displayTableMap(orders2));
}
}
复杂度分析
复杂度:O(n log n)。
1419. Minimum Number of Frogs Croaking(数青蛙)
题目描述
给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 "croak")的组合。
由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 "croak"。
请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。
要想发出蛙鸣 "croak",青蛙必须依序输出 'c','r','o','a','k' 这 5 个字母。
如果没有输出全部五个字母,那么它就不会发出声音。
如果字符串 croakOfFrogs 不是由若干有效的 "croak" 字符混合而成,请返回 -1。
示例 1
croakOfFrogs = "croakcroak" -> 1
示例 2
croakOfFrogs = "crcoakroak" -> 2
示例 3
croakOfFrogs = "croakcrook" -> -1
示例 4
croakOfFrogs = "croakcroa" -> -1
约束:
- 1 <=
croakOfFrogs.length<= 10^5。
解题思路
解法一:五状态计数(主提交版本)
维护 c/r/o/a/k 五个前缀状态的数量(即正在进行到该字符的青蛙数)。
遇 'c' 开新蛙同时计数 +1;遇后续字母要求前一个状态数 > 0,转移;
遇 'k' 完成时计数 -1(该蛙空闲可复用)。同时跟踪进行中最大蛙数。
结束时 cntTotal == 0 且中途无非法转移才有效。
复杂度:O(n),空间 O(1)。
解法二:队列模拟(对照)
拆成多只青蛙顺序,用 Map 模拟更直白但开销大;此处给出
「每只蛙用单字符状态机」等价实现:cnt 数组 + 违规检查。
代码实现
java
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int minNumberOfFrogs(String croakOfFrogs) {
int c = 0, r = 0, o = 0, a = 0, k = 0;
int active = 0, maxFrogs = 0;
for (char ch : croakOfFrogs.toCharArray()) {
if (ch == 'c') {
c++;
active++;
maxFrogs = Math.max(maxFrogs, active);
} else if (ch == 'r') {
if (c <= r) {
return -1;
}
r++;
} else if (ch == 'o') {
if (r <= o) {
return -1;
}
o++;
} else if (ch == 'a') {
if (o <= a) {
return -1;
}
a++;
} else if (ch == 'k') {
if (a <= k) {
return -1;
}
k++; // 完成一只
active--;
} else {
return -1;
}
}
// 结束时所有蛙必须完成:每个状态数量相等且 active == 0
if (active != 0 || c != r || r != o || o != a || a != k) {
return -1;
}
return maxFrogs;
}
public int minNumberOfFrogsArr(String croakOfFrogs) {
String seq = "croak";
int[] cnt = new int[5]; // 处于每个阶段(含刚完成)的蛙数
int active = 0, maxFrogs = 0;
for (char ch : croakOfFrogs.toCharArray()) {
int idx = seq.indexOf(ch);
if (idx < 0) {
return -1;
}
if (idx == 0) {
cnt[0]++;
active++;
maxFrogs = Math.max(maxFrogs, active);
} else {
if (cnt[idx - 1] == 0) {
return -1;
}
cnt[idx - 1]--;
if (idx == 4) {
active--;
} else {
cnt[idx]++;
}
}
}
for (int i = 0; i < 4; i++) {
if (cnt[i] != 0) {
return -1;
}
}
return active == 0 ? maxFrogs : -1;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.minNumberOfFrogs("croakcroak")); // 1
System.out.println(s.minNumberOfFrogs("crcoakroak")); // 2
System.out.println(s.minNumberOfFrogs("croakcrook")); // -1
System.out.println(s.minNumberOfFrogs("croakcroa")); // -1
System.out.println(s.minNumberOfFrogs("ccrrooaakk")); // 2
System.out.println(s.minNumberOfFrogsArr("croakcroak")); // 1
System.out.println(s.minNumberOfFrogsArr("crcoakroak")); // 2
System.out.println(s.minNumberOfFrogsArr("croakcrook")); // -1
System.out.println(s.minNumberOfFrogsArr("ccrrooaakk")); // 2
}
}
复杂度分析
复杂度:O(n),空间 O(1)。
1420. Build Array Where You Can Find The Maximum Exactly K Comparisons(生成数组)
题目描述
给定三个整数 n、m 和 k。考虑使用下图描述的算法找出正整数数组中最大的元素:
算法:ans = -1,cost = 0;从左到右扫描数组,若 x > ans 则 ans = x, cost++。
请你构建一个具有以下属性的数组 arr:
arr中包含确切的n个整数;- 1 <=
arr[i]<=m,其中 (0 <=i<n); - 将上面提到的算法应用于
arr之后,search_cost的值等于k。
返回在满足上述条件的情况下构建数组arr的方法数量,由于答案可能会很大,所以必须对 10^9 + 7 取余。
示例 1
n = 2, m = 3, k = 1 -> 6
示例 2
n = 5, m = 2, k = 3 -> 0
示例 3
n = 9, m = 1, k = 1 -> 1
示例 4
n = 50, m = 100, k = 25 -> 34549172
约束:
- 1 <=
n<= 50;1 <=m<= 100;0 <=k<=n。
解题思路
解法一:三维 DP + 前缀和优化(主提交版本)
dpijc = 前 i 个数、当前最大值为 j、已发生 c 次更新(cost)的方案数。
转移:
- 新元素 <= j:dpijc += dpi-1jc * j(放 1...j 任意值都不更新);
- 新元素 > j(新最大):dpixc+1 += dpi-1jc for x in (j, m]。
复杂度 O(n * m^2 * k),n≤50,m≤100,k≤50 约 1250 万可接受,用前缀和优化到 O(nm k)。
解法二:三维朴素 DP(对照)
直接三重枚举转移,无前缀和,代码直观但更慢。复杂度 O(n * m^2 * k)。
代码实现
java
public class Solution {
private static final int MOD = 1_000_000_007;
public int numOfArrays(int n, int m, int k) {
if (k == 0) {
return 0;
}
if (k > m) {
return 0; // 更新次数不可能超过最大值的不同取值
}
long[][] dp = new long[m + 2][k + 1]; // dp[j][c]
// 初始:第一个数取 j 时最大值 j,cost=1
for (int j = 1; j <= m; j++) {
dp[j][1] = 1;
}
for (int i = 2; i <= n; i++) {
long[][] ndp = new long[m + 2][k + 1];
long[][] prefix = new long[m + 2][k + 1];
// 计算前缀和 prefix[j][c] = sum_{x<=j} dp[x][c]
for (int c = 1; c <= k; c++) {
long acc = 0;
for (int j = 1; j <= m; j++) {
acc = (acc + dp[j][c]) % MOD;
prefix[j][c] = acc;
}
}
for (int j = 1; j <= m; j++) {
for (int c = 1; c <= k; c++) {
// 1) 放 <= j 的值:不更新,前缀不放大的转移来自 dp[j][c] * j
long same = dp[j][c] * j % MOD;
// 2) 放 > j 的值:从更小最大值的状态转移(新最大 j, c-1 次更新)
long larger = prefix[j - 1][c - 1]; // sum_{x<j} dp[x][c-1] 每个放 j 到 m?
// 实际上「新最大 = 恰好 j」来自前一次最大值 x < j:dp[x][c-1] 且放 j。
ndp[j][c] = (same + larger) % MOD;
}
}
dp = ndp;
}
long ans = 0;
for (int j = 1; j <= m; j++) {
ans = (ans + dp[j][k]) % MOD;
}
return (int) ans;
}
public int numOfArraysNaive(int n, int m, int k) {
if (k == 0 || k > m) {
return 0;
}
long[][] dp = new long[m + 1][k + 1];
for (int j = 1; j <= m; j++) {
dp[j][1] = 1;
}
for (int i = 2; i <= n; i++) {
long[][] ndp = new long[m + 1][k + 1];
for (int prevMax = 1; prevMax <= m; prevMax++) {
for (int c = 1; c <= k; c++) {
if (dp[prevMax][c] == 0) {
continue;
}
// 放 <= prevMax 的值(共 prevMax 种):最大值不变
ndp[prevMax][c] = (ndp[prevMax][c] + dp[prevMax][c] * prevMax) % MOD;
// 放 > prevMax 的值:新最大值 = nv,cost + 1
for (int nv = prevMax + 1; nv <= m; nv++) {
if (c + 1 <= k) {
ndp[nv][c + 1] = (ndp[nv][c + 1] + dp[prevMax][c]) % MOD;
}
}
}
}
dp = ndp;
}
long ans = 0;
for (int j = 1; j <= m; j++) {
ans = (ans + dp[j][k]) % MOD;
}
return (int) ans;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.numOfArrays(2, 3, 1)); // 6
System.out.println(s.numOfArrays(5, 2, 3)); // 0
System.out.println(s.numOfArrays(9, 1, 1)); // 1
System.out.println(s.numOfArrays(50, 100, 25)); // 34549172
System.out.println(s.numOfArraysNaive(2, 3, 1)); // 6
System.out.println(s.numOfArraysNaive(5, 2, 3)); // 0
System.out.println(s.numOfArraysNaive(9, 1, 1)); // 1
}
}