动态规划 | 贪心+二分查找:力扣300. 最长上升子序列

1、题目描述:

在这里插入图片描述

2、题解:

方法1:动态规划:
动态规划问题,弄清楚三点:
1、重复子问题;
2、最优子结构;
3、无后效性。

动态规划:
1、状态定义;
2、状态转移方程;
3、初始化;base case
4、输出;
5、思考状态压缩。

可以用递归去求,但是会存在重叠子问题,加个备忘录可以解决重复问题。
画dp table

状态定义:
dp[i]的值代表nums以第i个数字为结尾的最长子序列长度

转移方程:
python代码如下:

i遍历数组
dp[i] = max(dp[i],dp[j]+1) for j in [0,i)
解释:对于j∈[0,i):
1、如果nums[j] < nums[i],那么说明nums[i]可以接在nums[j]后面,此时最长上升子序列长度为dp[j]+1
2、如果nums[j] >= nums[i],nums[i]不可以接在nums[j]后面,上升子序列不成立

初始化:
dp[i]所有元素置1,解释:每个元素都可以单独成为一个长度为1子序列

返回值:
dp中的最大值
python:

#动态规划
if not nums:return 0
n = len(nums)
dp = [1] * n
for i in range(1,n):
    for j in range(0,i):
        if nums[j] < nums[i]:
            dp[i] = max(dp[i],dp[j]+1)
return max(dp)

C++:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        // 动态规划
        if (nums.empty()) return 0;
        int  n = nums.size();
        vector<int> dp(n,1);
        for (int i = 1;i < n;i++){
            for (int j = 0;j < i ;j++){
                if (nums[j] < nums[i])
                    dp[i] = max(dp[i],dp[j] + 1);
            }
        }
        int res = 0;
        for (auto p : dp)
            res = max(res,p);
        return res;
    }
};

方法2:贪心+二分查找
状态定义:
tail[i] 表示长度为i+1的所有上升子序列的结尾的最小值

状态转移方程:

遍历数组,对于每个值num
如果 num > tail[-1]:
        添加到tail后
        continue
在tail中用二分查找,找到第一个大于等于nums的位置,然后替换成nums(前只让让第 1 个严格大于 nums 的数变小)

初始化:
tail[0] = nums[0],解释:仅有一个元素时,它一定是长度为1且结尾最小的元素

返回值:
tail的长度
就是可以看成蜘蛛扑克牌的玩法:给一堆扑克牌,让从左到右分成若干堆。有一定规则:只能把点数小的牌放在大牌上,优先放在左边的堆,如果当前堆没有可放的位置,就新建一个堆去放。
python代码如下:

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums: return 0
        n = len(nums)
        tail = [nums[0]]
        for num in nums:
            if num > tail[-1]:
                tail.append(num)
                continue
            left, right = 0, len(tail) - 1
            while left < right:
                mid = left + (right - left) // 2
                if tail[mid] < num:
                    left = mid + 1
                else:
                    right = mid
            tail[left] = num
        return len(tail)

或者:

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:return 0
        n = len(nums)
        tail = [nums[0]]
        for i  in range(1,n):
            left,right = 0,len(tail)
            while left < right:
                mid = left + (right - left) // 2
                if tail[mid] < nums[i]:
                    left = mid + 1
                else:
                    right = mid
            if left == len(tail):
                tail.append(nums[i])
            else:
                tail[left] = nums[i]
        return len(tail)

或者:

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums: return 0
        n = len(nums)
        tail = [0] * n
        res = 0 #牌堆数初始化为0
        for num in nums:
            #搜索左侧边界的二分查找
            left, right = 0, res
            while left < right:
                mid = left + (right - left) // 2
                if tail[mid] < num:
                    left = mid + 1
                else:
                    right = mid
            if left == res:
                res += 1
            tail[left] = num
        return res

C++代码如下:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        // 贪心 + 二分
        if (nums.empty()) return 0;
        vector<int> tail;
        tail.push_back(nums[0]);
        for (auto num : nums){
            if (tail.back() < num) //只可以放比上一张小的
            {
                tail.push_back(num);
                continue;
            }
            int left = 0,right = tail.size() - 1;
            while (left < right){
                int mid = left + (right - left) / 2;
                if (tail[mid] >= num)
                    right = mid;
                else
                    left = mid + 1;
            }
            tail[left] = num;
        }
        return tail.size();
    }
};

3、复杂度分析:

方法1:
时间复杂度:O(N^2)
空间复杂度:O(N)
方法2:
时间复杂度:O(NlogN)
空间复杂度:O(N)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值