[学习笔记-C++篇]day20 贪心

1.算法入门_贪心

1.1 连续子数组的最大和

不用贪心的话,判断有问题。如果先设max值为0,那么可能无法输出负数值,如果++j判断,那么最后一个元素没办法进入判断。

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        
        vector<int> src(array);
        int max=src[0],maxlen;
        
        for(int i=0;i<src.size();i++)
        {
            int sum=src[i],len=1;
            //max=sum;
            int j=i;
            while(++j<src.size())
            {
                sum+=src[j];
                len++;
                if(sum>max)
                {
                    max=sum;
                    maxlen=len;
                }
            }
        }
        return max;
    
    }
};

采用数组保存每个索引开始后子数组最大值,但是会超时。

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        
        vector<int> src(array);
        
        //vector<int> src{-1,-2,-3,-4,5};
    vector<int> inmax; //不要给初值0
    
    for(int i=0;i<src.size();i++)
    {
        int sum=src[i];
        int max=sum;
        int j=i+1;
        
        while(j<src.size())
        {
            sum+=src[j];
            if(sum>max) max=sum;
            j++;
        }
        
        inmax.push_back(max);
    }
    
    sort(inmax.begin(),inmax.end());
    //cout << inmax[inmax.size()-1];
    return inmax[inmax.size()-1];
    }
};

仍旧是思路转变:不要总是从前往后找,从前面找不能确定什么时候开始什么时候结束,但是从后面找一定能确定结束位置。

解法:每一位找最大的子数组之和,关键在于正数相加,有负数加上也没关系,只要max不变,那么比的还是和的最大值。在统计的时候只要判断正负就可以用负数分隔出子数组来。

参考:题解 | #连续子数组的最大和#

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        if(array.size()==1) return array[0];
        int max=array[0];
        
        for(int i=1;i<array.size();i++)
        {
            if(array[i-1]>0) array[i]+=array[i-1];//子数组求的是正数结果和
            if(max<array[i]) max=array[i];
        }
        return max;
    }
};

参考:
动态规划思路:求解子问题,确定当前问题和前一个子问题的关系。(两者依据情况可交换顺序)

1.2 买卖股票的最好时机(一)

这道题的思路和上面是一脉相承的。
主要思路是判断盈亏,那么先麻烦一下,求相隔天之间的盈亏值,然后用上一题的求最大子数组和思路,求最大盈亏值。

class Solution {
public:
    /**
     * 
     * @param prices int整型vector 
     * @return int整型
     */
    int maxProfit(vector<int>& prices) {
        // write code here
        if(prices.size()==1) return 0;
        
        vector<int> diff(prices.size()-1);//肯定至少有2天,不然没法买卖
        
        for(int i=1;i<prices.size();i++)
        {
            diff.push_back(prices[i]-prices[i-1]);//获取间隔天的盈亏值,然后就可以用判盈亏累加的方式求最大值
        }
        
        int win=diff[0];
        for(int j=1;j<diff.size();j++)
        {
            if(diff[j-1]>0) diff[j]+=diff[j-1];//如果前一天盈利就加上
            if(win<diff[j]) win=diff[j];
        }
        
        if(win<0) return 0;
        else return win;
    }
};

大佬的简约思路:确定当前值和之前最小值之差。
那么需要确定2个值:当前最小值,当前盈利最大值。

Code

class Solution {
public:
    /**
     * 
     * @param prices int整型vector 
     * @return int整型
     */
    int maxProfit(vector<int>& prices) {
        // write code here
        int ans=0,mi=prices[0];
        
        for(int i=1;i<prices.size();i++)
        {
            ans=max(ans,prices[i]-mi);
            mi=min(mi,prices[i]);
        }
        return ans;
    }
};

1.3 买卖股票的最好时机(二)

仍旧是上一题的套路,先求相隔天的盈亏值,发现只要有盈利加起来就行。

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 计算最大收益
     * @param prices int整型vector 股票每一天的价格
     * @return int整型
     */
    int maxProfit(vector<int>& prices) {
        // write code here
        vector<int> diff(prices.size()-1);
        
        for(int i=1;i<prices.size();i++)
        {
            diff.push_back(prices[i]-prices[i-1]);//先求相隔天的盈亏值
        }
        
        int sum=0;
        for(int j=0;j<diff.size();j++)
        {
            if(diff[j]>0) sum+=diff[j];
        }
        
        return sum;
    }
};

1.4 通配符匹配

简单思路就是逐位判断,但是这种方法是有弊端的,只能判断非*开头的情况,大概通过31/45左右。

class Solution {
public:
    bool isMatch(const char *s, const char *p) {
        
        if(*s=='\0' && !(*p=='*' || *p=='\0')) return false;
            
        stack<char> tmp;
        
        while(*s!='\0')
        {
            tmp.push(*s++);
            if(*p==tmp.top() || *p=='?') { tmp.pop(); p++; }
        }
        
        if(*p=='*' || tmp.empty()) return true;
        else return false;
        
    }
};

果然还是要用动态规划。需要理清楚所有情况。

参考通配符匹配

用dp[i][j]表示s中前i个字符和p中前j个字符的匹配关系。如果用s匹配p不好匹配,因为p中字符可能空匹配或者多匹配,所以要用p去匹配s。
有如下3大种情况:
1)p[j]为小写字母,则要保证s[i]和p[j]匹配,则s第i位和p第j位匹配,且s前i-1位要和p前j-1位匹配,状态转移方程:dp[i][j]=dp[i-1][j-1]
2)p[j]='?',同样要保证s[i]和p[j]匹配,s第i位和p第j位匹配,且s前i-1位要和p前j-1位匹配,状态转移方程:dp[i][j]=dp[i-1][j-1]
3)p[j]='*',有2类情况:
   a)'*'对应空字符,则p第j位对应s空位,要保证p前j-1位和s前i位对应,状态转移方程:dp[i][j]=dp[i][j-1]
   b)'*'对应多个字符,则p第j位对应s第i位或者包含i的多位,要保证p前j位和s前i-1位对应,状态转移方程:dp[i][j]=dp[i-1][j]

此外,考虑边界情况。
1)dp[0][0]表示s和j均为空,此时dp[0][0]=1
2)p为空s不为空,必不匹配,此时dp[i][0]=0
3)s为空p不为空,只有p为'*'或者多位'*'才匹配,此时dp[0][j]=1

java程序比较好读一点:

class Solution {
public:
    bool isMatch(const char *s, const char *p) {
        
        int dp[1005][1005];
        dp[0][0]=1;
        
        for(int i=1;i<=p.size();i++)
        {
            if(p[i-1]=='*') dp[0][i]=true;
            else break;
        }
        for(int i=1;i<=s.size();i++)
        {
            for(int j=1;j<=p.size();j++)
            {
                if(p[j-1]=='*') dp[i][j]=dp[i][j-1] | dp[i-1][j];
                else if(p[j-1]=='?' || p[j-1]==s[i-1]) dp[i][j]=dp[i-1][j-1];
            }
        }
        return dp[s.size()][p.size()];
    }
};

C++程序:
注意一下,如果用字符数组,那一定要用str开头的那些函数去操作。

class Solution {
public:
    bool isMatch(const char *s, const char *p) {
        int m=strlen(s),n=strlen(p);
        vector<vector<int>> dp(m+1,vector<int>(n+1,false));//注意要定义多长度1位,用于给空字符串
        
        dp[0][0]=true;
        
        for(int i=1;i<=n;i++)
        {
            if(p[i-1]=='*') dp[0][i]=true;
            else break;//这步很巧妙,一旦p中有非*的,那肯定不匹配,直接跳出循环
        }
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(p[j-1]=='*') dp[i][j]=dp[i][j-1] || dp[i-1][j];
                else if(p[j-1]=='?' || p[j-1]==s[i-1]) dp[i][j]=dp[i-1][j-1];
            }
        }
        return dp[m][n];
    }
};

1.5 分糖果问题

如果涉及到自己定义排序函数,那么需要注意几点,参考C++ 二维vector排序(sort用法)

一般是自定义一个cmp函数,则注意:
1)形参是指针,2)函数加static关键字,因为sort的调用必须是静态成员

解题思路:
先把所有得分从低到高排列,然后判断每个索引前后有没有糖果,没的话,直接赋1;有的话,如果得分和前(后)相同,就取前一个糖果数;不相同,就取前一个糖果+1。

测试的时候只有6/15,超出就不行了。


# error
class Solution {
public:
    /**
     * pick candy
     * @param arr int整型vector the array
     * @return int整型
     */
    static bool cmp1(const vector<int> &a,const vector<int> &b)//注意这里形参是指针,加static关键字,sort调用必须是静态成员
    {
        return a[1]<b[1];
    }
    
    int candy(vector<int>& arr) {
        // write code here
        int m=arr.size();
        vector<vector<int>> dp(m,vector<int>(2,0));//第1列索引,第2列得分
        vector<int> can(m+2,0);//糖果,比索引多2位,和索引相比大1
        
        for(int i=0;i<m;i++)
        {
            dp[i][0]=i;
            dp[i][1]=arr[i];
        }
        
        sort(dp.begin(),dp.end(),cmp1);//按照第2列得分升序排列
        int candy=0;//统计糖果总数
        can[0]=0,can[m+1]=0;
        
        for(int i=0;i<m;i++)
        {
            if(can[dp[i][0]]==0 && can[dp[i][0]+2]==0) 
            { 
                can[dp[i][0]+1]=1;
                candy+=can[dp[i][0]+1];
            }
            else if(arr[dp[i][0]]==arr[dp[i][0]-1]) 
            {
                can[dp[i][0]+1]=can[dp[i][0]];
                candy+=can[dp[i][0]+1];
            }
            else
            {
                can[dp[i][0]+1]=max(can[dp[i][0]],can[dp[i][0]+2])+1;
                candy+=can[dp[i][0]+1];
            }
        }
        
        return candy;
    }
};

参考:题解 | #分糖果问题#

大佬的思路更简单:
先从左向右遍历,右边大于左边就+1;
然后从右向左遍历,左边大于右边就取max(左边孩子的糖果数,右边孩子的糖果数+1)。

简单的思路很难理出来,反正这里就是抓住关键点,一个是每人至少1个糖,一个是只和相邻的比较。

class Solution {
public:
    /**
     * pick candy
     * @param arr int整型vector the array
     * @return int整型
     */
    
    int candy(vector<int>& arr) {
        // write code here
        int m=arr.size();
        vector<int> can(m,1);
        
        for(int i=1;i<m;i++)
        {
            if(arr[i]>arr[i-1]) can[i]=can[i-1]+1;//首先用当前的和前一位得分相比,确保右边得分高的总比左边糖果数多
        }
        
        for(int i=m-2;i>=0;i--)
        {
            if(arr[i]>arr[i+1]) can[i]=max(can[i],can[i+1]+1);//用当前和后一位相比,取自己和后一位+1的最大值,保障左边得分高的也比右边糖果数多
        }
        
        int candy=0;
        for(int i=0;i<m;i++)
        {
            candy+=can[i];
        }
        return candy;
    }
};

1.6 跳格子

本来思路:
从0索引开始,在nums[0]范围内找最大值max,那么下一次就从最大值开始找nums[max]范围中的最大值,依次类推,直到索引到达倒数第二个。
但这样会超时。

所以优化方法是,只遍历一次,然后找每个索引处可以到达的最大索引maxPos,同时设置边界end,每当遍历到边界的时候,就是要跳一次的时候,step+1,然后重新设置end=maxPos

class Solution {
public:
    int jump(vector<int>& nums) {
        #if 0
        int step=0,mmax,ns;
        for(int i=0;i<nums.size()-1;)
        {
            mmax=0;
            int j=1;
            while(j<=nums[i])
            {
                if(nums[i+j]>mmax) ns=i+j;
            }
            step++;
            i=ns;
        }
        return step;
        #endif

        int step=0,end=0,mmax=0;
        for(int i=0;i<nums.size()-1;i++)
        {
            mmax=max(mmax,i+nums[i]);
            if(i==end)
            {
                end=mmax;
                step++;
            }
        }
        return step;
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值