贪心总结

待解决

一.贪心问题的定义

  1. 定义:贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
  2. 贪心的本质:选择每一阶段的局部最优,从而达到全局最优
  3. 何时使用:刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心

二.贪心解题的一般步骤

  1. .建立数学模型来描述问题;
  2. 把求解的问题分成若干个子问题
  3. 找出适合的贪心策略
  4. .对每一子问题求解,得到子问题的局部最优解;
  5. 把子问题的局部最优解合成原来问题的一个解。

三.贪心之应用

 例题1----455. 分发饼干:对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值

  • 按照最小最接近原则配对,即小饼干给小胃口
  • 按照最大最接近原则配对,即大饼干给大胃口
    int findContentChildren(vector<int>& g, vector<int>& s) {
        int cnt=0;
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        for(int i=0,j=0;i<g.size()&&j<s.size();j++){
            if(s[j]>=g[i]){
                i++;
                cnt++;
            }
        }
 
        return cnt;
    }

例题2--376. 摆动序列,给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

  • 相当于在折线图中,当有一个连续递增/递减的序列,只需要保存首尾元素即可,仅仅记录峰值。
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size()<2)
            return nums.size();
        int preDif=0,curDif=0;
        int len=1;
        for(int i=1;i<nums.size();i++){
            curDif=nums[i]-nums[i-1];
            if((preDif<=0&&curDif>0)||(preDif>=0&&curDif<0)){
                len++;
                preDif=curDif;
            }
 
        }
        return len;
        
    }

例题3-- 53. 最大子序和,给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

  •  在计算连续子序列x时,设该序列有两部分构成x1和x2,且x=x1+x2,若开始的子序列元素x1和为负值,即x1<0,那么x=x1+x2<x2;所以遇到开始的子序列和为负值时直接舍弃。
    int maxSubArray(vector<int>& nums) {
        int ans=nums[0],sum=0; //ans=nums[0],防止全是负数的情况
        for(int i=0;i<nums.size();i++){
            sum+=nums[i];
            ans=max(sum,ans);
            if(sum<0)
                sum=0;
        }
        return ans;
 
    }

例题4--122. 买卖股票的最佳时机 II, 多次买卖一支股票,求最大收益 

  •  凡是遇到上升的股票就卖出,等价于昨天买入,今天卖出;
    int maxProfit(vector<int>& prices) {
        if(prices.size()<2)
            return 0;
        int maxprofit=0;
        for(int i=1;i<prices.size();i++){
            if(prices[i]>prices[i-1])
                maxprofit+=(prices[i]-prices[i-1]);
        }
        return maxprofit;
    }

例题5--714. 买卖股票的最佳时机含手续费,给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

基本思路:贪心算法,核心思想仍然是山脚买入,山顶卖出,即到当前阶段的最小股价时买入,到当前阶段的最大股票时卖出,完成一次交易时,重新定义最小股价,但要注意两点:

  • 当该次交易不是该笔买入minPrice对应的最大收益时,需要将minPrices=prices[i]-fee,防止二次重复收费,表示并没有真正的完成该次交易
  • 当该次交易是该笔交易买入minPrice对应的最大收益时,也同样需要将minPrices=prices[i]-fee,因为在[prices[i]-fee,prices[i]),还不够手续费,没必要交易。 
 
    int maxProfit(vector<int>& prices, int fee) {
        int len=prices.size();
        if(len<1)
            return 0;
        int minPrice=prices[0];
        int ans=0;
        for(int i=1;i<len;i++){
            if(prices[i]<minPrice){
                minPrice=prices[i];
            }
            if(prices[i]-fee>minPrice){
                ans+=prices[i]-minPrice-fee;
                minPrice=prices[i]-fee;
            }
        }
        return ans;
    }

 例题6-- 55. 跳跃游戏,给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度,判断你是否能够到达最后一个位置。

  • 每走一步,都观察是否提高覆盖范围,r=max(r,i+nums[i]),r表示的是目前能走到的最远的距离
  • 如果i>r,表示不能走到第i步
    bool canJump(vector<int>& nums) {
        int r=0;
        for(int i=0;i<nums.size();i++){
            if(i>r)
                return false;
            r=max(r,i+nums[i]);
        }
        return true;
    }

  例题7-- 45. 跳跃游戏 II,给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。

  •  在当前覆盖范围内,寻找可以跳到最远距离的下一步的局部最优解,也就是下一步的覆盖范围
  • 当前覆盖范围走到尽头后,更新为下一步的覆盖范围
    int jump(vector<int>& nums) {
        if(nums.size()<2)  //可省,不影响结果,但是在for循环中,必须要i<nums.size()-1;
            return 0;
        int cnt=0;
        int r=0;  //当前覆盖范围
        int nextR=0; //下一步的覆盖范围
        for(int i=0;i<nums.size()-1&&r>=nums.size()-1;i++){//如果i<nums.size()-1,即最后一步不用走
            nextR=max(nextR,i+nums[i]);
            if(i==r){  //走到当前覆盖范围尽头
                r=nextR;
                cnt++;
                //cout<<i<<"  "<<cnt<<"  "<<r<<endl;
            }
        }
        return cnt;
    }

例题8-- 1005. K 次取反后最大化的数组和

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)以这种方式修改数组后,返回数组可能的最大和。

  • 若K>=负数的个数,负数全部去取反后,剩下就只需要对绝对值最小的元素不断取反
  • 若K<负数的个数,对负数序列,按绝对值从大到小排序,先对绝对值较大的数取反
    int largestSumAfterKNegations(vector<int>& A, int K) {
        vector<int> Neg;  //存储负数部分,实际上直接取反,存的是正数
        int ans=0;
        int minEle=abs(A[0]);  //记录绝对值最小的数
        for(int i=0;i<A.size();i++){
            if(A[i]>=0){
                ans+=A[i];
            }
            else{
                Neg.emplace_back(-A[i]);
                ans-=A[i];   //相当于ans存储的是所有元素绝对值得和,所以之后需要减去时,要*2
            }
            minEle=min(minEle,abs(A[i]));
        }

        sort(Neg.begin(),Neg.end());

        if(K>=Neg.size()){
            int left=K-Neg.size();//根据奇偶判断是否要减去
            //cout<<minEle<<"  "<<(left&1)*minEle<<endl;
            ans-=2*((left&1)*minEle);
        }
        else{
            int left=Neg.size()-K;
            for(int i=0;i<left;i++){   
                ans-=2*Neg[i];
            }
        }
        return ans;
 
    }

例题9-- 738. 单调递增的数字,给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。

基本思路:由于各个位置上单调递增,对于N=a1a2a3a4a5a6a7a8a9,必然满足0<=a1<=...<=a9<=9),t[i]表示有i个连续1组成的数

那么必然可以写成这样的形式:

                  N=a1*t[9]+(a2-a1)*t[8]+...(a9-a8)*t[1];

  •  由于N最大为1e9,所以可以从t[9]开始,又由于数字不超过9,所以最多累加9次
  • 此类问题,先放最大,再放小一点,以此类推,放苹果也用到了类似的思想,

    int monotoneIncreasingDigits(int N) {       
        if(N<10)
            return N;
        int ones=111111111;
        int result=0;
        for(int i=0;i<9;i++){
            while(result+ones>N){
                ones/=10;
            }
            result+=ones;
        }
        return result;

 
    }
 

例题10-- 134. 加油站,在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

基本思路:计算left[i,j],如果小于零,那么0,j内所有的站点都不能作为起点,抛弃重新计数,left[j+1,..],知道满足要求的left[start,start+1>=0,..left[start,n-1]>=0                                                                                     

  • 如果left[i,j]<0,那么从i到j中的任意一点都不能环游一圈,即肯定不是起点
  • 如果left[0,n-1]>=0,那么一定可以环游一圈,即存在left[j,j+1],left[j,j+2],...,left[j,j-1]均大于等于零。

 

    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n=gas.size();
        int left=0;
        int start=0;
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=gas[i]-cost[i];
            left+=gas[i]-cost[i];  //left[i,j];
            if(left<0){   //left[0,i]必然不能作为起点
                left=0;
                start=i+1;
            }
        }
        return sum>=0?start:-1;
    }

121. 买卖股票的最佳时机 122. 买卖股票的最佳时机 II  406. 根据身高重建队列     300. 最长上升子序列

 1024. 视频拼接      763. 划分字母区间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值