最近一直打的很烂,赛前安慰自己只要三道题不出错就算进步,结果只做出来两道题,要被自己蠢哭了。
你说是题目变难了吧,老铁们都唰唰的过题。说是自己变菜了吧,这水平下降的也太快了点,照这样下去,下次只能出一道题了。
想了半天,只能是题目变难了,老铁们也变强了,只有我留在原地了,真的太难了。

5854. 学生分数的最小差值
思路:排序,枚举
时间复杂度:
首先将 nums 升序排序,则答案为:
class Solution {
public:
int minimumDifference(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
int anw = nums[k-1] - nums[0];
for (int i = 1; i+k-1 < nums.size(); i++) {
anw = min(anw, nums[i+k-1] - nums[i]);
}
return anw;
}
};
5855. 找出数组中的第 K 大整数
思路:排序
时间复杂度:
因为输入数据不含前导零,所以长字符串对应的数字一定比短字符串的大。因此,可以先通过二级排序将 nums 降序排序,排序后的 nums[k-1] 即为答案。
二级排序规则为:
- 长度不同的按长度降序排序
- 长度相同的按字典序降序排序
class Solution {
public:
string kthLargestNumber(vector<string>& nums, int k) {
sort(nums.begin(), nums.end(), [](const auto &l, const auto &r) -> bool {
if (l.size() != r.size()) {
return l.size() > r.size();
}
return l > r;
});
return nums[k-1];
}
};
5856. 完成任务的最少工作时间段
思路:位运算,动态规划,枚举子集
时间复杂度:
设有长度为 的数组 , 表示任务完成状态为 时的最小花费。
任务完成状态 可理解为由 个比特表示的集合,从低位到高位,如果第 位比特为 1,则表示 已完成,反之未完成。
显然,状态 可划分为两个子状态 和 ,满足:
换言之, 中为 1 的比特,仅在 或 中的一个为一,另一个为零。
于是,得到了状态转移方程:
特别的,当 代表的任务集合的耗时不超过 时,。
如何枚举 和
给定 ,可通过下述方式枚举 和 。
for (int a = mask, b = 0; a > b; a = (a-1)&mask, b = a^mask) {
// do something
}
其中关键在于 a=(a-1)&mask。
设有 和 的值如上所示,减法运算和与运算对 的影响如下图示:
减法运算将 中「值为 1 」的「最低位」的比特置为 0,并将其后的 0 全置为 1。
与运算将不应变为 1 的比特再置回 0。
这套组合拳可理解为:将 中那些「在 中的对应比特为 0」的比特删去,然后做减法,这显然可以枚举出 的所有子集。
一个小剪枝
设初始时,。随着枚举进行,必然会从 变为 。显然此时没必要继续枚举下去了,因为 和 是对称的。
时间复杂度
对于包含 个比特且恰有 个比特为 1 的 ,共有 个子集,这样的 共有 个。因此整体的计算量可用 的二项式展开来表示:
class Solution {
public:
int minSessions(vector<int>& tasks, int sessionTime) {
int n = tasks.size();
int m = (1<<n);
vector<int> sum(m, 0);
for (int i = 1; i < m; i++) {
for (int j = 0, b = 1; ; b <<= 1, j++) {
if (i&b) {
sum[i] = sum[i^(b)] + tasks[j];
break;
}
}
}
vector<int> dp(m, numeric_limits<int>::max());
dp[0] = 0;
for (int i = 1; i < m; i++) {
if (sum[i] <= sessionTime) {
dp[i] = 1;
} else {
for (int a = i, b = 0; a > b; a = (a-1)&i, b = a^i) {
dp[i] = min(dp[i], dp[a] + dp[b]);
}
}
}
return dp[m-1];
}
};
1987. 不同的好子序列数目
思路:动态规划,滚动数组,去重
时间复杂度:
假设我们现在有两个集合 和 ,分别由 '0' 开头的序列和 '1' 开头的序列组成,且其中无重复序列。
现在我们要向所有序列的前面加 '1',构成新的集合 包含:
- 个以 '10' 开头的序列
- 个以 '11' 开头的序列
因为 和 本身无重复,所以 也无重复。另外, 中的序列长度均大于一,所以将 '1' 插入 中仍不会有重复。
除了证明 本身无重复外,还需证明 ,考虑 中的三类序列:
- 以 '10' 开头,必然由 的某个子集添加 '1' 而来,这部分必然在 中。
- 以 开头,必然由 的某个子集添加 '1' 而来,这部分必然也在 中。
- '1' 本身,我们向 添加了 '1',所以该序列也在 中。
所以,
所以有,。
同理,向所有序列的前面加 '0',构成的新集合 包含:
- 个以 '00' 开头的序列
- 个以 '01' 开头的序列
同样的,也可将 '0' 插入 中。
因此,
class Solution {
public:
int numberOfUniqueGoodSubsequences(string binary) {
int zero = 0, one = 0; // 初始时均为空集合
int mod = 1e9+7;
int has_zero = 0; // 判断是否幽灵
for (int i = binary.size()-1;i >= 0; i--) {
if (binary[i] == '0') {
has_zero = 1;
zero = (zero + one + 1)%mod;
} else {
one = (one + zero + 1)%mod;
}
}
return (one + has_zero) % mod;
}
};