代码随想录|贪心算法部分

目录

一:基础知识

二:题目

1.分饼干

2.摆动数列(难)

3.最大子序列的和(要多思考)

4.买卖股票的最佳时机||

5.跳跃游戏

6.跳跃游戏||

7.K次取反后最大化的数组和(简单)


一:基础知识

   局部最优->全局最优  eg. 一堆钞票中拿走十张,需要总金额达到最大。  解:每次拿最大的钱

   解题步骤:无固定模板 看是否能举反例,若不能则是贪心  (说白了就是常识性的推导

二:题目

1.分饼干

饼干充分(满足最接近胃口的) 分给小孩,且饼干不剩,小孩可以没有吃饱。(虽然有小胃口喂小饼干,可是我觉得下面这个好理解一些)

贪心:大胃口喂大饼干,充分利用饼干尺寸,喂好每一个胃口

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
    sort(g.begin(),g.end());
    sort(s.begin(),s.end());
    int i,j;  //i是饼干 j是胃口
    int coun=0;
    for(i=s.size()-1,j=g.size()-1,j>=0;j--)
    {
        if(s[i]>g[j]&&i>=0)//大胃口满足大饼干
         {
            count++;
            i--;   //i已经满足了最好的胃口,则进行下一个的挑选
        }
    }
    return count;
}
};

(ps:其实我觉得c++的排序这样好用一些,同时算长度直接用  数组名 . size()  不用再去用c里面的strlen 和string.h )

2.摆动数列(难)

首先需要明白一点,摆动数列的意思是两数值之差  正负交替出现所组成的子数列

分析很难想到,看Car老师l的解法,有三种情况:单调中有平波(2种画法),波峰是平波(2种画法),数列的首尾有平波,最后构成了代码如下:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
       if(nums.size()<=1) return nums.size();
       int curdiff=0;
       int prediff=0;
       int result=1;//计数最后一个数值是巅峰,计为1个;
       int i;
       for(i=0;i<nums.size()-1;i++)
         {
             curdiff=nums[i+1]-nums[i];
          if((prediff>=0&&curdiff<0)||(prediff<=0&&curdiff>0))
//符合上面的三种情况
//可以亲自画图,尤其是首,我们假设它是平波,尾已经在result里面+1初始化了
          {
              result++;
              prediff=curdiff;//只有当条件符合时,才能移动pre,否则就是平波(curdiff==0)
          }
         } 
          return result;
    }
};

必要时画图理解 

3.最大子序列的和(要多思考)

题目:给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:

输入:nums = [1]
输出:1
示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

刚开始的时候没理解到题意,以为和上面一道题相似,但是看了题解,才发现自己很菜,连题都没有读清楚,所以越简单的题,越要耐心看。

首先可以考虑暴力解法,一次一次慢慢循环遍历

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result=INT32_MIN;
        int count=0;
        int i,j;
        for(i=0;i<nums.size();i++)
        {
            count=0;//注意清零
            for(j=i;j<nums.size();j++)//注意j=i的意思,是从头i开始遍历
                {
                    count+=nums[j];
                    result=result<count?count:result;//不断判断最新的和
                }
        }
    return result;
    }
};

这个暴力,运行时间有点超时了,所以试试贪心。可能由于我蒟蒻(juruo)所以对于贪心算法只有看题解了。当连续和出现负数时,停止继续相加,

“遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和”。

不断调整最大子序和区间的起始位置(暴力)=找到每个数相加的count值不小于0(只要大于0就满足和相加)

终止位置是通过不断比较count和result(前一个count的值)的大小,确定最大的和

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result=INT32_MIN;//初始化一个较小的值
        int count=0;
        int i;
        for(i=0;i<nums.size();i++)
        {
            count+=nums[i];
            if(count>result)
//取区间累计的最大值(相当于不断确定最大子序终止位置)
                result=count;
//这个是不断的更新result的值,便于后面count相加时,能大于之前的和
            if(count<=0)  
//当子序列开始为负数或0时,重置起始位置,防止子序列和一直为负数
                count=0;
        }
    return result;
    }
};

贪心:就在于当和为负数时(负数只会拉低和),不在进行相加,转换至下一组数。(应该时这个意思)

误区:如果输入用例都是-1,或者 都是负数,这个贪心算法跑出来的结果是0。× 错误

解答:由于定义了INT32_MIN, 则后面一定会去更新result的值为新值。

4.买卖股票的最佳时机||

贪心:最终利润是可以分解成每两天的利润,我们就搜集每两天的正利润(感觉有点不可思议这样思考)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int i,count=0,result=0;
        for(i=1;i<prices.size();i++)//注意这是从1开始
//或者是for(i=0;i<prices.size()-1;i++)
//      count=prices[i+1]-prices[i];//利润序列比股票序列少一天
        {
            count=prices[i]-prices[i-1];
            if(count>0)//保证利润为正
            {
                result=result+count;
            }
        }
        return result;
    }
};

//或者是Carl老师写的这个更简洁
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int result = 0;
        for (int i = 1; i < prices.size(); i++) {
            result += max(prices[i] - prices[i - 1], 0);//c++中与0之间的比较取最大值
        }
        return result;
    }
};

5.跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

示例 1:

  • 输入: [2,3,1,1,4]
  • 输出: true
  • 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

示例 2:

  • 输入: [3,2,1,0,4]
  • 输出: false
  • 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

由于一个数能够跳无数种可能,不断的取寻找覆盖的最大范围,考虑每个数是否能否到达终点 

难点:如何表示它跳过去后的数值,这个地方用数组的下标来表示。

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int cover=0;
        if(nums.size()==1)return true;
        for(int i=0;i<=cover;i++)//注意<=
        {
            cover=max(i+nums[i],cover);
            if(cover>=nums.size()-1) return true;
        }
        return false;
    }
};

6.跳跃游戏||

示例:

  • 输入: [2,3,1,1,4]
  • 输出: 2
  • 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

说明: 假设你总是可以到达数组的最后一个位置。

与上一道区别:是否能到达终点   最少走几步到终点

这道题设置了curdistance和nextdistance两个新的变量记录最大范围。

贪心:找到当前位置的最大覆盖范围,一步尽可能的走完,找到最少的步数

 普遍判断到最后一个数的情况:

class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size()==1)return 0;
        int curDistance=0;//记录当前这个数的覆盖范围
        int ans=0;//记录次数
        int nextDistance=0;
        int i;
        for(i=0;i<nums.size();i++)
        {
            nextDistance=max(nums[i]+i,nextDistance);
            if(i==curDistance)//保证一步尽量走完到最大步数
//如果到了该值覆盖的最大范围,则进行如下操作,否则继续执行next值的最大范围判断,直到到达该值的最大范围。(始终保证该值的最大范围用完)
            {
                if(curDistance<nums.size()-1)//判断现在的覆盖范围是否超出数
                {
                    ans++;
                    curDistance=nextDistance;//重新赋值范围
                    if(nextDistance>=nums.size()-1)  break;
                }
             }
         }
        return ans;
    }
};

优化代码:停留在倒数第二位上判断,由于题目中有这样一段话:假设你总是可以到达数组的最后一个位置。(这说明倒数第二个数总能到达,也就是数组中倒数第二个数没有0)

//这个方法多少有点不理解,不知道为什么
class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size()==1)return 0;
        int curDistance=0;//记录当前这个数的覆盖范围
        int ans=0;//记录次数
        int nextDistance=0;
        int i;
        for(i=0;i<nums.size()-1;i++)//区别
        {
            nextDistance=max(nums[i]+i,nextDistance);
            if(i==curDistance)
//如果到了该值覆盖的最大范围,则进行如下操作,否则继续执行next值的最大范围判断,直到到达该值的最大范围。(始终保证该值的最大范围用完)
           {
                    ans++;
                    curDistance=nextDistance;//重新赋值范围
           }    
         }
        return ans;
    }
};

这两道类似的题可以动笔罗列情况更好理解 

7.K次取反后最大化的数组和(简单)

贪心:负数变正数  最小数变负数

class Solution {
static bool cmp(int a, int b) {
    return abs(a) > abs(b);//c++的函数还不是很会哦
}
public:
    int largestSumAfterKNegations(vector<int>& A, int K) {
        sort(A.begin(), A.end(), cmp);       // 将所有数的绝对值进行从大到小排序
        for(int i=0;i<A.size();i++)
        {
            if(A[i]<0&&K>0)
            {
                A[i]=A[i]*(-1);//转化为负数的方法
                K--;
            }
        }
        if(K%2==1)A[A.size()-1]*=-1;//若负数转化玩还有k,则进行最小数转化为负数
//问题:为什么要这样用K,k一定只用一次吗。
//答:题中所述可以重复利用一个值,所以k为偶数时,变来变去等于原值(不改变原值的情况下把k的次数用完),所以只计算k为奇数
        int result=0;
        for(int a:A) result+=a;
        return result;
    }
};

猜你喜欢

转载自blog.csdn.net/Brittney27/article/details/128792421