LeetCode 242周赛

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 242周赛相关的知识,希望对你有一定的参考价值。

242周赛

今日参加第一次参加周赛,昨天看了一下周赛的奖励,积分挺多的,而且自认为做了也有不短时间题了,最起码能过两道吧,结果就过了一道,虽说在宿舍做电脑用着确实不太习惯,虽说第一次参加有点不太熟悉流程,虽说第二道题我感觉我是做的对的,哈哈,不管咋样,还是菜,搞的我心态有点崩

5763. 哪种连续子字符串更长

题目描述

给你一个二进制字符串 s 。如果字符串中由 1 组成的 最长 连续子字符串 严格长于 由 0 组成的 最长 连续子字符串,返回 true ;否则,返回 false 。

    例如,s = "110100010" 中,由 1 组成的最长连续子字符串的长度是 2 ,由 0 组成的最长连续子字符串的长度是 3 。

注意,如果字符串中不存在 0 ,此时认为由 0 组成的最长连续子字符串的长度是 0 。字符串中不存在 1 的情况也适用此规则。


示例 1:

输入:s = "1101"
输出:true
解释:
由 1 组成的最长连续子字符串的长度是 2:"1101"
由 0 组成的最长连续子字符串的长度是 1:"1101"
由 1 组成的子字符串更长,故返回 true 。

示例 2:

输入:s = "111000"
输出:false
解释:
由 1 组成的最长连续子字符串的长度是 3:"111000"
由 0 组成的最长连续子字符串的长度是 3:"111000"
由 1 组成的子字符串不比由 0 组成的子字符串长,故返回 false 。

示例 3:

输入:s = "110100010"
输出:false
解释:
由 1 组成的最长连续子字符串的长度是 2:"110100010"
由 0 组成的最长连续子字符串的长度是 3:"110100010"
由 1 组成的子字符串不比由 0 组成的子字符串长,故返回 false 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longer-contiguous-segments-of-ones-than-zeros
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

唯一通过的一道题,看了一下,用了13分钟,当时感觉也就用了10来分钟,最起码第二道应该也能做出来
写的有点长,不过当时也没管那么多,直接交了过了

class Solution {
    public boolean checkZeroOnes(String s) {
        //最长连续子串
        int l = s.length();
        char c = s.charAt(0);
        int max0 = 0;
        int max1 = 0;
        int count = 1;
        for(int i = 1; i < l; i++){
            char curr = s.charAt(i);
            if(curr == c){
                count++;
            }else{
                if(c == '1'){
                    max1 = Math.max(max1, count);
                    count = 1;
                }else{
                    max0 = Math.max(max0, count);
                    count = 1;
                }
            }
            c = curr;
        }
        
        if(c == '1'){
            max1 = Math.max(max1, count);
        }else{
            max0 = Math.max(max0, count);
        }        
        return max1 > max0;         
    }
}

5764. 准时到达的列车最小时速

题目描述

给你一个浮点数 hour ,表示你到达办公室可用的总通勤时间。要到达办公室,你必须按给定次序乘坐 n 趟列车。另给你一个长度为 n 的整数数组 dist ,其中 dist[i] 表示第 i 趟列车的行驶距离(单位是千米)。

每趟列车均只能在整点发车,所以你可能需要在两趟列车之间等待一段时间。

    例如,第 1 趟列车需要 1.5 小时,那你必须再等待 0.5 小时,搭乘在第 2 小时发车的第 2 趟列车。

返回能满足你准时到达办公室所要求全部列车的 最小正整数 时速(单位:千米每小时),如果无法准时到达,则返回 -1 。

生成的测试用例保证答案不超过 10^7 ,且 hour 的 小数点后最多存在两位数字 。


示例 1:

输入:dist = [1,3,2], hour = 6
输出:1
解释:速度为 1 时:
- 第 1 趟列车运行需要 1/1 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 1 小时发车的列车。第 2 趟列车运行需要 3/1 = 3 小时。
- 由于是在整数时间到达,可以立即换乘在第 4 小时发车的列车。第 3 趟列车运行需要 2/1 = 2 小时。
- 你将会恰好在第 6 小时到达。

示例 2:

输入:dist = [1,3,2], hour = 2.7
输出:3
解释:速度为 3 时:
- 第 1 趟列车运行需要 1/3 = 0.33333 小时。
- 由于不是在整数时间到达,故需要等待至第 1 小时才能搭乘列车。第 2 趟列车运行需要 3/3 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 2 小时发车的列车。第 3 趟列车运行需要 2/3 = 0.66667 小时。
- 你将会在第 2.66667 小时到达。

示例 3:

输入:dist = [1,3,2], hour = 1.9
输出:-1
解释:不可能准时到达,因为第 3 趟列车最早是在第 2 小时发车。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-speed-to-arrive-on-time
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

当时看到这个题,瞬间就想到了二分,虽然预感到这个double会有问题,但是我感觉测试几次应该能过,心里很开心
开始我想着是把开始二分时候的左右边界先确定一下,然后就想着最慢的速度应该是 距离/时间, 最快应该是一个小时经过一个距离最长的地点;然后二分,确定当前速度能不能到达,不行就把速度变大,反之减小

然后败倒在一个[1,1,100000],2.01(几个0我记不清了)测试用例上,给的答案是更多的0的一个数,然后我就想到了,最后一站这个小数点后面的数会起作用,而且右边界也不能这么给。然后索性就把左边界直接写成1,右边界直接写成最大值了,然后就发现最初的几个示例也过不了了,输出都是1,然后我就懵了,咋回事???

可能当时有点紧张吧,我现在一看,给最大值以后,因为我写的是left+right,当时也是图省事,没写(right - left) / 2 + left…直接越界了…真想捶死自己…
下面是我当时写的代码

class Solution {
    public int minSpeedOnTime(int[] dist, double hour) {
        //第一辆车发车时间是0, 第二辆车最早发车时间是1,后面最早发车时间以此类推;
        //因此最慢的速度是 和 / hour
        //最快的速度是max
        //然后二分求最小
        
        //int max = 0;
        //int sum = 0;
        int l = dist.length;
        //for(int dis : dist){
        //    max = Math.max(max, dis);
        //    sum += dis;
        //}
        if(hour < (l - 1) * 1.0)
            return -1;
        int left = 1;
        int right = Integer.MAX_VALUE;
        while(left < right){
            int mid = (left + right) >> 1;
            if(judge(dist, mid, hour)){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
    
    public boolean judge(int[] dist, int mid, double hour){
        for(int dis : dist){
            if(dis * 1.0 / (mid * 1.0) > (double)(dis / mid))
                hour -= (double)(dis / mid) + 1;
            else{
                hour -= (double)(dis / mid);
            }
        }
        return hour >= 0.0;
    }
}

改了那个右边界以后,然后把判断方法中,最后一步的判断重新写了一下,就过了…
不过向上取整看了一下还是写的繁琐了
向上取整可以用(dist[i] + mid - 1) / mid

class Solution {
    public int minSpeedOnTime(int[] dist, double hour) {
        //第一辆车发车时间是0, 第二辆车最早发车时间是1,后面最早发车时间以此类推;
        //因此最慢的速度是 和 / hour
        //最快的速度是max
        //然后二分求最小
        
        int l = dist.length;     
        if(hour < (l - 1) * 1.0)
            return -1;
        int left = 1;
        int right = 10000000;
        while(left < right){
            int mid = (left + right) >> 1;
            if(judge(dist, mid, hour)){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
    
    public boolean judge(int[] dist, int mid, double hour){
        for(int i = 0; i < dist.length - 1; i++){
            int dis = dist[i];
             if(dis * 1.0 / (mid * 1.0) > (double)(dis / mid))
                hour -= (double)(dis / mid) + 1;
            else{
                hour -= (double)(dis / mid);
            }
        }
        return hour - dist[dist.length - 1] * 1.0 / (mid * 1.0) >= 0.0;
    }
}

5765. 跳跃游戏 VII

题目描述

给你一个下标从 0 开始的二进制字符串 s 和两个整数 minJump 和 maxJump 。一开始,你在下标 0 处,且该位置的值一定为 '0' 。当同时满足如下条件时,你可以从下标 i 移动到下标 j 处:

    i + minJump <= j <= min(i + maxJump, s.length - 1) 且
    s[j] == '0'.

如果你可以到达 s 的下标 s.length - 1 处,请你返回 true ,否则返回 false 。


示例 1:

输入:s = "011010", minJump = 2, maxJump = 3
输出:true
解释:
第一步,从下标 0 移动到下标 3 。
第二步,从下标 3 移动到下标 5 。

示例 2:

输入:s = "01101110", minJump = 2, maxJump = 3
输出:false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game-vii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

由于在前面那个题卡了很久,做这道题没多长时间了,
但是当时一看,这不就是动态规划吗,然后就写了下面代码

class Solution {
    public boolean canReach(String s, int minJump, int maxJump) {
        int l = s.length();
        boolean[] dp = new boolean[l];
        dp[0] = true;
        for(int i = 1; i < l; i++){
            if(s.charAt(i) == '1'){
                dp[i] = false;
                continue;
            }
            int left = i - maxJump;
            int right = i - minJump;
            
            for(int j = left; j <= right; j++){
                if(j < 0)
                    continue;
                if(dp[j]){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[l - 1];
    }
}

超时了,有点意外,不过要是不超时,应该也不算个中等题了,最后想了一会没想出来怎么优化,就去看了一眼最后一题,到点了,吃饭

主要优化点,应该是中间线性查找那部分,这里前缀和用的很巧妙
是dp数组的前缀和
可以将为true的点,dp[i]值赋为1,这样,如果一个点能从left到right范围内跳过来,那么说明这个范围内肯定有dp[left] + dp[left + 1]… + dp[right] != 0,而这个式子可以用求前缀和的方式以O(1)的时间复杂度得到!
具体实现过程中,在计算dp数组的同时计算前缀和数组

class Solution {
    public boolean canReach(String s, int minJump, int maxJump) {
        //使用前缀和优化
        //如果能转移,那么left到right之间肯定dp[i]的和是大于1的
        int l = s.length();
        int[] dp = new int[l];
        int[] pre = new int[l];
        pre[0] = 1;

        dp[0] = 1;      //表示该位置可达
        for(int i = 1; i < l; i++){
            int left = i - maxJump;
            int right = i - minJump;
            if(s.charAt(i) == '0'){
                //如果右边界小于0,则无法转移
                if(right < 0){

                }      
                //右边界不小于0,左边界小于0,那么可以从0到right转移
                else if(left <= 0){
                    dp[i] = pre[right] != 0 ? 1 : 0;
                }else{
                    dp[i] = (pre[right] - pre[left - 1]) != 0 ? 1 : 0;
                }
            }
            pre[i] = pre[i - 1] + dp[i];
            
        }
        return dp[l - 1] == 1;
    }
}

看到另一个巧妙的思路:
之前一直都想的是当前位置从前面位置转移而来,而这个思路是根据当前位置,确定后面多少个位置可以到达;如果不加以优化,会导致重复判断很多位置,会超时。因此需要优化,对于已经判断过的位置,设置一个变量来标记上一次循环中,到达的最右边位置,下次判断根据这个位置来选择左边界left。

class Solution {
    public boolean canReach(String s, int minJump, int maxJump) {
        //另一个思路,如果能到达当前位置,则标记后面能达到的位置,并对重复部分进行优化
        int l = s.length();
        int[] dp = new int[l];

        dp[0] = 1;      //表示该位置可达
        int limit = 0;  //上一轮达到的最远下标
        for(int i = 0; i < l; i++){
            //如果该位置可达
            if(dp[i] == 1){
                int right = Math.min(i + maxJump, l - 1);
                int left = Math.max(limit, i + minJump);
                for(int j = left; j <= right; j++){
                    if(s.charAt(j) == '0')
                        dp[j] = 1;
                } 
                limit = right;              
            }
            if(limit == l - 1)
                break;           
        }
        return dp[l - 1] == 1;
    }
}

5766. 石子游戏 VIII

题目描述

Alice 和 Bob 玩一个游戏,两人轮流操作, Alice 先手 。

总共有 n 个石子排成一行。轮到某个玩家的回合时,如果石子的数目 大于 1 ,他将执行以下操作:

选择一个整数 x > 1 ,并且 移除 最左边的 x 个石子。
将 移除 的石子价值之 和 累加到该玩家的分数中。
将一个 新的石子 放在最左边,且新石子的值为被移除石子值之和。
当只剩下 一个 石子时,游戏结束。

Alice 和 Bob 的 分数之差 为 (Alice 的分数 - Bob 的分数) 。 Alice 的目标是 最大化 分数差,Bob 的目标是 最小化 分数差。

给你一个长度为 n 的整数数组 stones ,其中 stones[i] 是 从左边起 第 i 个石子的价值。请你返回在双方都采用 最优 策略的情况下,Alice 和 Bob 的 分数之差 。

 

示例 1:

输入:stones = [-1,2,-3,4,-5]
输出:5
解释:
- Alice 移除最左边的 4 个石子,得分增加 (-1) + 2 + (-3) + 4 = 2 ,并且将一个价值为 2 的石子放在最左边。stones = [2,-5] 。
- Bob 移除最左边的 2 个石子,得分增加 2 + (-5) = -3 ,并且将一个价值为 -3 的石子放在最左边。stones = [-3] 。
两者分数之差为 2 - (-3) = 5 。
示例 2:

输入:stones = [7,-6,5,10,5,-2,-6]
输出:13
解释:
- Alice 移除所有石子,得分增加 7 + (-6) + 5 + 10 + 5 + (-2) + (-6) = 13 ,并且将一个价值为 13 的石子放在最左边。stones = [13] 。
两者分数之差为 13 - 0 = 13 。
示例 3:

输入:stones = [-10,-12]
输出:-22
解释:
- Alice 只有一种操作,就是移除所有石子。得分增加 (-10) + (-12) = -22 ,并且将一个价值为 -22 的石子放在最左边。stones = [-22] 。
两者分数之差为 (-22) - 0 = -22 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/stone-game-viii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

博弈的这种题,也做了几道了,一看到还是懵逼
关键点1:因为每次移除过后,还要把得到的分数放入原数组的最左边,所以数组的总和是不会变化的,而且每次取都是取这个数组的一个前缀和,因此需要预先处理前缀和
关键点2:题目中所说的,Alice目标是最大化分差,Bob目标是最小化分差,其实两个人的目标都是使自己的分数最大的同时,另一个人所能获得的分数最小,也就是分差最大,所以两个人的策略是相同的
关键点3:从模拟开始,一步步优化
其他就不写了,自己也整不太明白,这种题慢慢来吧
参考题解:https://leetcode-cn.com/problems/stone-game-viii/solution/bao-li-on-2-on-dpyou-hua-xue-dao-liao-by-6peh/

总结一下这种博弈题,两个人都时最优解,那么两个人的策略一般来说是一样的,然后就对这个过程先试着模拟,一般来说都时递归;然后再对递归进行优化
另一种情况,就是有数学规律,在什么条件下,一定赢或者一定输,这种题一般返回值是boolean类型,需要数学推导

class Solution {
    public int stoneGameVIII(int[] stones) {
        //看完没想太明白到底该怎么办,
        //下一个人的分数是上一个人的分数,加上当前选择的分数,因此总分不会发生变化
        //因为是从左边开始,因此每次一个人选择的是都是这个数组的前缀和
        //所以A的目标是使他自己的前缀和最大的同时B的前缀和最小
        
        //设dp[i]表示当Alice可以选择的下标u在[i,n)范围内时,Alice与Bob 分数的最大差值

        int l = stones.length;

        int[] pre = new int[l];
        pre[0] = stones[0];
        for(int i = 1; i < l; i++){
            pre[i] = pre[i - 1] + stones[i];
        }
        int[] dp = new int[l];
        dp[l - 1] = pre[l - 1];

        for(int i = l - 2; i >= 1; i--){
            dp[i] = Math.max(dp[i + 1], pre[i] - dp[i + 1]);
        }
        return dp[1];

    }
}

以后要是周末有空就参加周赛,感觉挺好的。这次马马虎虎算做了两道,下次目标三道,冲!!!!!

以上是关于LeetCode 242周赛的主要内容,如果未能解决你的问题,请参考以下文章

单周赛 242 题解

LeetCode周赛第 291 场周赛(Go语言实现版)

LeetCode周赛第 288 场周赛(Go语言实现版)

LeetCode周赛第 288 场周赛(Go语言实现版)

LeetCode周赛第 288 场周赛(Go语言实现版)

LeetCode周赛第 288 场周赛(Go语言实现版)