力扣周赛日记351

1. 前言

很难受,题2wa了很多发,一直卡一个case,想破脑瓜没想明白,还是太菜。然后题3比题2还简单,题4模拟细节巨多,补题的时候看到灵神的c++代码大为震撼。

2. 赛题

LeetCode 第 351 场周赛

2.1 题1

LeetCode 2748. 美丽下标对的数目

2.1.1 题意

数组中相对位置不同的两个数,如果位置前的数的最高位和位置处于后的最低位互质,那么这是一个美丽数对。求数组中美丽数对的数目。

2.1.2 分析

数据范围限制最高位最低为都是[1,9] 时间复杂度上限可以到O( n 3 n^3 n3)

2 <= nums.length <= 100
1 <= nums[i] <= 9999
nums[i] % 10 != 0

先遍历一遍数组把每个位置的最高位最低为求出记录下。
然后枚举数组位置i, j,
判断对应两个数的高位、低位最大公因数是否为1(这里可以直接枚举,因为两个数范围都是[1,9],是常量级,也可以先做个表,把所有情况记录)

2.1.3 我的解法

class Solution {
public:
    int gcd(int x, int y){
        // 1 2 3 4 5 6 7 8 9
        // 1 2 3 4 5 6 7 8 9
        for(int i=9; i>=2; i--){
            if((x%i==0) && (y%i==0)){
                return i;
            }
        }
        // 最大公因数为1
        return 1;
    }
    int countBeautifulPairs(vector<int>& nums) {
        int n = nums.size();
        vector<int> b(n, 0);
        vector<int> e(n, 0);
        // 求出最高位和最低位
        for(int i=0; i<n; i++){
            // 最低为直接取模
            e[i] = nums[i]%10;
            int tmp = nums[i];
            // 最高位需要不断除10
            while(tmp>=10){
                tmp/=10;
            }
            b[i] = tmp;
        }
        int res = 0;
        for(int i=0; i<n; i++){
            for(int j=i+1; j<n; j++){
                // 最大公因数
                res += (gcd(b[i], e[j]) == 1);
            }
        }
        return res;
    }
};

2.1.4 学习题解反思

我的解法:
时间复杂度O( n 2 n^2 n2)
空间复杂度O( n n n)

灵神太强了,以后跟着灵神学了,用一个表记下已经出现的最高位的数字的次数,然后改为枚举最高位的可能(就是[1,9]),可以降时间。

2.1.5 bug日记

第一题wa一发,菜哭。求最高位的时候的循环结束条件边界取错(错误情况取tmp>10)。

2.2 题2

LeetCode 6471. 得到整数零需要执行的最少操作数

2.2.1 题意

给定两个数num1和num2,每次对 num1 减去 2^i + num2 ,求使num1变成0的次数

2.2.2 分析

假设对num1做k次减后变成0,那么有
n u m 1 = k ∗ n u m 2 + 2 i 1 + 2 i 2 + . . . + 2 i k num1=k*num2+2^{i_1}+2^{i_2}+...+2^{i_k} num1=knum2+2i1+2i2+...+2ik
即:
n u m 1 − k ∗ n u m 2 = 2 i 1 + 2 i 2 + . . . + 2 i k num1-k*num2=2^{i_1}+2^{i_2}+...+2^{i_k} num1knum2=2i1+2i2+...+2ik

考虑等式右边的含义:
2 i 1 2^{i_1} 2i1表示二进制下第 i 1 i_1 i1位为1, 2 i 2 2^{i_2} 2i2表示二进制下第 i 2 i_2 i2位为1…
但是如果两个相同的位相加,为1的位数会-1,如 i 1 = = i 2 , 2 i 1 + 2 i 2 = 2 i 1 + 1 i_1==i_2,2^{i_1}+2^{i_2}=2^{i_1+1} i1==i22i1+2i2=2i1+1
也就是说,等式右边是在二进制表示下1的个数最多只有k位的数。

那么他有下界嘛,可以简单预料到二进制下1的个数最少为1。
这是一个粗略的下界,对于等式右边每个数有 2 i j > = 1 2^{i_j}>=1 2ij>=1
那么 n u m 1 − k ∗ n u m 2 > = k num1-k*num2 >= k num1knum2>=k

1 <= num1 <= 10^9
-10^9 <= num2 <= 10^9

总结一下,满足以下条件的最小值即为答案
b i t C o u n t ( n u m 1 − k ∗ n u m 2 ) < = k bitCount(num1-k*num2)<=k bitCount(num1knum2)<=k && n u m 1 − k ∗ n u m 2 > = k num1-k*num2 >=k num1knum2>=k

2.2.3 我的解法

class Solution {
public:
    long bitCount(long x){
        long res = 0;
        while(x!=0){
            // 用lowbit求1的个数
            x &= (x-1);
            res++;
        }
        return res;
    }
    int makeTheIntegerZero(int num1, int num2) {
        // nums1 = 2^a + 2^b + 2^c + k * nums2
        int res = 0;
        // 防溢出
        long n1 = num1;
        long n2 = num2;
        for(long i=1; i<=64; i++){
            // 枚举符合条件的i,
            // i不可能为0次,因为num1!=0
            n1 -= n2; // 每次递减
            if((n1)>=i && bitCount(n1) <=i){
                // 满足条件即为结果
                return i;
            }
        }
        return -1;   
    }
};

2.2.4 学习题解反思

我的解法:
时间复杂度O(1), (这个复杂度极不严谨,建议学习灵神
空间复杂度O(1)

关于结果上界是64,有没有可能出现64以上次数的结果?
当k=64时, n u m 1 − k ∗ n u m 2 num1-k*num2 num1knum2的结果中有至少65位为1才能出现该情况。
也就是 n u m 1 − k ∗ n u m 2 num1-k*num2 num1knum2溢出long long型。
但是注意看数据范围最坏情况 n u m 1 = 1 0 9 , n u m 2 = − 1 0 9 , 此时 n u m 1 − 64 ∗ n u m 2 = 65 ∗ 1 0 9 < L O N G _ M A X num1=10^9,num2=-10^9,此时num1-64*num2=65*10^9<LONG\_MAX num1=109,num2=109,此时num164num2=65109<LONG_MAX不会溢出,所以一个粗浅的答案上界为64.

2.2.5 bug日记

2.2.5.1 下限估计不准

很可惜,周赛的时候下限估计的不够准确,导致最后一个hidden case过不了,看了下评论区,也有不少一样情况兄弟。我还是太菜了。

2.3 题3

LeetCode 2750. 将数组划分成若干好子数组的方式

2.3.1 题意

对数组进行划分,每个划分后的子数组只含有1个1,求划分的方法数

2.3.2 分析

看一下数据范围,时间复杂度上限到O( n ∗ l o g n n*logn nlogn)

1 <= nums.length <= 10^5
0 <= nums[i] <= 1

要求划分的子数组恰好含有1个1,那么可以划分的地方就是数组中两个1的位置之间。
举个例子

数组 [0,1,0,0,1] 可以划分的位置有
[0,1] [0,0,1]
[0,1,0] [0,1]
[0,1,0,0] [1]

这只是两个1之间的划分方式,后面可能还有别的1,它的结果应该是相邻的1的划分种类数乘积在一起。

注意细节上,出现全0没有划分的情况

2.3.3 我的解法

class Solution {
public:
    int numberOfGoodSubarraySplits(vector<int>& nums) {
        int mod = 1e9+7;
        long res = 1;
        int n = nums.size();
        vector<int> x;
        // 找到为1的下标 从小到大顺序
        for(int i=0; i<n; i++){
            if(nums[i]==1)
                x.emplace_back(i);
        }
        int m = x.size();
        // 如果没有出现1
        if(x.size() == 0){
            return 0;
        }
        // 获得结果
        for(int i=1; i<m; i++){
            res = ( (res%mod) * ((x[i]-x[i-1]) %mod) )%mod;
        }
        return res;
    }
};

2.3.4 学习题解反思

我的解法:
时间复杂度O( n n n),
空间复杂度O( n n n)

2.3.5 bug日记

2.3.5.1 特殊情况

全0的情况没考虑,直接wa一发,菜哭。

2.4 题4

LeetCode 2751. 机器人碰撞

2.4.1 题意

n个机器人在水平轴向左/右运动,每个机器人有初始健康值,若机器人发送碰撞,健康值大的机器人健康值-1,健康值小的机器人直接消失,健康值相同两个机器人都消失。求最后活着的机器人的健康值,按初始给定的顺序返回。

2.4.2 分析

看一下数据,复杂度大概在O( n ∗ l o g n n*logn nlogn)级别。

1 <= positions.length == healths.length == directions.length == n <= 10^5
1 <= positions[i], healths[i] <= 10^9
directions[i] == ‘L’ 或 directions[i] == ‘R’
positions 中的所有值互不相同

按照位置排序
找到机器人,这个机器人往左边运动,且机器人左边有往右运动的机器人
把往右运动的机器人按位置依次放入栈中
模拟碰撞事件

2.4.3 我的解法

class Robot{
public:
    // 定义类
    int pos;
    int healths;
    char dir;
    // 下标,因为要按原顺序排结果
    int index;
    Robot (int p, int h, int d, int i): pos(p), healths(h), dir(d), index(i){}
    bool operator < (Robot r){
        // 重载
        return this->pos < r.pos;
    }
    bool operator > (Robot r){
        return this->pos > r.pos;
    }
};
bool cmp(Robot a, Robot b){
    // 按下标顺序排序
    return a.index < b.index;
}
class Solution {
public:
    vector<int> survivedRobotsHealths(vector<int>& positions, vector<int>& healths, string directions) {
        int n = positions.size();
        vector<Robot> robot;
        // sort by position
        // 按照位置排序
        for(int i=0; i<n; i++){
            robot.emplace_back(Robot(positions[i], healths[i], directions[i], i) );
        }
        sort(robot.begin(), robot.end());

        // collision
        // 发送碰撞
        stack<int> right;
        // 按照位置排序
        // 找到机器人,这个机器人往左边运动,且机器人左边有往右运动的机器人
        // 把往右运动的机器人按位置依次放入栈中
        // 模拟碰撞事件
        for(int i=0;i<n;i++){
            if(robot[i].dir == 'L'){
                if(right.empty()){
                    // no robot move twards right
                    // 这个机器人左边没有向右运动的机器人
                    // 不会继续发送碰撞
                    continue;
                }
                else{
                    // do collision
                    while(1){
                        if(right.empty()){
                            // 这个机器人左边没有向右运动的机器人
                            // 不会继续发送碰撞
                            break;
                        }
                        int ind = right.top();
                        right.pop();
                        if(robot[ind].healths == robot[i].healths){
                            // 向左向右的抵消了
                            robot[ind].healths = 0;
                            robot[i].healths = 0;
                            // 向左运动的噶了 不能接着创
                            break;
                        }
                        else if(robot[ind].healths < robot[i].healths){
                            // 向右运动的被创噶了 
                            // 向左运动的可以接着创
                            robot[ind].healths = 0;
                            robot[i].healths -= 1;
                        }
                        else{
                            robot[ind].healths -= 1;
                            robot[i].healths = 0;
                            right.push(ind);
                            // 向左运动的噶了 不能接着创
                            break;
                        }
                    }
                }
            }
            else{
                right.push(i);
            }
        }

        vector<int> res;
        // sort by index and find res
        // 按结果排序
        sort(robot.begin(), robot.end(),cmp);
        for(int i=0; i<n; i++){
            if(robot[i].healths != 0){
                // 没被创ga的机器人
                res.emplace_back(robot[i].healths);
            }
        }
        return res;
    }
};

2.4.4 学习题解反思

时间复杂度O( n ∗ l o g n n*logn nlogn),
空间复杂度O( n n n)

别问,问就是直接学的题解,灵神的c++的解法也太妙了,是真优雅,学习了。

3. 后记

仅分享自己的想法,有意见和指点非常感谢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值