第 256 场力扣周赛(状态压缩+dp,二进制子序列的动规940)
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第 256 场力扣周赛(状态压缩+dp,二进制子序列的动规940)相关的知识,希望对你有一定的参考价值。
第 256 场力扣周赛
有事没做,来看一下题
5854. 学生分数的最小差值
题目描述
给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。
从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分 和 最低分 的 差值 达到 最小化 。
返回可能的 最小差值 。
示例 1:
输入:nums = [90], k = 1
输出:0
解释:选出 1 名学生的分数,仅有 1 种方法:
-[90] 最高分和最低分之间的差值是 90 - 90 = 0
可能的最小差值是 0
示例 2:
输入:nums = [9,4,1,7], k = 2
输出:2
解释:选出 2 名学生的分数,有 6 种方法:
-[9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5
-[9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8
-[9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2
-[9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3
-[9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3
-[9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6
可能的最小差值是 2
提示:
1 <= k <= nums.length <= 1000
0 <= nums[i] <= 105
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-difference-between-highest-and-lowest-of-k-scores
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
排序以后遍历所有情况就好了
class Solution {
public int minimumDifference(int[] nums, int k) {
Arrays.sort(nums);
int l = nums.length;
int min = Integer.MAX_VALUE;
for(int i = 0; i < l - k + 1; i++){
min = Math.min(nums[i + k - 1] - nums[i], min);
}
return min;
}
}
5855. 找出数组中的第 K 大整数
题目描述
给你一个字符串数组 nums 和一个整数 k 。nums 中的每个字符串都表示一个不含前导零的整数。
返回 nums 中表示第 k 大整数的字符串。
注意:重复的数字在统计时会视为不同元素考虑。例如,如果 nums 是 [“1”,“2”,“2”],那么 “2” 是最大的整数,“2” 是第二大的整数,“1” 是第三大的整数。
示例 1:
输入:nums = [“3”,“6”,“7”,“10”], k = 4
输出:“3”
解释:
nums 中的数字按非递减顺序排列为 [“3”,“6”,“7”,“10”]
其中第 4 大整数是 “3”
示例 2:
输入:nums = [“2”,“21”,“12”,“1”], k = 3
输出:“2”
解释:
nums 中的数字按非递减顺序排列为 [“1”,“2”,“12”,“21”]
其中第 3 大整数是 “2”
示例 3:
输入:nums = [“0”,“0”], k = 2
输出:“0”
解释:
nums 中的数字按非递减顺序排列为 [“0”,“0”]
其中第 2 大整数是 “0”
提示:
1 <= k <= nums.length <= 10^4
1 <= nums[i].length <= 100
nums[i] 仅由数字组成
nums[i] 不含任何前导零
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-the-kth-largest-integer-in-the-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
字符串排序,然后找第k-1个
class Solution {
public String kthLargestNumber(String[] nums, int k) {
Arrays.sort(nums, (a, b) -> (a.length() == b.length() ? b.compareTo(a) : (a.length() > b.length() ? -1 : 1)));
/*
Arrays.sort(nums, new Comparator<String>(){
public int compare(String a, String b){
return a.length() == b.length() ? b.compareTo(a) : (a.length() > b.length() ? -1 : 1);
}
});
*/
return nums[k - 1];
}
}
5856. 完成任务的最少工作时间段
题目描述
你被安排了 n 个任务。任务需要花费的时间用长度为 n 的整数数组 tasks 表示,第 i 个任务需要花费 tasks[i] 小时完成。一个 工作时间段 中,你可以 至多 连续工作 sessionTime 个小时,然后休息一会儿。
你需要按照如下条件完成给定任务:
如果你在某一个时间段开始一个任务,你需要在 同一个 时间段完成它。
完成一个任务后,你可以 立马 开始一个新的任务。
你可以按 任意顺序 完成任务。
给你 tasks 和 sessionTime ,请你按照上述要求,返回完成所有任务所需要的 最少 数目的 工作时间段 。
测试数据保证 sessionTime 大于等于 tasks[i] 中的 最大值 。
示例 1:
输入:tasks = [1,2,3], sessionTime = 3
输出:2
解释:你可以在两个工作时间段内完成所有任务。
-第一个工作时间段:完成第一和第二个任务,花费 1 + 2 = 3 小时。
-第二个工作时间段:完成第三个任务,花费 3 小时。
示例 2:
输入:tasks = [3,1,3,1,1], sessionTime = 8
输出:2
解释:你可以在两个工作时间段内完成所有任务。
-第一个工作时间段:完成除了最后一个任务以外的所有任务,花费 3 + 1 + 3 + 1 = 8 小时。
-第二个工作时间段,完成最后一个任务,花费 1 小时。
示例 3:
输入:tasks = [1,2,3,4,5], sessionTime = 15
输出:1
解释:你可以在一个工作时间段以内完成所有任务。
提示:
n == tasks.length
1 <= n <= 14
1 <= tasks[i] <= 10
max(tasks[i]) <= sessionTime <= 15
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-number-of-work-sessions-to-finish-the-tasks
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
这个题我不确定要是周赛能不能做出来,首先我想到的是贪心,但很快发现贪心不行。
然后我看到数据范围,很快又想到了状态压缩+dp,但是不知道怎么转移…
在有限的时间内,我可能会去看最后一题,然后如果不行的话,回来再写dfs,说不定侥幸能过
做了不知道多少到状压dp了,遇到还是做不出来,菜的想哭了
这里主要是枚举子集这个问题不会处理
记住这个处理方法:
for(int j = i; j > 0; j = ((j - 1) & i)){
//这里j是i的子集,取子集就相当于每次把i中的1变成0
//而每次减1,相当于把最右边的1变成0,右边的位置都变成1,
//然后与i相与,可以得到比j小的最大的i的最大的子集
//一直减,直到都为0,就可以枚举到所有的子集
}
}
这个可以重点看看:https://leetcode-cn.com/problems/minimum-number-of-work-sessions-to-finish-the-tasks/solution/zhuang-ya-dpshi-shi-hou-xue-xi-yi-xia-li-q4mk/
class Solution {
public int minSessions(int[] tasks, int sessionTime) {
//很直观的想到了贪心,但是写了一下不行
//然后看到数据范围,就想到状态压缩了
//用一个数表示第几个任务被选过了
//然后dp[mask] 表示对应任务被选过时,所需要的最少的工作时间段
//但是想不到如何进行状态转移,
//看了题解,唉,还是太菜了,
//枚举子集,如果一个子集内的所有任务可以在一个时间段内完成,那么就可以转移
int l = tasks.length;
//首先预处理每个子集的和
int[] sum = new int[1 << l];
for(int i = 1; i < (1 << l); i++){
for(int j = 0; j < l; j++){
//如果当前位置被取到了
if((i & (1 << j)) != 0){
sum[i] = sum[i ^ (1 << j)] + tasks[j];
//这里为什么是跳出,因为这个值计算过了,就不需要重复计算了
break;
}
}
}
//for(int i = 0; i < (1 << l); i++){
// System.out.print(sum[i] + " ");
//}
//然后定义dp[]为选取当前mask表示的任务以后,所需要的最少的工作时间段
int[] dp = new int[1 << l];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for(int i = 1; i < (1 << l); i++){
//怎么枚举子集,想一下
//就是说,每次都有一个1变为0
for(int j = i; j > 0; j = ((j - 1) & i)){
//如果能在一个时间段内完成
if(sum[j] <= sessionTime){
dp[i] = Math.min(dp[i], dp[i ^ j] + 1);
}
}
}
return dp[(1 << l) - 1];
}
}
另一个比较容易想到的枚举子集的方法,就是遍历所有0到i的所有值,然后与i相或,如果还是等于i的话(或者相与,等于j),就是i的子集,如下:
class Solution {
public int minSessions(int[] tasks, int sessionTime) {
//很直观的想到了贪心,但是写了一下不行
//然后看到数据范围,就想到状态压缩了
//用一个数表示第几个任务被选过了
//然后dp[mask] 表示对应任务被选过时,所需要的最少的工作时间段
//但是想不到如何进行状态转移,
//看了题解,唉,还是太菜了,
//枚举子集,如果一个子集内的所有任务可以在一个时间段内完成,那么就可以转移
int l = tasks.length;
//首先预处理每个子集的和
int[] sum = new int[1 << l];
for(int i = 1; i < (1 << l); i++){
for(int j = 0; j < l; j++){
//如果当前位置被取到了
if((i & (1 << j)) != 0){
sum[i] = sum[i ^ (1 << j)] + tasks[j];
//这里为什么是跳出,因为这个值计算过了,就不需要重复计算了
break;
}
}
}
//for(int i = 0; i < (1 << l); i++){
// System.out.print(sum[i] + " ");
//}
//然后定义dp[]为选取当前mask表示的任务以后,所需要的最少的工作时间段
int[] dp = new int[1 << l];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for(int i = 1; i < (1 << l); i++){
//怎么枚举子集,想一下
//就是说,每次都有一个1变为0
for(int j = 1; j <= i; j++){
//另一种判断方法,就是一个个枚举,如果是子集,就判断能否在一个时间段内完成
if((i | j) == i && sum[j] <= sessionTime){
dp[i] = Math.min(dp[i], dp[i ^ j] + 1);
}
}
}
return dp[(1 << l) - 1];
}
}
5857. 不同的好子序列数目
题目描述
给你一个二进制字符串 binary 。 binary 的一个 子序列 如果是 非空 的且没有 前导 0 (除非数字是 “0” 本身),那么它就是一个 好 的子序列。
请你找到 binary 不同好子序列 的数目。
比方说,如果 binary = “001” ,那么所有 好 子序列为 [“0”, “0”, “1”] ,所以 不同 的好子序列为 “0” 和 “1” 。 注意,子序列 “00” ,“01” 和 “001” 不是好的,因为它们有前导 0 。
请你返回 binary 中 不同好子序列 的数目。由于答案可能很大,请将它对 109 + 7 取余 后返回。
一个 子序列 指的是从原数组中删除若干个(可以一个也不删除)元素后,不改变剩余元素顺序得到的序列。
示例 1:
输入:binary = “001”
输出:2
解释:好的二进制子序列为 [“0”, “0”, “1”] 。
不同的好子序列为 “0” 和 “1” 。
示例 2:
输入:binary = “11”
输出:2
解释:好的二进制子序列为 [“1”, “1”, “11”] 。
不同的好子序列为 “1” 和 “11” 。
示例 3:
输入:binary = “101”
输出:5
解释:好的二进制子序列为 [“1”, “0”, “1”, “10”, “11”, “101”] 。
不同的好子序列为 “0” ,“1” ,“10” ,“11” 和 “101” 。
提示:
1 <= binary.length <= 10^5
binary 只含有 ‘0’ 和 ‘1’ 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-unique-good-subsequences
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
class Solution {
public int numberOfUniqueGoodSubsequences(String binary) {
//又是动态规划,最近没做出来一道
//主要是去重,怎么判断是否重复
//因为关心开头是0还是1,所以定义状态为以i位置开始的子串中,好子序列的个数
//dp[i][0]为以i开始的子串,开头为0的好子序列的个数
//dp[i][1]为以i开始的子串,开头为1的好子序列的个数
//因为是卡头,所以想到倒序动态规划
//如果当前字符binary[i] == ‘0’。三种情况:
//1.可以添加到原来每一个子串的前面,形成新的,也就是dp[i][0] = dp[i + 1][0] + dp[i + 1][1]
//2.也可以自己单独成为一个,1
//3.原来的dp[i + 1][0]个以0开头的子序列,但是这个不能添加,因为这些情况肯定包含在1、2中,是重复的,这里好好理解一下
//所以dp[i][0] = dp[i + 1][0] + dp[i + 1][1] + 1
//dp[i][1] = dp[i + 1][1]
//对于binary[i] = '1'的情况,也和0的相同
//所以最后的结果就是dp[0][1]
int l = binary.length();
int[][] dp = new int[l + 1][2];
int MOD = (int)1e9 + 7;
int zero = 0; //0单独判断是否存在,因为最后输出的是1开头的
for(int i = l - 1; i >= 0; i--){
if(binary.charAt(i) == '0'){
zero = 1;
dp[i][0] = (dp[i + 1][0] + dp[i + 1][1] + 1) % MOD;
dp[i][1] = dp[i + 1][1];
}else{
dp[i][1] = (dp[i + 1][0] + dp[i + 1][1] + 1) % MOD;
dp[i][0] = dp[i + 1][0];
}
}
return (dp[0][1] + zero) % MOD;
}
}
看了 940. 不同的子序列 II 这个题再来看这道题,都说这两个题像
都是找不同的子序列
940中,是判断dp[i],前i-1个字母,有没有和第i个字母相同的,如果没有,就能够在原dp[i - 1]个序列上加上当前字符;如果有,那么就找最近出现的相同的字符的位置,假设是j,因为添加相同的字符,相当于在dp[j - 1]个序列上,加了相同的字符,就重复了,所以要减去dp[j - 1]
那么换到这道题,全部是0和1组成的字符串,那么就相当于是两个不同的字母,思路也是一样
class Solution {
public int numberOfUniqueGoodSubsequences(String binary) {
/*
看了 940. 不同的子序列 II 这个题再来看这道题,都说这两个题像都是找不同的子序列
940中,是判断dp[i],前i-1个字母,有没有和第i个字母相同的,
如果没有,就能够在原dp[i - 1]个序列上加上当前字符;
如果有,那么就找最近出现的相同的字符的位置,假设是j,
因为添加相同的字符,相当于在dp[j - 1]个序列上,加了相同的字符,就重复了,所以要减去dp[j - 1]
那么换到这道题,全部是0和1组成的字符串,那么就相当于是两个不同的字母,思路也是一样,
*/
int MOD = (int)1e9 + 7;
int l = binary.length();
int[] dp = new int[l + 1];
//dp[0] = 1; //空字符串视为有效
int[] last = new int[2];
last[0] = -1; //上一个0出现的位置
last[1] = -1; //上一个1出现的位置
for(int i = 0; i < l; i++){
int temp = binary.charAt(i) - '0';
//如果之前没出现过这个字符
if(last[temp] == -1){
//如果没出现过1,那么就是乘2 + 1,因为以当前1开始也可以
if(temp == 1)
dp[i + 1] = (2 * dp[i] + 1) % MOD;
else
dp[i + 1] = (2 * dp[i]) % MOD;
}else{
dp[i + 1] = ((dp[i] * 2) % MOD - dp[last[temp]]) % MOD;
dp[i + 1] = (dp[i + 1] + MOD) % MOD;
}
last[temp] = i;
}
int zero = 0;
if(binary.contains("0"))
zero = 1;
return (dp[l] + zero) % MOD;
}
}
以上是关于第 256 场力扣周赛(状态压缩+dp,二进制子序列的动规940)的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 第 58 场力扣夜喵双周赛(动态规划马拉车算法,前后缀处理)/ 第 253 场力扣周赛(贪心,LIS)
第 254 场力扣周赛(KMP贪心快速幂二分+多源bfs并查集 + 时光倒流)
LeetCode494. 目标和 / 474. 一和零 / 203. 移除链表元素 / 第 244 场力扣周赛
LeetCode 第 59 场力扣夜喵双周赛(最短路径数+迪杰斯特拉动态规划+最长公共前缀问题) / 第255场周赛(二进制转换,分组背包,子集还原数组(脑筋急转弯))