1673. 找出最具竞争力的子序列:滑动窗口加单调栈思想——渐步分析加多重优化

1673. 找出最具竞争力的子序列:滑动窗口加单调栈思想——渐步分析加多重优化

1673. 找出最具竞争力的子序列 - 力扣(LeetCode)

题目描述

给你一个整数数组 nums 和一个正整数 k ,返回长度为 k 且最具 竞争力nums 子序列。
数组的子序列是从数组中删除一些元素(可能不删除元素)得到的序列。
 
在子序列 a 和子序列 b 第一个不相同的位置上,如果 a 中的数字小于 b 中对应的数字,那么我们称子序列 a 比子序列 b(相同长度下)更具 竞争力 。 例如,[1,3,4][1,3,5] 更具竞争力,在第一个不相同的位置,也就是最后一个位置上, 4 小于 5
示例 1:
 
输入: nums = [3,5,2,6], k = 2
输出: [2,6]
解释: 在所有可能的子序列集合 {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中,[2,6] 最具竞争力。

题目分析

  1. 找到长度为k且最具竞争力的子序列,这个最具竞争是要相同元素最小。
  2. 那么我们来分析题意:
    1. 设n为数组长度,第一个元素一定是下标为[0, n-k]之间的最小值。因为要确保子序列长度为k。
    2. 设第一个元素下标为k,那么第二个元素一定是 [k+1, n-k+1]的最小值。
    3. 以此类推。
  3. 上述题意是滑动窗口思想的,同时因为若第二个元素下标不是n-k+1,那么其一定比前者大。也刚好符合单调栈思想。因此我们可以用非递减(递增或相等)单调栈。
  4. 因此思路步骤应该为:
    1. 我们首先在下标范围[0, n-k]之间构建单调栈,然后取出栈中最小的元素也是最开始压入栈元素,那么栈肯定不符合要求,所以我们用双端队列即可。
    2. 取出最小元素后,将下标为n-k+1的元素压入双端队列中
    3. 然后再取出其中最小元素。
    4. 依次类推

代码

class Solution {
public:
    vector<int> mostCompetitive(vector<int>& nums, int k) {

        int n = nums.size();
        deque<int> deq;

        //将下标在[0, n-k]的元素压入非递减单调双端队列(思想和单调栈一样)
        for(int i=0; i<=n-k; i++){
            // while中>号控制的单调栈特性,>表示非递减,< 表示非递增 严格递增递减需要进一步设计。
            while(!deq.empty()&&deq.back()>nums[i]){
                deq.pop_back();
            }
           deq.push_back(nums[i]);
        }
        vector<int> ans;
        //每次取一个元素,再将下标为n - k + ans.size()的元素压入单调双端队列中
        while(!deq.empty()){
            int curr = deq.front();
            ans.push_back(curr);
            deq.pop_front();
            
            int index = n - k + ans.size();
            if(index>=n) break;
            while(!deq.empty()&&deq.back()>nums[index]){
               deq.pop_back();
            }
            deq.push_back(nums[index]);
        }
        return ans;
    }
};

优化1

上述为正常思路,同时其可以进行一些优化。
优化1

  • 思想回顾: 之前通过题目分析后我们确定了两步走的思路,第一先将滑动窗口的值压入栈中,然后再将往后遍历,每次都取值,然后将滑动窗口往后,将窗口新包括的元素加入单调双端队列中。
  • 优化前: 需要使用单调双端队列替代单调栈,在取值的时候,需要往双端队列中加入新的元素。
  • 优化: 我们一次往单调栈中添加所有元素,不在取值的时候再进行添加操作,这样我们可以保证栈中的元素刚好k个,然后就可以直接用单调栈倒叙往数组中添加元素。
  • 优化后: 可以直接使用单调栈, 一次添加元素。
  • 但其实这两者时间复杂度没有差别。
class Solution {
public:
    vector<int> mostCompetitive(vector<int>& nums, int k) {
        int n = nums.size();
        stack<int> sta;

        for(int i=0; i<=n-1; i++){
            //确保栈中元素加上剩余元素能超过k个,要不然最后子序列长度就不够了
            while(!sta.empty()&&sta.top()>nums[i]&&sta.size()+n-i-k>0){
                sta.pop();
            }
            //确保刚好到k个
            if(sta.size()<k)
                 sta.push(nums[i]);
        }

        cout<<sta.size();
        vector<int> ans(k, 0);
        while(!sta.empty()){
            int curr = sta.top();
            ans[k-1] = curr;
            k--;
            sta.pop();
        }
        return ans;
    }
};

优化2

单调栈问题常见优化,使用数组来表示单调栈。这样直接就添加元素后就可以直接进行输出了。

class Solution {
public:
    vector<int> mostCompetitive(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> ans;

        for(int i=0; i<=n-1; i++){
            int index = ans.size();
            while(index!=0&&ans[index-1]>nums[i]&&index+n-i-k>0){
                ans.pop_back();
                index = ans.size();
            }
            //确保刚好到k个
            if(index < k)
                 ans.push_back(nums[i]);
        }
        return ans;
    }
};
  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值