LeetCode第19场双周赛(Biweekly Contest 19)解题报告

这周的双周赛总体简单,主要是最后一题有些难度。

第一题:模拟。

第二题:滑动窗口。

第三题:数学。

第四题:0 / 1 最短路(BFS)。

详细题解如下。


1. 将数字变成 0 的操作次数(Number of Steps to Reduce A Number to Zero)

            AC代码(C++)

2. 大小为 K 且平均值大于等于阈值的子数组数目(Number of Sub Arrays of Size K And Average Greater Than or Equal to Threshold)

            AC代码(C++)

3.时钟指针的夹角(Angle Between Hands of A Clock)

            AC代码(C++)

4.跳跃游戏 IV(Jump Game IV)

            AC代码(C++)


LeetCode第19场双周赛地址:

https://leetcode-cn.com/contest/biweekly-contest-19/


1. 将数字变成 0 的操作次数(Number of Steps to Reduce A Number to Zero)

题目链接

https://leetcode-cn.com/problems/number-of-steps-to-reduce-a-number-to-zero/

题意

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。

示例 1:

输入:num = 14
输出:6
解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 2) 7 是奇数,减 1 得到 6 。
步骤 3) 6 是偶数,除以 2 得到 3 。
步骤 4) 3 是奇数,减 1 得到 2 。
步骤 5) 2 是偶数,除以 2 得到 1 。
步骤 6) 1 是奇数,减 1 得到 0 。

提示:

  • 0 <= num <= 10^6

解题思路

题目意思很简单,其实在整数中除 2 相当于位运算的右移 1 位。对于 int 型,32位,所以最多循环 62 次(如果是奇数,相当于先 - 1,再 >> 1。如果是偶数,那就是 >> 1)

因此根据题目意思进行模拟即可,不会超时。

 

AC代码(C++)

class Solution {
public:
    int numberOfSteps (int num) {
        int cnt = 0;
        while(num)
        {
            if(num % 2 == 0)
                num /= 2;
            else
                --num;

            ++cnt;
        }
        return cnt;
    }
};

 


2. 大小为 K 且平均值大于等于阈值的子数组数目(Number of Sub Arrays of Size K And Average Greater Than or Equal to Threshold)

题目链接

https://leetcode-cn.com/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/

题意

给你一个整数数组 arr 和两个整数 k 和 threshold 。

请你返回长度为 k 且平均值大于等于 threshold 的子数组数目。

示例 1:

输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4
输出:3
解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。

示例 3:

输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5
输出:6
解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。

提示:

  • 1 <= arr.length <= 10^5
  • 1 <= arr[i] <= 10^4
  • 1 <= k <= arr.length
  • 0 <= threshold <= 10^4

 

解题思路

看到想到的先是,暴力求和每一段 k 的和,复杂度是 O(n ^ 2),这样子会超时。

经过分析,我们可以发现,当我们计算得到某一段的值 sum 时,对于下一段的值,可以不用重新计算那么多,其实就是 sumNow = sum + 后面新进来的那个值 - 前面出去的值。

这样子,我们只需要遍历一次 O(N) 的复杂度就是可以,所有 长度为 k  的子数组的和,然后再判断每一段的平均值是否大于 阈值(平均值 > 可以转化为 sum > k * 阈值,把 除法 转化为 乘法)。.

也就是使用了,用 k 的窗口去滑动计算值

 

AC代码(C++)

class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int n = arr.size();
        if(n < k) return 0;

        int cnt = 0;
        int sum = 0;
        // 第一段长度为 k 的子数组和 
        for(int i = 0;i < k;++i)
        {
            sum += arr[i];
        }
        if(sum >= k * threshold) ++cnt;
        
        // 新的子数组和,加上当前值,减去最前面的那个值
        for(int i = k;i < n;++i)
        {
            sum = sum - arr[i - k] + arr[i];
            if(sum >= k * threshold) ++cnt;
        }
        return cnt;
    }
};

 


3.时钟指针的夹角(Angle Between Hands of A Clock)

题目链接

https://leetcode-cn.com/problems/angle-between-hands-of-a-clock/

题意

给你两个数 hour 和 minutes 。请你返回在时钟上,由给定时间的时针和分针组成的较小角的角度(60 单位制)。

示例 1:

(示例有图,具体看链接)

输入:hour = 12, minutes = 30
输出:165

提示:

  • 1 <= hour <= 12
  • 0 <= minutes <= 59
  • 与标准答案误差在 10^-5 以内的结果都被视为正确结果。

解题分析

一道数学分析题,我们可以以 12 点时候为 0 角度,分别计算 时针和分针的角度,然后两个角度作差后,取这个差值和 360 - 差值中的最小值(因为角度是 <= 180的)。

先分析 分针的角度,总共 60 m 分 360 °,所以是 6 ° / m,所以分针的角度就是 = 6 * 分针时间

接着分析 时针的角度,总共 12 h 分 360°,所以是 30 ° / h,但是在一小时内,时针走过的角度,还会与分针有关(可以看第一个示例图),所以还要再加上角度由于分针走的角度,那么在一个小时 30 °,会由于时针 60 m,所以也就是 0.5 °/ m。所以时针的角度总共是 = 30 * 时针 + 0.5 * 分针。

注意的地方,根据数据范围 h 是 1 - 12,但我们由于 12 点是 0 °,所以我们对时针要使得 12 变成 0,其他不影响,所以先时针对 12 进行取模。

这道题不难,主要就是数学分析后,再进行按最后结果计算即可。

 

AC代码(C++)

class Solution {
public:
    double angleClock(int hour, int minutes) {
        hour %= 12;
        double hAng = 0.5 * minutes + 30 * hour;
        double mAng = 6 * 1.0 * minutes;

        double res = fabs(hAng - mAng);

        res = min(res, 360 - res);
        return res;
    }
};

 


4.跳跃游戏 IV(Jump Game IV)

题目链接

https://leetcode-cn.com/problems/jump-game-iv/

题意

给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。

每一步,你可以从下标 i 跳到下标:

  • i + 1 满足:i + 1 < arr.length
  • i - 1 满足:i - 1 >= 0
  • j 满足:arr[i] == arr[j] 且 i != j

请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。

注意:任何时候你都不能跳到数组外面。

示例 1:

输入:arr = [100,-23,-23,404,100,23,23,23,3,404]
输出:3
解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。

示例 2:

输入:arr = [11,22,7,7,7,7,7,7,7,22,13]
输出:3

提示:

  • 1 <= arr.length <= 5 * 10^4
  • -10^8 <= arr[i] <= 10^8

解题分析

又是跳跃游戏,某次周赛的跳跃游戏 III,是用动态规划解决,因此我们首先先考虑能不能用动态规划解决。根据数据范围来看,如果用动态规划做的,那只能 dp[ i ] 一维(二维会超时了),那么根据转移关系来看,也是可以找到的,但是问题来了,动态规划的顺序无法得知,因为有第三种跳跃方式,所以无法更新状态。

所以动态规划做不了

那么我们再从头看题目,它告诉了我们转移关系,是不是告诉我们,从一个点,可以到其他点的方式,然后问我们到达终点的最少操作次数(也就是距离),那么可以看成是问,最短路问题,也就是给的三种跳跃关系,是告诉了我们节点之间的连接关系(邻接表),然后问最短路,由于这个是 无权图(0 / 1) 的最短路问题,所以用 BFS解决。

那么知道了BFS,剩下的就是,建立邻接表由于相同数字的下标是可以到处走的,所以我们用 map<int, vector<int> >,map的key来筛选相同的值,相同值的下标放在一起,表示可以互通(剩下的就是前跳和后跳)。

接着就是BFS模板即可。

 

但是直接BFS会发现超时,为什么呢?是因为,如果当所有数都是相同的,除了终点的数不同,那么根据BFS,我们每一次到了节点就要看这个点的所有相邻点,那么如果所有数都是相同的,对于每一个数,我们都要邻接表扫一次,所以时间复杂度就被卡成了 O(N ^ 2),那么就会超时。

 

所以重点在于,第三种跳跃方式,由于是最短路BFS,先入队的肯定是最短距离,因为 只要一个数的下标进行第三种跳跃方式了,那么下次相同这个数的下标就不要去遍历 邻接表了(因为前面已经入过队了),所以我们新开一个数组,用于记录某个点,是不是经历了第三种跳跃方式,如果经历了,那么下次再到这些点,我们就不用考虑第三种跳跃方式了。

 

所以这道题就是 BFS最短路 + 小处理(防止数据卡成 O(N ^ 2) )。

 

AC代码(C++)

const int MAXN = 5e4 + 15;

class Solution {
public:
    int vis[MAXN], same[MAXN], dist[MAXN];
    map<int, vector<int> > g;  // 相同数字的下标放在一块儿
    queue<int> q;

    int minJumps(vector<int>& arr) {
        g.clear();
        while(!q.empty()) q.pop();
        
        int n = arr.size();
        for(int i = 0;i < n;++i) g[arr[i]].push_back(i);
        
        // BFS最短路模板
        memset(vis, 0, sizeof(vis));
        memset(same, 0, sizeof(same));
        memset(dist, -1, sizeof(dist));
        q.push(0);
        vis[0] = 1;
        dist[0] = 0;

        while(!q.empty())
        {
            int cur = q.front();
            q.pop();
            if(cur == n - 1)
            {
                return dist[n - 1];
            }

            if(cur + 1 < n && vis[cur + 1]==0)
            {
                q.push(cur + 1);
                dist[cur + 1] = dist[cur] + 1;
                vis[cur + 1] = 1;
            }

            if(cur - 1 >= 0 && vis[cur - 1]==0)
            {
                q.push(cur - 1);
                dist[cur - 1] = dist[cur] + 1;
                vis[cur - 1] = 1;
            }
            
            // 重点,第三种跳跃方式,如果前面这个数相同的已经经历了,那我们就不用考虑了
            if(same[cur] == 0)
            {
                for(int i = 0;i < g[arr[cur]].size();++i)  // 找相同数的所有下标
                {
                    int next = g[arr[cur]][i];  
                    if(vis[next] == 1) continue;

                    q.push(next);
                    dist[next] = dist[cur] + 1;
                    vis[next] = 1;
                    
                    same[next] = 1;  // 这些相同数的下标都已经,经历了第三种跳跃方式了
                }
            }
        }
        return -1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值