第 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场周赛(二进制转换,分组背包,子集还原数组(脑筋急转弯))

力扣周赛有含金量吗

解题报告力扣 第 271 场周赛