【LeetCode】1671. 得到山形数组的最少删除次数 + 300. 最长递增子序列 【二分查找】【贪心】【动态规划】

105 阅读3分钟

1671. 得到山形数组的最少删除次数

题目链接

image.png

image.png

Python3

官方题解链接

image.png

image.png

方法一: 动态规划 O(n2)O(n)\lgroup O(n^2)、O(n)\rgroup

class Solution:
    # 子模块: 获取最长递增子模块
    def getLengthofLIS(self, nums):
        n = len(nums)
        dp = [1] * n   # 这里 每个都要遍历,dp 长度确定. 
        # dp[i]: 以 nums[i] 结尾的 最长上升子序列 的长度

        for i in range(1, n): # 
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j]+1)

        return dp 

    # 主模块
    def minimumMountainRemovals(self, nums: List[int]) -> int:
        # 获取 前缀 长度
        pre = self.getLengthofLIS(nums)
        # 获取 后缀长度
        suf = self.getLengthofLIS(nums[::-1])[::-1]
        length = 0

        for pre_L, suf_L  in zip(pre, suf):
            if pre_L > 1 and suf_L > 1:
                length = max(length, pre_L + suf_L - 1)
        return  len(nums) - length

image.png

⭐ 方法二:二分查找 O(nlogn)O(n)\lgroup O(nlogn)、O(n) \rgroup

这里不能 用贪心了,因为 两边分开找,总和不一定。

class Solution:
    # 子模块: 获取最长递增子模块
    def getLengthofLIS(self, nums):
        """ 二分查找 第一次见这种写法"""
        dp = []   # 这里 每个都要遍历,dp 长度确定. 
        # dp[i]: 以 nums[i] 结尾的 最长上升子序列 的长度

        lis = []
        for i, num in enumerate(nums):
            it = bisect_left(lis, num)  ## 在有序列表中查找将 num 插入的位置,并返回左侧的索引(相同值的最左边位置)。
            if it == len(lis):
                lis.append(num)
                dp.append(len(lis))
            else:
                lis[it] = num 
                dp.append(it+1)
        return dp 

    # 主模块
    def minimumMountainRemovals(self, nums: List[int]) -> int:
        # 获取 前缀 长度
        pre = self.getLengthofLIS(nums)
        # 获取 后缀长度
        suf = self.getLengthofLIS(nums[::-1])[::-1]
        length = 0

        for pre_L, suf_L  in zip(pre, suf):
            if pre_L > 1 and suf_L > 1:
                length = max(length, pre_L + suf_L - 1)
        return  len(nums) - length


C++

⭐ 方法二:二分查找 O(nlogn)O(n)\lgroup O(nlogn)、O(n) \rgroup

class Solution {
public:
    // 主模块
    int minimumMountainRemovals(vector<int>& nums) {
        vector<int> pre = getLengthofLIS(nums);
        vector<int> suf = getLengthofLIS({nums.rbegin(), nums.rend()}); //rbegin  rend
        reverse(suf.begin(), suf.end());

        int length = 0;
        for (int i = 0; i < nums.size(); ++i){
            if (pre[i] > 1 && suf[i] > 1){
                length = max(length, pre[i] + suf[i] - 1);
            }
        }
        return nums.size() - length;

    }    
    
    // 子模块
    vector<int> getLengthofLIS(const vector<int> & nums){ // 要 const 
        vector<int> dp, lis;
        for (int i = 0; i < nums.size(); ++i){
            auto it = lower_bound(lis.begin(), lis.end(), nums[i]);  // lower_bound
            if (it == lis.end()){
                lis.push_back(nums[i]);
                dp.push_back(lis.size());
            }
            else{
                *it = nums[i];
                dp.push_back(it - lis.begin() + 1);
            }
        }
        return dp;
    }
};

300. 最长递增子序列

题目链接

image.png

image.png

Python3

方法一: 动态规划 O(n2)O(n)\lgroup O(n^2)、O(n) \rgroup

注意子序列的理解,不需要连续。但也不是小于就可以,还要结合 dp。

image.png

image.png

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        dp = []  # 以 nums[i] 结尾的 最长上升子序列 的长度,这样 转移公式才不会错
        for i in range(len(nums)):
            dp.append(1)  # 重要 提前将 dp[i] 置1
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i],dp[j]+1)  # 和上一个 dp[j] 比较, 找最大的
        return max(dp)

image.png

⭐ 方法二: 贪心 + 二分查找 O(nlogn)O(n)\lgroup O(nlogn)、O(n) \rgroup

image.png

image.png

image.png

image.png

image.png

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        d = [nums[0]]  ## 记录 长度为 index + 1 的子序列的 尾数. 这个尾数越小越好
        idx = 0
        for i in range(1, len(nums)):
            if nums[i] > d[idx]:
                d.append(nums[i])
                idx += 1
            else:  ## 更小的尾数  更新前面
                left, right = 0, len(d)-1
                loc = right  # 
                while left <= right:
                    mid = left + (right - left)//2
                    if d[mid] >= nums[i]:
                        loc = mid  ##  注意这里的写法
                        right = mid - 1
                    else:
                        left = mid + 1
                # 更新 d[k+1] 
                d[loc] = nums[i]
        return len(d)            
             

image.png

C++

⭐ 方法二: 贪心 + 二分查找 O(nlogn)O(n)\lgroup O(nlogn)、O(n) \rgroup

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> d;
        d.push_back(nums[0]);
        int idx = 0;
        for (int i = 1; i < nums.size(); ++i){
            if (nums[i] > d[idx]){
                d.push_back(nums[i]);
                idx += 1;
            }
            else{
                int left = 0, right = d.size()-1;
                int loc = right;
                while (left <= right){
                    int mid = (right + left) >> 1;
                    if (d[mid] >= nums[i]){
                        loc = mid;
                        right = mid - 1;
                    }
                    else{
                        left = mid + 1;
                    }
                }
                d[loc] = nums[i];
            }
        }
        return d.size();
    }
};