[Leetcode Weekly Contest]256
链接:LeetCode
[Leetcode]1984. 学生分数的最小差值
给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。
从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分 和 最低分 的 差值 达到 最小化 。
返回可能的最小差值 。
暴力循环即可。
import java.util.Arrays;
class Solution {
public int minimumDifference(int[] nums, int k) {
Arrays.sort(nums);
int res = Integer.MAX_VALUE;
for(int i=0;i<nums.length-k+1;++i)
{
int start = i;
int end = i+k-1;
res = Math.min(res,nums[end]-nums[start]);
}
return res;
}
}
[Leetcode]1985. 找出数组中的第 K 大整数
给你一个字符串数组 nums 和一个整数 k 。nums 中的每个字符串都表示一个不含前导零的整数。
返回 nums 中表示第 k 大整数的字符串。
注意:重复的数字在统计时会视为不同元素考虑。例如,如果 nums 是 ["1","2","2"],那么 "2" 是最大的整数,"2" 是第二大的整数,"1" 是第三大的整数。
自定义排序可快速找到所有排好序的字符串,对于特定第k大,则使用小根堆为最佳解法。
import java.util.PriorityQueue;
class Solution {
public String kthLargestNumber(String[] nums, int k) {
PriorityQueue<String> q = new PriorityQueue<String>((o1, o2) -> {
// 从小到大排序
if (o1.length() == o2.length()) {
return o1.compareTo(o2);
} else {
return o1.length() - o2.length();
}
});
for(String num : nums) {
q.offer(num);
if (q.size() > k) {
q.poll();
}
}
System.out.println(q);
return q.peek();
}
}
[Leetcode]1986. 完成任务的最少工作时间段
你被安排了 n 个任务。任务需要花费的时间用长度为 n 的整数数组 tasks 表示,第 i 个任务需要花费 tasks[i] 小时完成。一个 工作时间段 中,你可以 至多 连续工作 sessionTime 个小时,然后休息一会儿。
你需要按照如下条件完成给定任务:
如果你在某一个时间段开始一个任务,你需要在 同一个 时间段完成它。
完成一个任务后,你可以 立马 开始一个新的任务。
你可以按 任意顺序 完成任务。
给你 tasks 和 sessionTime ,请你按照上述要求,返回完成所有任务所需要的 最少 数目的 工作时间段 。
测试数据保证 sessionTime 大于等于 tasks[i] 中的 最大值 。
这是一道经典的状压DP题。
状态压缩,其实就是将每个任务 task[i] 看作某个「十进制数字」的二进制上的第 i 位,1 表示该任务被选择,反之不被选择。其经典思想是通过二进制表示一个数组的状态。
我们用 \(f[\textit{mask}]\) 表示当选择任务的状态为 \(\textit{mask}\) 时,最少需要的工作时间段。其中 \(\textit{mask}\) 是一个长度为 n 的二进制表示,\(\textit{mask}\) 从低到高的第 i 位为 1 表示第 i 个任务已经完成,0 表示第 i 个任务未完成。
在进行状态转移时,我们可以枚举最后一个工作时间段完成了哪些任务。
这是这道题的一个难点。通过枚举最后一个工作时间段的任务来分裂所有状态。
显然,这些任务对应的状态 \(\textit{subset}\) 一定是 \(\textit{mask}\) 的一个子集。
另外一个难点就是如何列举mask的子集。
其中 \(\textit{mask} \backslash \textit{subset}\) 表示将 \(\textit{subset}\) 中的所有 1 从 \(\textit{mask}\) 中移除后的二进制表示,可以使用按位异或运算求出。
如\(\textit{mask}\)是1111, 其中一个\(\textit{subset}\)为1001,也就是我们最后一个工作时间段完成了第一个和最后一个任务,那么我们在最后一个任务之前需要完成的状态就是\(\textit{mask} \backslash \textit{subset}\),也就是0110.
需要注意的是,\(\textit{subset}\) 中包含的任务的工作时间总和不能大于 \(\textit{sessionTime}\)。因此在动态规划之前,我们可以进行预处理,即在 \([1, 2^n)\)的范围内枚举每一个二进制表示 \(\textit{mask}\),计算其如果包含的任务的工作时间总和。如果其小于等于 \(\textit{sessionTime}\),那么将 \(\textit{valid}[\textit{mask}]\) 标记为 \(\text{True}\),否则标记为 \(\text{False}\)。这样在动态规划时,我们就不需要再计算 \(\textit{subset}\) 是否满足要求了。
动态规划的边界条件为\(f[0] = 0\),最终的答案即为 \(f[2^n - 1]\)]。
class Solution {
public int minSessions(int[] tasks, int sessionTime) {
int n = tasks.length, m = 1 << n;
boolean[] valid = new boolean[m];
Arrays.fill(valid,false);
for(int i=1;i<m;++i)
{
int spendTime = 0;
for(int bit=0;bit<n;++bit)
{
if((i&(1<<bit))!=0)
{
spendTime += tasks[bit];
}
}
if(spendTime <= sessionTime)
{
valid[i] = true;
}
}
int[] dp = new int[m];
Arrays.fill(dp,Integer.MAX_VALUE);
dp[0] = 0;
for(int i=1;i<m;++i)
{
int subset = i;
while(subset>0)
{
if(valid[subset])
{
dp[i] = Math.min(dp[i],dp[subset^i]+1);
}
subset = (subset-1)&i;
}
}
return dp[m-1];
}
}
[Leetcode]1987. 不同的好子序列数目
给你一个二进制字符串 binary 。 binary 的一个 子序列 如果是 非空 的且没有 前导 0 (除非数字是 "0" 本身),那么它就是一个 好 的子序列。
请你找到 binary 不同好子序列 的数目。
比方说,如果 binary = "001" ,那么所有 好 子序列为 ["0", "0", "1"] ,所以 不同 的好子序列为 "0" 和 "1" 。 注意,子序列 "00" ,"01" 和 "001" 不是好的,因为它们有前导 0 。
请你返回 binary 中 不同好子序列 的数目。由于答案可能很大,请将它对 1e9 + 7 取余 后返回。
一个 子序列 指的是从原数组中删除若干个(可以一个也不删除)元素后,不改变剩余元素顺序得到的序列。
动态规划。难点还是在于寻找子问题之间的推导关系
我们用 \(f[i][0]\) 和 \(f[i][1]\) 分别表示使用字符串 \(\textit{binary}\) 的第 0, 1, \(\cdots\), i 个字符,可以构造出的以 \(0/1\) 结尾的不同的好子序列的数目。
由于「好子序列」不能包含前导 0,但本身可以为 0,那么我们可以规定「好子序列」必须以 1 开始并求出答案,如果 \(\textit{binary}\) 中包含 0 就再对答案增加 1。这样做可以避免处理复杂的前导 0 规则。
如果第 i 个字符是 1,那么以 0 结尾的不同的好子序列的数目不会改变,即:
难点在于如何通过已经求得结果的状态求出 \(f[i][1]\) 的值。
以1结尾的好序列个数,则可以分成两种情况
- 包括第i个字符,为\(f[i-1][0] + f[i-1][1] + 1\),设有A类
- 不包括第i个字符,为\(f[i−1][1]\),设有B类
其实可以想象A类一定包括了B类的字符序列,对于任意一个B类中的子序列,它的最后一个元素一定为 1,如果我们将该元素变为第 i 个字符 1,那么就将该 B 类子序列「转换」为了 A 类子序列。因此,每一个 B 类子序列都唯一地对应着一个 A 类子序列,说明 B 是 A 的子集,那么两类总共的个数也就是\(f[i-1][0] + f[i-1][1] + 1\)。
同理也可得第 i 个字符是 0的情况。
class Solution {
public final static int MOD = (int)1e9+7;
public int numberOfUniqueGoodSubsequences(String binary) {
int even = 0, odd = 0;
int n = binary.length();
for(int i=0;i<n;++i)
{
if (binary.charAt(i) == '0') even = (even+odd)%MOD;
else odd = (even+odd+1)%MOD;
}
int res = even + odd;
if(binary.contains("0")) res++;
return res%MOD;
}
}