动态规划篇——“瞻前顾后”

剑指 Offer 63. 股票的最大利润
在这里插入图片描述

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty()) return 0;
        int min=prices[0];
        int max=0;
        for(int i=0;i<prices.size();i++){
            min = std::min(min,prices[i]);
            max = std::max(max,prices[i]-min);
        }
        return max;
    }
};

理一下思路:

  1. 先定义出minmax
  2. 在循环中,遇到比min小的prices[i],就改变最小值
  3. max的操作同样是如此

一个循环下来,就可以得出答案
但是呢,自己多思考了一下,就炸出了自己的思维漏洞

[7,2,11,1,9,4]
假如,在遇到比min小的那个数之前,就已经得出了这个数组最大的差值max
那这时候再改变最小值min是不是有问题?
就如上这个数组,当min==2时,最大差值是max==11-2==9,而遍历到后面min=std::min(2,1)
max怎么办?
这里的例子比较简单,一下子能想通过来,但当时没看答案之前,就想不通的…
因为max已经记录下来了,如果后面有一个数n>=11,那n-1 > 11-2
这时max就会改变,而不是当时自己以为的已经是最大




像这样修改前面已经暂定下来的值的题,还有一道


300. 最长递增子序列
在这里插入图片描述

最长上升子序列好像是dp里一个常见的类型题,百度百科直接给出了两种时间复杂度的解法,也有博主整理了这个问题



动态规划

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int len = nums.size();
        vector<int> ans(len,1);
        
        for(int i=1;i<len;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]) ans[i] = std::max(ans[i],ans[j]+1);
            }
        }
        sort(ans.begin(),ans.end());
        return ans[len-1];
    }
};

这里还有一种解法 —— 贪心+二分

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int len = nums.size();
        vector<int> ans;
        
        ans.push_back(nums[0]);
        for(int i=1;i<len;i++){
            
            int back = ans[ans.size()-1];  

            if(nums[i]==back) continue;
            else if(nums[i]>back) ans.push_back(nums[i]);
            else{
                
                int left=0;
                int right=ans.size();
                while(left<right){
                    int mid=(left+right)>>1;

                    if(ans[mid]<nums[i]) left=mid+1;
                    else right=mid;
                }
                ans[right]=nums[i];
            }
        }
        return ans.size();
        
    }
};

在看代码时,记住一个前提,程序返回的是递增子序列的长度,而不是递增的子序列
输入的数组nums = [10,9,2,5,3,7,101,18],放置子序列的容器ans
程序的思路
遍历nums,若nums[i]比ans最后一个元素大,则将nums[i]添加到ans尾部
否则,替换ans数组中第一个比nums[i]大于或等于的元素
程序开始
int back=ans[ans.size()-1];
back==10nums[i]==9 >>> ans=={ 10 } ans=={ 9 }
back==9nums[i]==2 >>> ans=={ 9 } ans=={ 2 }
back==2nums[i]==5 >>> ans=={ 2,5 }
back==5nums[i]==3 >>> ans=={ 2,5 } ans=={ 2,3 }
back==3nums[i]==7 >>> ans=={ 2,3,7 }
back==7nums[i]==101 >>> ans=={ 2,3,7,101 }
back==101nums[i]==18 >>> ans=={ 2,3,7,101 } ans=={ 2,3,7,18 }
程序返回
4

为什么可以这么做?前面已经确定下来的子序列还能修改?
如果数组是nums[10,9,1,2,8,9,10,3,4,5,18,101],而程序结束时ans={1,2,3,4,5,18,101}
最长递增子序列可以是{1,2,8,9,10,18,101},也可以是{1,2,3,4,5,18,101}
如果数组nums很长很长,那明显{1,2,3,4,5···}的序列会更长
如果数组nums[10,9,1,2,8,9,10,3,4],在4就结束,ans={1,2,3,4,10}
这个不是最长递增子序列,但ans的长度是正确答案
{1,2,8,9,10}数组内,任意替换除了头尾的元素,最长递增子序列都是原来的序列
而一旦替换了最后一个元素,那最长递增子序列又有一个答案,而且是更优的答案

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值