贪心算法练习

一、P455分发饼干

一眼双指针,略

二、P376摆动序列

被难住了,看题解有2个解法,分别是动态规划和贪心;

动态规划:

有2个重要的概念:

某个序列被称为「上升摆动序列」,当且仅当该序列是摆动序列,且最后一个元素呈上升趋势。如序列 
[1,3,2,4],[1,3,2,4] 即为「上升摆动序列」

某个序列被称为「下降摆动序列」,当且仅当该序列是摆动序列,且最后一个元素呈下降趋势。如序列 
[4,2,3,1][4,2,3,1] 即为「下降摆动序列」

人话就是一个整体趋势向上,一个整体趋势向下.

那这有什么用昵,看看题目的示例2的图像是什么样的。

nums = [1,17,5,10,13,15,10,5,16,8]

 看的出来在【3,6】这一段是连续上升的,所以【3,6】这段保留有一个就行,这里保留5,结果序列为

nums = [1,17,5,15,10,5,16,8]

 明显这个序列就是下降摆动序列。

可以看出其有以下规律

若dp[i]是下降摆动序列,那么dp[i-1]是上升摆动序列,dp[i-2]是上升。。。

得出结论,长度为n的数组若是摆动序列,则其前n-1的长度的数组为与之相反的序列。

再看原图:

是否可以弄出来一个指针,不断比较指针现在的值和指针前一个的值,进而判断其是上升还是下降序列:

会有2种情况:

1:nums[i]>nums[i-1]

这种情况可以参考【1,2】和【3,6】和【7,8】

我们发现单独这一个条件就看不拿出来这个是否是上升摆动序列,因为可能是连增的情况,如【3,6】,可以发现如果这个之前的是下降摆动序列,这个就是上升摆动序列,否则是单增序列。设置up数组表示上升摆动序列,down表示下降摆动序列,有

up[i-1]  -----四不像

down[i-1]  ---上升摆动序列

2;nums[i]<nums[i-1]

同理,可得

up[i-1] ----下降摆动序列

down[i-1] ---四不像

3:nums[i]==nums[i-1]

四不像

得到状态转移方程:



  代码如下:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int len=nums.size();
        if(len==1){
            return 1;
        }
        vector<int> up;
        vector<int> down;
        up.resize(len);
        down.resize(len);
        //遍历
        up[0]=1;
        down[0]=1;
        for(int i=1;i<len;i++){
            if(nums[i]>nums[i-1]){//现在比之前大
                up[i]=max(down[i-1]+1,up[i-1]);
                down[i]=down[i-1];
            }
            else if(nums[i]<nums[i-1]){//现在比之前小
                down[i]=max(down[i-1],up[i-1]+1);
                up[i]=up[i-1];
            }
            else{
                up[i]=up[i-1];
                down[i]=down[i-1];
            }
        }
        return max(up[len-1],down[len-1]);
    }
};

进阶dp:

只维护一个变量

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if (n < 2) {
            return n;
        }
        int up = 1, down = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] > nums[i - 1]) {
                up = max(up, down + 1);
            } else if (nums[i] < nums[i - 1]) {
                down = max(up + 1, down);
            }
        }
        return max(up, down);
    }
};

贪心:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if (n < 2) {
            return n;
        }
        int prevdiff = nums[1] - nums[0];
        int ret = prevdiff != 0 ? 2 : 1;
        for (int i = 2; i < n; i++) {
            int diff = nums[i] - nums[i - 1];
            if ((diff > 0 && prevdiff <= 0) || (diff < 0 && prevdiff >= 0)) {
                ret++;
                prevdiff = diff;
            }
        }
        return ret;
    }
};

三、P53最大子数和

第一眼前缀和,然后遍历求和,代码如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int len=nums.size();
        if(len==1){
            return nums[0];
        }
        vector<int> res;
        res.resize(len);
        for(int i=0;i<len;i++){
            if(i==0){
                res[i]=nums[i];
            }
            else{
                res[i]=res[i-1]+nums[i];
            }
            cout<<res[i]<<endl;
        }
        int nowless=0;
        int nowsum=res[0];
        for(int i=0;i<len;i++){
            if(i==0){
                if(res[i]<0){
                    nowless=res[i];
                }
                continue;
            }
            int temp=res[i]-nowless;
            if(temp>nowsum){
                nowsum=temp;
            }
            if(res[i]<nowless){
                nowless=res[i];
            }
        }
        return nowsum;
    }
};

一看排名太靠后了,原来还是动态规划。,。。。

想了想,自己看不出来,总结了一下,主要的原因是选取的问题

注意dp的无后效性

举例:

 有后效性

 简单来说就是不考虑后面的了,考虑以其结尾的。

设有dp数组,dp[i]表示截至i长时以i结尾的最大值

考虑一下:

dp[i]=max(dp[i-1]+nums[i],nums[i]);

代码如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int len=nums.size();
        if(len==1){
            return nums[0];
        }
        vector<int> dp;
        dp.resize(len);
        //初值
        dp[0]=nums[0];
        for(int i=1;i<len;i++){
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
        }
        int sum=INT_MIN;
        for(int i=0;i<len;i++){
            if(dp[i]>sum){
                sum=dp[i];
            }
        }
        return sum;
    }
};

四、P122买股票的最佳时机2

一眼dp

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len=prices.size();
        //任何时候最多持有1股
        //2种情况-第x天持有或者没有持有股票
        //持有是否卖,没有是否买
        //dp[i][j]  == 利润
        //i表示第几天
        //j表示是否持有股票
        //7,1,5,3,6,4
        //dp[1][0]=0;
        //dp[1][1]=-7;
        //dp[2][0]=0;
        //dp[2][1]=-1;
        //dp[3][0]=4
        //dp[3][1]=0;
        //dp[4][0]=4; //卖出股票了
        //dp[4][1]=-3;
        //dp[i][0]=p[i]-dp[i-1][1];
        //dp[i][1]=if(dp[i][0]==dp[i-1][0]) -->max(dp[i-1][1],p[i]);
        //    else dp[i][1]=0;
        if(len==1){
            return 0;
        }
        int dp[len][2];
        dp[0][0]=0;
        dp[0][1]=prices[0];
        for(int i=1;i<len;i++){
            dp[i][0]=dp[i-1][0]+max(0,prices[i]-dp[i-1][1]);
            if(dp[i][0]==dp[i-1][0]){
                dp[i][1]=min(dp[i-1][1],prices[i]);
            }
            else{
                dp[i][1]=prices[i];
            }
            //cout<<i<<"    "<<dp[i][0]<<"   "<<dp[i][1]<<endl;
        }
        return dp[len-1][0];
    }
};

五、P55跳跃游戏

一眼dp

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int len=nums.size();
        vector<int> res;
        res.resize(len);
        res[0]=1;
        //赋值
        for(int i=0;i<len;i++){
            //更新
            if(res[i]){
                for(int j=0;j<=nums[i];j++){
                    if(i+j>=len){
                        break;
                    }
                    res[i+j]=1;
                }
            }
            else{
                return false;
            }
        }
        return true;
    }
};

发现超时了。。。这个时间复杂的一个是o(n),可能是测试数据的问题,要更小一点,不能遍历所以的节点了,换一个思路。

前面的不断更新,实质上是不断更新到最远的距离,更新最远距离就行。

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int len=nums.size();
        vector<int> res;
        res.resize(len);
        int maxlen=0;//从0开始
        for(int i=0;i<len;i++){
            if(maxlen<i){
                return false;
            }
            maxlen=max(maxlen,nums[i]+i);
            if(maxlen>=len-1){
                return true;
            }
        }
        return false;
    }
};

六、P45跳跃游戏2

上一题一开始的思路

class Solution {
public:
    int jump(vector<int>& nums) {
        //多了一个次数
        //时间复杂度降低了
        //dp[i]=min(dp[i],dp[i-j]+1)
        int len=nums.size();
        vector<int> dp(len,INT_MAX);
        //dp.resize(len);
        dp[0]=0;
        for(int i=0;i<len;i++){
            int temp=nums[i];
            for(int j=0;j<=temp;j++){
                if(i+j>=len){
                    break;
                }
                dp[i+j]=min(dp[i+j],dp[i]+1);
            }
        }
        return dp[len-1];
    }
};

七、P1005K 次取反后最大化的数组和

简单的模拟

class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        sort(nums.begin(),nums.end());
        int len=nums.size();
        //检测负数的个数
        int sumneg=0;

        for(int i=0;i<len;i++){
            if(nums[i]>=0){
                break;
            }
            sumneg++;
        }
        int sum=0;
        if(sumneg>=k){
            //int ct=0;
            for(int i=0;i<len;i++){
                if(i<k){
                    sum=sum-nums[i];
                }
                else{
                    sum=sum+nums[i];
                }
                //ct++;
            }
        }
        else {
            int temp=k-sumneg;
            for(int i=0;i<len;i++){
                if(i>=sumneg){
                    break;
                }
                nums[i]=-nums[i];
            }
            sort(nums.begin(),nums.end());
            if(temp%2==1){
                nums[0]=-nums[0];
            }
            for(int i=0;i<len;i++){
                sum=sum+nums[i];
            }

        }
        return sum;
        //3种情况
        //1:sumneg > k  优先翻转大的
        
        //2: sumneg == k 翻转
        //3: sumneg < k  额外翻转的次数是 temp=k-sumneg  temp==1  --翻转最小的
        //temp==2 不翻转

    }
};

八、P134加油站

找了半天,想dp,最后发现暴力就行,这里贴上官方代码

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size();
        int i = 0;
        while (i < n) {
            int sumOfGas = 0, sumOfCost = 0;
            int cnt = 0;
            while (cnt < n) {
                int j = (i + cnt) % n;
                sumOfGas += gas[j];
                sumOfCost += cost[j];
                if (sumOfCost > sumOfGas) {
                    break;
                }
                cnt++;
            }
            if (cnt == n) {
                return i;
            } else {
                i = i + cnt + 1;
            }
        }
        return -1;
    }
};

做一个笔记,这里官方的代码很巧妙的避免了圈的问题,通过余数,值得学习

九、135. 分发糖果

看题意,感觉从左向右边遍历一下和从右边向左遍历一下,就行,但发现时间很长,代码如下

class Solution {
public:
    int candy(vector<int>& ratings) {
        int len=ratings.size();
        if(len==1){
            return 1;
        }
        //if  r[i]> r[i-1]  s[i]>s[i-1]
        //if  r[i]> r[i+1]   s[i]>s[i+1]
        // if r[i]=r[i+1]
        //2次遍历
        //
        vector<int> dp(len,1);
        for(int i=1;i<len;i++){
            if(ratings[i]>ratings[i-1]){
                dp[i]=dp[i-1]+1;
            }
        }
        for(int i=len-2;i>=0;i--){
            if(ratings[i]>ratings[i+1]&&dp[i]<=dp[i+1]){
                dp[i]=dp[i+1]+1;
            }
        }
        //右边到左边更新
        int sum=0;
        for(int i=0;i<len;i++){
            sum+=dp[i];
            cout<<dp[i]<<"  ";
        }
        return sum;
    }
};

看了看题解,发现主要是贪心,思路是看图发现它是递增递减

 重点是将其所在递减序列所有的加1,那么难度主要就是如何找到递减序列

下面是官方题解:

class Solution {
public:
    int candy(vector<int>& ratings) {
        int n = ratings.size();
        int ret = 1;
        int inc = 1, dec = 0, pre = 1;
        for (int i = 1; i < n; i++) {
            if (ratings[i] >= ratings[i - 1]) {
                dec = 0;
                pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1;
                ret += pre;
                inc = pre;
            } else {
                dec++;
                if (dec == inc) {
                    dec++;
                }
                ret += dec;
                pre = 1;
            }
        }
        return ret;
    }
};

看看就行。

十、P860柠檬水找零

简单的模拟

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int len=bills.size();
        int st5=0;
        int st10=0;//优先给10r的
        for(int i=0;i<len;i++){
            if(bills[i]==5){
                st5++;
            }
            else if(bills[i]==10){
                st5--;
                st10++;
            }
            else{
                if(st10>0){
                    st5--;
                    st10--;
                }
                else{
                    st5=st5-3;
                }
            }
            if(st5<0||st10<0){
                return false;
            }
        }
        return true;
    }
};

十一、406. 根据身高重建队列

一开始没有思路,看了题解,总结来说就是高的看不到矮的,不受矮的影响,矮的会看高的,要受其影响,因此先按照高度排序,先给高的排好位置,再逐步排矮的,代码如下:

class Solution {
public:
    static bool cmp(vector<int> p1,vector<int> p2){
        if(p1[0]>p2[0]){
            return true;
        }
        else if((p1[0]==p2[0])&&(p1[1]<p2[1])){
            return true;
        }
        return false;
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        int len=people.size();
        vector<int>  path;
        vector<vector<int>> res;
        //res.resize(len,vector<int>(2));
        for(int i=0;i<len;i++){
            if(i==0||people[i][1]>=res.size()){
                res.push_back(people[i]);
            }
            else{
                res.insert(people[i][1]+res.begin(), people[i]);
            }
        }
        return res;

    }
};

十二、452. 用最少数量的箭引爆气球

找规律

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.empty()) {
            return 0;
        }
        sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>& v) {
            return u[1] < v[1];
        });
        int pos = points[0][1];
        int ans = 1;
        for (const vector<int>& balloon: points) {
            if (balloon[0] > pos) {
                pos = balloon[1];
                ++ans;
            }
        }
        return ans;
    }
};

十三、435. 无重叠区间

class Solution {
public:
    static bool cmp(vector<int> p1,vector<int> p2){
        return p1[0]>p2[0];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end());
        int len=intervals.size();
        if(len==1){
            return 0;
        }
         int l=intervals[0][1];
         int ans=0;
         for(int i=1;i<len;i++){
             if(intervals[i][0]<l){//重叠
                ans++;
             }
             if(intervals[i][0]>=l){//不重叠
                 l=intervals[i][1];
             }
             l=min(l,intervals[i][1]);
         }
        return ans;
    }
};

十四、763. 划分字母区间

难点是思路,把字母最后出现的位置看成区间

class Solution {
public:
    vector<int> partitionLabels(string s) {
        //转换为区间
        //记录每一个字母最后出现的位置
        //则开始-结束的位置的所有元素的末尾位置都再结束位置之前
        int a[26];
        memset(a,-1,sizeof(a));
        int len=s.size();
        for(int i=0;i<len;i++){
            a[(int)(s[i]-'a')]=i;
        }
        //遍历
        vector<int> res;

        for(int i=0;i<len;i++){
            int end=a[(int)(s[i]-'a')];
            int flag=1;
            for(int j=i;j<=end;j++){
                if(a[(int)(s[j]-'a')]>end){
                    end=a[(int)(s[j]-'a')];
                }
            }
                //cout<<i<<"   "<<end<<endl;
                res.push_back(end-i+1);
                i=end;
            
        }
        return res;
    }
};

十五、剑指 Offer II 074. 合并区间

思路同上

class Solution {
public:
    static bool cmp(vector<int> s1,vector<int> s2){
        return s1[0]>s2[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(),intervals.end());
        int maxr=intervals[0][1];
        int maxl=intervals[0][0];
        vector<vector<int> > res;
        int len=intervals.size();
        for(int i=1;i<len;i++){
            if(intervals[i][0]>maxr){
                //更新
                res.push_back({maxl,maxr});
                maxr=intervals[i][1];
                maxl=intervals[i][0];
                continue;
            }
            if(intervals[i][1]>maxr){
                maxr=intervals[i][1];
            }
        }
        res.push_back({maxl,maxr});
        return res;
    }
};

十六、738. 单调递增的数字

看不懂,看题解理解一下:

先找一个递增序列的结尾,然后将前一位减1,进位,同时递推的满足前面都是递增序列,最后将尾数全设为9

public:
    int monotoneIncreasingDigits(int n) {
        string strN = to_string(n);
        int i = 1;
        while (i < strN.length() && strN[i - 1] <= strN[i]) {
            i += 1;//递增的
        }
        if (i < strN.length()) {
            while (i > 0 && strN[i - 1] > strN[i]) {
                strN[i - 1] -= 1;
                i -= 1;
            }
            for (i += 1; i < strN.length(); ++i) {
                strN[i] = '9';
            }
        }
        return stoi(strN);
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值