LeetCode 第202场周赛 题解

有点简单

文章目录

  • 存在连续三个奇数的数组
    • a.题目
    • a.分析
    • a.参考代码
  • 使数组中所有元素相等的最小操作数
    • b.题目
    • b.分析
    • b.参考代码
  • 两球之间的磁力
    • c.题目
    • c.分析
    • c.参考代码
  • 吃掉 N 个橘子的最少天数
    • d.题目
    • d.分析
    • d.参考代码

存在连续三个奇数的数组

a.题目

给你一个整数数组 arr,请你判断数组中是否存在连续三个元素都是奇数的情况:如果存在,请返回 true ;否则,返回 false 。

示例 1

输入:arr = [2,6,4,1]
输出:false
解释:不存在连续三个元素都是奇数的情况。

示例 2

输入:arr = [1,2,34,3,4,5,7,23,12]
输出:true
解释:存在连续三个元素都是奇数的情况,即 [5,7,23] 。

提示

  • 1 <= arr.length <= 1000
  • 1 <= arr[i] <= 1000

a.分析

由于是连续的 所以一旦有偶数之后 肯定是会断开的 证明省略
那么设置一个计数即可

时间复杂度是一边扫描O(n)

a.参考代码

class Solution {
public:
    bool threeConsecutiveOdds(vector<int>& arr) {
        int cnt=0;
        for(int i=0;i<arr.size();i++)
            if(arr[i]&1){	//奇数
                cnt++;
                if(cnt==3)return true;
            }
            else cnt=0;	//偶数就置零 因为是必须连续 证明省略
        return false;
    }
};

使数组中所有元素相等的最小操作数

b.题目

存在一个长度为 n 的数组 arr ,其中 arr[i] = (2 * i) + 1 ( 0 <= i < n )。
一次操作中,你可以选出两个下标,记作 x 和 y ( 0 <= x, y < n )并使 arr[x] 减去 1 、arr[y] 加上 1 (即 arr[x] -=1 且 arr[y] += 1 )。最终的目标是使数组中的所有元素都 相等 。题目测试用例将会 保证 :在执行若干步操作后,数组中的所有元素最终可以全部相等。
给你一个整数 n,即数组的长度。请你返回使数组 arr 中所有元素相等所需的 最小操作数 。

示例 1

输入:n = 3
输出:2
解释:arr = [1, 3, 5]
第一次操作选出 x = 2 和 y = 0,使数组变为 [2, 3, 4]
第二次操作继续选出 x = 2 和 y = 0,数组将会变成 [3, 3, 3]

示例 2

输入:n = 6
输出:9

提示

  • 1 <= n <= 10^4

b.分析

这个一增一减 显然会往中间靠
所以最少次数肯定是最后靠拢到中间数上

先说结论:中间数为n
至于怎么出来的 你可以通过找规律
当然也可以通过讨论n为奇偶数的情况

  • 当n为奇数的时候
    • 中间数位置为n/2
    • 中间数为 2*(n/2)+1 == n
  • 当n为偶数的时候
    • 中间数位置为n/2-1和n/2中间
    • 中间数为 ((2*(n/2-1)+1)+(2*(n/2)+1))/2==((n-1)+(n+1))/2==n

那么知道中间数就好办了 直接把左区间距离中间数的次数加上

总的复杂度是O(n)

b.参考代码

class Solution {
public:
    int minOperations(int n) {
        int ans=0;
        for(int i=0;i<n/2;i++)	//只选取中间数左半边
            ans+=n-(2*i)-1;	//公式 n是中间的数
        return ans;
    }
};

两球之间的磁力

c.题目

在代号为 C-137 的地球上,Rick 发现如果他将两个球放在他新发明的篮子里,它们之间会形成特殊形式的磁力。Rick 有 n 个空的篮子,第 i 个篮子的位置在 position[i] ,Morty 想把 m 个球放到这些篮子里,使得任意两球间 最小磁力 最大。
已知两个球如果分别位于 x 和 y ,那么它们之间的磁力为 |x - y| 。
给你一个整数数组 position 和一个整数 m ,请你返回最大化的最小磁力。

示例 1

输入:position = [1,2,3,4,7], m = 3
输出:3
解释:将 3 个球分别放入位于 1,4 和 7 的三个篮子,两球间的磁力分别为 [3, 3, 6]。最小磁力为 3 。我们没办法让最小磁力大于 3 。

示例 2

输入:position = [5,4,3,2,1,1000000000], m = 2
输出:999999999
解释:我们使用位于 1 和 1000000000 的篮子时最小磁力最大。

提示

  • n == position.length
  • 2 <= n <= 10^5
  • 1 <= position[i] <= 10^9
  • 所有 position 中的整数 互不相同 。
  • 2 <= m <= position.length

c.分析

这里我是直接想到整体二分的 所以不能提供如何思考出来的思路
因为题目的答案存在以下的单调性质

如果某个最小磁力是可行的话 那么更小的磁力是一定可行的 更大的磁力是可能存在的
(最小磁力显然必然只存在于相邻的篮子 证明略)

怎么去理解这句话呢 比如1~10上都有篮子 m=3
那么我假设最大最小磁力为1 那么显然我随便放【1,2,3】、【1,5,9】、【6,7,8】 都是可行的 当然你会发现1、5、9的最小磁力为4 但是它符合假设 因此我们必然可以压缩距离来达到更小的最小磁力
那么我再假设最大最小磁力为5 那么我最极限贪心就是放 【1,6,11】
但是显然11比10大 已经没有那个篮子了 所以我们就可以断定 不可能存在假设的最大最小磁力大于等于5的情况
假设5不成立 那么可以试下4 发现【1,5,9】可以 那么这个答案就肯定是4 因为5及以上的都不成立
因此这里是存在单调性的 你的假设小于等于答案 就肯定存在那个假设的最小磁力是可行的 反之大于了答案 那么就肯定是不可行的

这里可以把最大边界那个值给二分出来

那么下个问题就是如何判定某个假设的最小磁力是否可行 这里采取贪心的做法
通俗点来说就是尽量在比最小磁力要大的情况下尽量紧凑 因为这样可以腾出后面更多的空间 比如上面的假设为5的情况 那肯定右区间最小的情况就是【1,6,11】不存在更紧凑的
当然这道题的篮子并不是连续的 所以会出现实际最小磁力会比假设的要大 那么这个不要紧 这个就恰恰证明了还能有更大的最小磁力

贪心的证明可以用反证法 略 大概就是你不这样选的话 会导致可用长度变短 会导致不可行的状况

总的时间复杂度是O(nlogn)的 因为贪心判断是否可行是需要遍历一次篮子的位置的

c.参考代码

class Solution {
public:
    int maxDistance(vector<int>& pos, int m) {
        sort(pos.begin(),pos.end());
        int l=1,r=pos.back()-pos[0];
        while(l<r){	//二分假设的答案
            int mid=(l+r+1)>>1;
            if(check(mid,pos,m))l=mid;
            else r=mid-1;
        }
        return l;
    }
    inline bool check(int x,vector<int> &pos,int m){	//贪心去判断是否可行
        int idx=0;	//贪心去肯定先选取第一位
        m--;	//已经选了一个了
        for(int i=1;i<pos.size();i++)
            if(pos[i]-pos[idx]>=x){		//可以再选这个
                m--;
                idx=i;
            }
        return m<=0;	//选够了
    }
};

吃掉 N 个橘子的最少天数

d.题目

厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子:
吃掉一个橘子。

  • 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子。
  • 如果剩余橘子数 n 能被 3 整除,那么你可以吃掉 2*(n/3) 个橘子。
  • 每天你只能从以上 3 种方案中选择一种方案。

请你返回吃掉所有 n 个橘子的最少天数。

示例 1

输入:n = 10
输出:4
解释:你总共有 10 个橘子。
第 1 天:吃 1 个橘子,剩余橘子数 10 - 1 = 9。
第 2 天:吃 6 个橘子,剩余橘子数 9 - 2*(9/3) = 9 - 6 = 3。(9 可以被 3 整除)
第 3 天:吃 2 个橘子,剩余橘子数 3 - 2*(3/3) = 3 - 2 = 1。
第 4 天:吃掉最后 1 个橘子,剩余橘子数 1 - 1 = 0。
你需要至少 4 天吃掉 10 个橘子。

示例 2

输入:n = 6
输出:3
解释:你总共有 6 个橘子。
第 1 天:吃 3 个橘子,剩余橘子数 6 - 6/2 = 6 - 3 = 3。(6 可以被 2 整除)
第 2 天:吃 2 个橘子,剩余橘子数 3 - 2*(3/3) = 3 - 2 = 1。(3 可以被 3 整除)
第 3 天:吃掉剩余 1 个橘子,剩余橘子数 1 - 1 = 0。
你至少需要 3 天吃掉 6 个橘子。

示例 3

输入:n = 1
输出:1

示例 4

输入:n = 56
输出:6

提示

  • 1 <= n <= 2*10^9

d.分析

看到数据范围的我挺慌的 因为知道肯定不能遍历所有状态
而且状态里的-1这个项我知道肯定不需要枚举太多 因此开始我是想有没有某种贪心能行的 但是想了十分钟 都证明不了一个对的

后来还是从-1这个项太冗余入手 如果dfs的话就肯定要经历一次全部状态
但是BFS就不用 所以直接跑个BFS就行了 因为BFS的话每层-1其实很慢的 /2和/3很快就能在一定层数下解决掉问题 所以不用担心BFS分支出来的-1会有很多
但是有个重点是 这个BFS必须要记录访问过的 因为根据顺序的话 /2/3和/3/2这种顺序问题必将会导致大量的重复路径 所以必须记录访问过的 用unordered_set来做个vis数组

接着就是常规的BFS 复杂度无法估算 和状态量有关


当然我在这里想给大家讲另一种做法 更加快的O(logn)做法

用dfs会超时是因为要经历-1这个O(n)的过程 但是通过分析之后可以把-1这个步骤给优化掉:
假设当前数为x

  • x要不就是x%20 要不就是(x-1)%20
  • x要不就是x%30 要不就是(x-1)%30 要不就是(x-2)%3==0

所以其实可以把-1这部分在整除时候直接减去1或2 而不是每次递归去O(n)地去-1
而且你会发现 不会存在-2除以2 -3除以3 -4 -5等情况 为什么呢?
大概证明下:
假设x可以-3后除以3=y 那么这里步数就是3+1=y
但是显然x-3后能整除 必然x可以整除3 所以是x/3=y+1 而同时走到y的步数只需要1+1=y 显然无论在何种情况下都是直接除比较好
同理(x-2)/2=y 是2+1=y的 也是比1+1=y要差

(补充条件是 x必然可以通过-1来达到/2或通过-1or-2来达到/3 所以就不讨论-4 -5 -6的情况了

削去了-1的步骤后 整个递归变成了只有/2和/3的 那么这个必然是个O(logn)的复杂度 当然必须用记忆化搜索优化递归树

d.参考代码

BFS

class Solution {
public:
    int minDays(int n) {
        unordered_set<int> vis;
        queue<int> q;
        q.push(n);
        vis.insert(n);
        int day=0;
        while(q.size()){
            int t=q.size();
            day++;
            while(t--){
                int now=q.front();
                q.pop();
                if(now==1)return day;
                if(now%3==0&&!vis.count(now/3)){
                    q.push(now/3);
                    vis.insert(now/3);
                }
                if(now%2==0&&!vis.count(now/2)){
                    q.push(now/2);
                    vis.insert(now/2);
                }
                q.push(now-1);
            }
        }
        return day;
    }
};

DFS公式剪枝

class Solution {
public:
    unordered_map<int,int> mem;
    int minDays(int n) {	//必须记忆化搜索
        if(n==0)return 0;
        if(n==1)return 1;
        if(mem.count(n))return mem[n];
        return mem[n]=min(minDays(n/2)+n%2+1,minDays(n/3)+n%3+1);
    }
};

你可能感兴趣的:(leetcode,周赛,算法,数据结构,动态规划,c++,leetcode)