LeetCode做题记录---二分第二篇(二分+前缀和,贪心等)

1.LeetCode: 1292. 元素和小于等于阈值的正方形的最大边长

原题链接

  给你一个大小为 m x n 的矩阵 mat 和一个整数阈值 threshold。
请你返回元素总和小于或等于阈值的正方形区域的最大边长;如果没有这样的正方形区域,则返回 0 。
示例 1:
输入:mat = [[1,1,3,2,4,3,2],[1,1,3,2,4,3,2],[1,1,3,2,4,3,2]], threshold = 4
输出:2
解释:总和小于或等于 4 的正方形的最大边长为 2,如图所示。
示例 2:
输入:mat = [[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2]], threshold = 1
输出:0
提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 300
0 <= mat[i][j] <= 10^4
0 <= threshold <= 10^5

   1 ) 1) 1)既然是要求矩阵的范围和,那就应该是二位前缀和了,再看题目要求我们找到一个范围总和小于threshold 的最大值,也就是某个较小值的最大值,很自然的想到了二分.
   2 ) 2) 2)那么思路如下,先求出这个矩阵的二维前缀和,然后通过二分查找某个边长,通过检查其合法性不断更新 a n s ans ans,然后继续扩大范围.
   3 ) 3) 3)那么合法性应该如何判断呢?首先我们二分找到了一个边长 m i d mid mid,然后再矩阵中找到第一个前缀和小于等于 t h r e s h o l d threshold threshold的位置即可。具体做法就是,先从第 m i d mid mid行,第 m i d mid mid列开始,因为这个边长要符合条件首先矩阵中应该存在对于一个点的坐标 ( i , j ) (i,j) (i,j)来说,存在能够跟他构成一个边长为 m i d mid mid正方形的点也就是说这个点的坐标最小也该是 ( m i d − 1 , m i d − 1 ) (mid-1,mid-1) (mid1mid1).
  那么该点的坐标在不越界的情况下我们去检查从 ( i − l + 1 , j − l + 1 ) (i-l+1,j-l+1) (il+1,jl+1) ( i , j ) (i,j) (i,j)范围内的前缀和是否符合条件如果符合就返回 t r u e true true否则检查下一个点.

class Solution {
private:
    int prev[305][305];
    int get_valid(int x,int y){
        if(x==-1||y==-1){
            return 0;
        }
        return prev[x][y];
    }
    int getnum(int rr,int cr,int rl,int cl){
        return get_valid(rr,cr)-get_valid(rr,cl-1)-get_valid(rl-1,cr)+get_valid(rl-1,cl-1);
    }
    bool check(int row,int col,int l,int threshold){
        for(int i=l-1;i<row;i++){
            for(int j=l-1;j<col;j++){
                if(getnum(i,j,i-l+1,j-l+1)<=threshold){
                    return true;
                }
            }
        }
        return false;
    }
public:
    int maxSideLength(vector<vector<int>>& mat, int threshold) {
        int row=mat.size(),col=mat[0].size();
        for(int i=0;i<row;i++){
            for(int j=0;j<col;j++){
                if(i==0&&j==0){
                    prev[0][0]=mat[0][0];
                }else if(i==0){
                    prev[i][j]=prev[i][j-1]+mat[i][j];
                }else if(j==0){
                    prev[i][j]=prev[i-1][j]+mat[i][j];
                }else prev[i][j]=prev[i-1][j]+prev[i][j-1]-prev[i-1][j-1]+mat[i][j];
            }
        }
        int l=0,r=300;
        int ans=0;
        while(l<=r){
            int mid=((r-l)>>1)+l;
            if(check(row,col,mid,threshold)){
                ans=mid;
                l=mid+1;
            }else r=mid-1;
        }
        return ans;
    }    
};

2.LeetCode:1954. 收集足够苹果的最小花园周长

原题链接

  给你一个用无限二维网格表示的花园,每一个 整数坐标处都有;一棵苹果树。整数坐标 (i, j) 处的苹果树有 |i| + |j| 个苹果。
你将会买下正中心坐标是 (0, 0) 的一块 正方形土地 ,且每条边都与两条坐标轴之一平行。
  给你一个整数 neededApples ,请你返回土地的 最小周长 ,使得 至少 有 neededApples 个苹果在土地 里面或者边缘上。
|x| 的值定义为:
  如果 x >= 0 ,那么值为 x
  如果 x < 0 ,那么值为 -x
示例 1:
输入:neededApples = 1
在这里插入图片描述
输出:8
解释:边长长度为 1 的正方形不包含任何苹果。
但是边长为 2 的正方形包含 12 个苹果(如上图所示)。
周长为 2 * 4 = 8 。
示例 2:
输入:neededApples = 13
输出:16
示例 3:
输入:neededApples = 1000000000
输出:5040

  这道题同样是寻找一个较小值的最大值,也是利用二分一个答案出来然后进行判断.
  首先我们要明确一点那就是边是否有可能是奇数,答案是不可能.如果边是奇数的话那么每行每列上就会有偶数个点整个土地的点也是偶数就不存在中心点了.
  那么如何二分呢?这个先放在一边我们先看边长为2时候的苹果个数:
每行给我买带来的苹果数量是:(负数取绝对值)
− 1 + 0 + − 1 -1 + 0 + -1 1+0+1 − 1 + 0 + − 1 -1 + 0 + -1 1+0+1 − 1 + 0 + − 1 -1 + 0 + -1 1+0+1
每列带给我们的苹果个数是:(负数取绝对值)
1 + 1 + 1 1+ 1 + 1 1+1+1 0 + 0 + 0 0+ 0 + 0 0+0+0 − 1 + − 1 + − 1 -1 +-1 +-1 1+1+1
  仔细观察不难发现将行的苹果数量的图旋转一下就是列带给我们的苹果数量,也就是说如果把行和列上的苹果数量拆开来看他们其实是一样的,也就是说我们在统计数量的时候只需要统计一次再×2即可。而数量的统计就很简单了,以0为界,左右分别有 n / 2 n/2 n/2个点,符合等差数列公式也就是 n ∗ ( n + 1 ) / 2 n*(n+1)/2 n(n+1)/2,这样每行的个数就是 n ∗ ( n + 1 ) n*(n+1) n(n+1),之后再乘上行数,最后结果再×2,就是当前边长下苹果的数量。因此我们二分的依据就是边长,同时前面分析过了,边长只能为偶数,为了保证这一点我们把每次二分到的 m i d ∗ 2 mid*2 mid2,使他变为偶数。

class Solution {
public:
    bool check(long long len,long long need){
        long long num=len/2;
        return (len+1)*num*(num+1)*2>=need;
    }
    long long minimumPerimeter(long long neededApples) {
        long long l=0,r=100000;//由check的表达式可以是一个关于len的三次函数,所以有边界只需要1e5即可
        long long ans=0;
        while(l<=r){
            long long mid=(r+l)/2;
            if(check(mid*2,neededApples)){
                ans=mid*2;
                r=mid-1;
            }else l=mid+1;
        }
        return ans*4;//由于枚举的是边长所以返回结果要×4
    }
};

3.LeetCode: 1631. 最小体力消耗路径

原题链接

  你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
  一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
  请你返回从左上角走到右下角的最小 体力消耗值 。
示例 1:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。
示例 2:
输入:heights = [[1,2,3],[3,8,4],[5,3,5]]
输出:1
解释:路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。
示例 3:
输入:heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
输出:0
解释:上图所示路径不需要消耗任何体力。

同样需要我们找到一个最大值的最小值,二分答案.

class Solution {
    int dir[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
public:
    bool bfs(vector<vector<int>>& heights,int row,int col,int h){
        queue<pair<int,int>> q;
        bool vis[105][105];
        memset(vis,false,sizeof(vis));
        q.push(make_pair(0,0));
        vis[0][0]=true;
        int x,y,tx,ty;
        while(!q.empty()){
            pair<int,int> tmp=q.front();
            q.pop();
            x=tmp.first;
            y=tmp.second;
            if(x==row-1&&y==col-1){
                return true;
            }
            for(int i=0;i<4;i++){
                tx=x+dir[i][0];
                ty=y+dir[i][1];
                if(tx>=row||ty>=col||tx<0||ty<0){
                    continue;
                }
                if(abs(heights[x][y]-heights[tx][ty])>h){
                    continue;
                }
                if(!vis[tx][ty]){
                    vis[tx][ty]=true;
                    q.push(make_pair(tx,ty));
                }
            }
        }
        return false;
    }
    int minimumEffortPath(vector<vector<int>>& heights) {
        int l=0,r=1000005;
        int row=heights.size(),col=heights[0].size();
        int ans=0;
        while(l<=r){
            int mid=((r-l)>>1)+l;
            if(bfs(heights,row,col,mid)){
                ans=mid;
                r=mid-1;
            }else l=mid+1;
        }
        return ans;
    }
};

4.LeetCode: 2226. 每个小孩最多能分到多少糖果

原题链接

  给你一个 下标从 0 开始 的整数数组 candies 。数组中的每个元素表示大小为 candies[i] 的一堆糖果。你可以将每堆糖果分成任意数量的 子堆 ,但 无法 再将两堆合并到一起。
  另给你一个整数 k 。你需要将这些糖果分配给 k 个小孩,使每个小孩分到 相同 数量的糖果。每个小孩可以拿走 至多一堆 糖果,有些糖果可能会不被分配。
  返回每个小孩可以拿走的 最大糖果数目 。
示例 1:
输入:candies = [5,8,6], k = 3
输出:5
解释:可以将 candies[1] 分成大小分别为 5 和 3 的两堆,然后把 candies[2] 分成大小分别为 5 和 1 的两堆。现在就有五堆大小分别为 5、5、3、5 和 1 的糖果。可以把 3 堆大小为 5 的糖果分给 3 个小孩。可以证明无法让每个小孩得到超过 5 颗糖果。
示例 2:
输入:candies = [2,5], k = 11
输出:0
解释:总共有 11 个小孩,但只有 7 颗糖果,但如果要分配糖果的话,必须保证每个小孩至少能得到 1 颗糖果。因此,最后每个小孩都没有得到糖果,答案是 0 。
提示:
1 <= candies.length <= 10^5
1 <= candies[i] <= 10^7
1 <= k <= 10^12

  这道题先看数据量1<=K<=10^12数据量超过了 i n t int int,所以我们采用二分来解决,那么如何二分呢?我们找到一个 m i d mid mid,进行合法性检查,具体做法是遍历数组中每个元素,该堆能够分给的人数就是 c a n d i e s [ i ] / m i d candies[i]/mid candies[i]/mid,我们对其进行累加,如果当前累加到的人数>=k跳出循环并且更新 a n s ans ans,由于要求最大值之后让 l = m i d + 1 l=mid+1 l=mid+1,如果分到的人数小于 k k k,让 r = m i d − 1 r=mid-1 r=mid1

class Solution {
public:
    int maximumCandies(vector<int>& candies, long long k) {
        int l=1,r=10000000;//candies[i]属于[1,1e7]所以左边界为1,右边界为1e7
        int ans=0;
        long long  tmp=0;
        while(l<=r){
            tmp=k;
            int mid=((r-l)>>1)+l;
            for(int i=0;i<candies.size();i++){
                tmp-=candies[i]/mid;
                if(tmp<=0){
                    break;
                }
            }
            if(tmp<=0){
                ans=mid;
                l=mid+1;
            }else r=mid-1;
        }
        return ans;
    }
};

5.LeetCode:剑指 Offer II 073. 狒狒吃香蕉

6.LeetCode :875. 爱吃香蕉的珂珂

原题链接

  狒狒喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
  狒狒可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉,下一个小时才会开始吃另一堆的香蕉。
  狒狒喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
  返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
示例 1:
输入: piles = [3,6,7,11], H = 8
输出: 4
示例 2:
输入: piles = [30,11,23,4,20], H = 5
输出: 30
示例 3:
输入: piles = [30,11,23,4,20], H = 6
输出: 23
提示:
1 <= piles.length <= 10^4
piles.length <= H <= 10^9
1 <= piles[i] <= 10^9

  题目要求我们寻找可以在H小时内吃完所有香蕉的最小速度,即是找到一个合法条件的最小值,二分十分合适。二分区间可以是数组内的最小值到最大值,这里直接才使用了数组元素的值的范围。然后进行二分,进行遍历数组,对于每堆香蕉需要的时间是 p i l e s [ i ] / m i d piles[i]/mid piles[i]/mid,现在考虑是否有余数,如果没有余数吃完这堆香蕉的时间就是 p i l e s [ i ] / m i d piles[i]/mid piles[i]/mid,如果有余数进行向上取整即可,综上所述,我们目前吃完每堆的时间就是 p i l e s [ i ] / m i d piles[i]/mid piles[i]/mid向上取整,最后判断所用时间是否大于H即可。由于我们要找一个最小值所以合法的话 r = m i d − 1 r=mid-1 r=mid1,否则 l = m i d + 1 l=mid+1 l=mid+1

class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
         int l=1,r=1000000000;
        int ans=0;
        while(l<=r){
            int mid=((r-l)>>1)+l;
            int cur=0;
            for(int i=0;i<piles.size();i++){
                cur+=(piles[i]+mid-1)/mid;//向上取整
                if(cur>h){
                    break;
                }
            }
            if(cur<=h){
                ans=mid;
                r=mid-1;
            }else l=mid+1;
        }
        return ans;
    }
};

7. LeetCode:1011. 在 D 天内送达包裹的能力

  传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。
  传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
  返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。
示例 1:
输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。
示例 2:
输入:weights = [3,2,2,4,1,4], days = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4
示例 3:
输入:weights = [1,2,3,1,1], days = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1
提示:
1 <= days <= weights.length <= 5 * 10^4
1 <= weights[i] <= 500

  这道题和昨天的那篇博客中的一道题很像,即是我们无法对数组进行排序,因为题目要求我们必须按数组中元素的顺序进行运输,但是这道题并非不能二分。我们二分出一个 m i d mid mid并且对数组进行遍历,对连续的元素重量进行累加,首先如果当前货物重量大于 m i d mid mid为非法条件,其次如果当前累加的和超过了 m i d mid mid所用的天数加1,最后在合法的条件下更新 a n s ans ans,并且题目要我们求一个最小值,所以合法时让 r = m i d − 1 r=mid-1 r=mid1,否则 l = m i d + 1 l=mid+1 l=mid+1;

class Solution {
public:
    bool check(int carry,vector<int>& weights,int days){
        int day=1,cur=0;
        for(int i=0;i<weights.size();i++){
            if(weights[i]>carry){
                return false;//不能运输直接返回false
            }
            if(cur+weights[i]>carry){//超重cur清0,day++
                cur=0;
                day++;
            }
            cur+=weights[i];//累加
        }
        return day<=days;//如果当前运输的天数<days一定可以合理安排每天的运输量达到days比如示例3。
        //例如最极端的情况是days-day的天数不运输货物。
        //当然如果==days也是合法条件返回true;
    }
    int shipWithinDays(vector<int>& weights, int days) {
        int l=1,r=500*50000;//以免days==1的情况区间范围不够。
        int ans=r;
        while(l<=r){
            int mid=((r-l)>>1)+l;
            if(check(mid,weights,days)){
                ans=mid;
                r=mid-1;
            }else l=mid+1;
        }
        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值