• 单调栈题目:找出最具竞争力的子序列


    题目

    标题和出处

    标题:找出最具竞争力的子序列

    出处:1673. 找出最具竞争力的子序列

    难度

    6 级

    题目描述

    要求

    给你一个整数数组 nums \texttt{nums} nums 和一个正整数 k \texttt{k} k,返回 nums \texttt{nums} nums 的长度为 k \texttt{k} k 且最具竞争力的子序列。

    数组的子序列是从数组中删除一些元素(可能不删除元素)得到的序列。

    在子序列 a \texttt{a} a 和子序列 b \texttt{b} b 第一个不相同的位置上,如果 a \texttt{a} a 中的数字小于 b \texttt{b} b 中对应的数字,那么我们称子序列 a \texttt{a} a 比子序列 b \texttt{b} b(相同长度下)更具竞争力。 例如, [1,3,4] \texttt{[1,3,4]} [1,3,4] [1,3,5] \texttt{[1,3,5]} [1,3,5] 更具竞争力,在第一个不相同的位置,也就是最后一个位置上, 4 \texttt{4} 4 小于 5 \texttt{5} 5

    示例

    示例 1:

    输入: nums   =   [3,5,2,6],   k   =   2 \texttt{nums = [3,5,2,6], k = 2} nums = [3,5,2,6], k = 2
    输出: [2,6] \texttt{[2,6]} [2,6]
    解释:在所有可能的子序列集合 {[3,5],   [3,2],   [3,6],   [5,2],   [5,6],   [2,6]} \texttt{\{[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]\}} {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中, [2,6] \texttt{[2,6]} [2,6] 最具竞争力。

    示例 2:

    输入: nums   =   [2,4,3,3,5,4,9,6],   k   =   4 \texttt{nums = [2,4,3,3,5,4,9,6], k = 4} nums = [2,4,3,3,5,4,9,6], k = 4
    输出: [2,3,3,4] \texttt{[2,3,3,4]} [2,3,3,4]

    数据范围

    • 1 ≤ nums.length ≤ 10 5 \texttt{1} \le \texttt{nums.length} \le \texttt{10}^\texttt{5} 1nums.length105
    • 0 ≤ nums[i] ≤ 10 9 \texttt{0} \le \texttt{nums[i]} \le \texttt{10}^\texttt{9} 0nums[i]109
    • 1 ≤ k ≤ nums.length \texttt{1} \le \texttt{k} \le \texttt{nums.length} 1knums.length

    解法

    思路和算法

    这道题中,竞争力顺序和字典序相似。要得到数组 nums \textit{nums} nums 的长度为 k k k 的最具竞争力的子序列,等价于从数组 nums \textit{nums} nums 中删除 nums . length − k \textit{nums}.\textit{length} - k nums.lengthk 个元素,使得剩下的 k k k 个元素形成的子序列是竞争力顺序最靠前的。记 remove = nums . length − k \textit{remove} = \textit{nums}.\textit{length} - k remove=nums.lengthk,则 remove \textit{remove} remove 是需要删除的元素个数。

    基于上述分析,这道题和「移掉 K 位数字」相似。

    如果只从数组 nums \textit{nums} nums 中删除一个元素,使得剩下的子序列为竞争力顺序最靠前,则应该使子序列中靠前的数字尽可能小。可能有两种情况,以下是两种情况和对应的策略:

    1. 存在下标 i i i 使得 nums [ i ] > nums [ i + 1 ] \textit{nums}[i] > \textit{nums}[i + 1] nums[i]>nums[i+1],则找到满足该要求的最小的下标 i i i,将下标 i i i 处的元素删除之后,剩下的子序列为竞争力顺序最靠前;

    2. 不存在下标 i i i 使得 nums [ i ] > nums [ i + 1 ] \textit{nums}[i] > \textit{nums}[i + 1] nums[i]>nums[i+1],则将 nums \textit{nums} nums 的最后一个元素删除,剩下的子序列为竞争力顺序最靠前。

    根据上述策略执行 remove \textit{remove} remove 次删除元素操作,得到的子序列即为最具竞争力的子序列。

    由于数组 nums \textit{nums} nums 的长度最大为 1 0 5 10^5 105,因此模拟上述操作的做法会超出时间限制,需要优化。

    由于上述策略优先考虑删除左边的元素,因此可以从左到右遍历 nums \textit{nums} nums,在遍历过程中进行删除操作,只能删除 remove \textit{remove} remove 个元素。为了使得删除操作符合上述策略,可以使用单调栈,单调栈存储数字,满足从栈底到栈顶的数字单调递增。

    从左到右遍历数组 nums \textit{nums} nums,对于每个元素,进行如下操作:

    1. 如果栈不为空、栈顶元素大于当前元素且已经删除的元素个数小于 remove \textit{remove} remove,则将栈顶元素出栈,重复该操作直到上述条件不满足(三个条件之一不满足即停止);

    2. 将当前元素入栈。

    遍历结束之后,如果已经删除的元素个数小于 remove \textit{remove} remove,则需要继续删除元素,删除元素的方法是将栈顶元素出栈,直到删除的元素个数达到 remove \textit{remove} remove。此时,栈内元素按照从栈底到栈顶的顺序构成的子序列为数组 nums \textit{nums} nums 的长度为 k k k 的最具竞争力的子序列。

    以下是示例 2 的计算过程,其中 num = [ 2 , 4 , 3 , 3 , 5 , 4 , 9 , 6 ] \textit{num} = [2, 4, 3, 3, 5, 4, 9, 6] num=[2,4,3,3,5,4,9,6] k = 4 k = 4 k=4,需要删除的元素个数 remove = 4 \textit{remove} = 4 remove=4

    1. 下标 0 0 0 处的元素是 2 2 2,将 2 2 2 入栈, stack = [ 2 ] \textit{stack} = [2] stack=[2],其中左边为栈底,右边为栈顶。

    2. 下标 1 1 1 处的元素是 4 4 4,将 4 4 4 入栈, stack = [ 2 , 4 ] \textit{stack} = [2, 4] stack=[2,4]

    3. 下标 2 2 2 处的元素是 3 3 3,由于 4 > 3 4 > 3 4>3,因此将 4 4 4 出栈,将 3 3 3 入栈, stack = [ 2 , 3 ] \textit{stack} = [2, 3] stack=[2,3],共删除 1 1 1 个元素。

    4. 下标 3 3 3 处的元素是 3 3 3,将 3 3 3 入栈, stack = [ 2 , 3 , 3 ] \textit{stack} = [2, 3, 3] stack=[2,3,3],共删除 1 1 1 个元素。

    5. 下标 4 4 4 处的元素是 5 5 5,将 5 5 5 入栈, stack = [ 2 , 3 , 3 , 5 ] \textit{stack} = [2, 3, 3, 5] stack=[2,3,3,5],共删除 1 1 1 个元素。

    6. 下标 5 5 5 处的元素是 4 4 4,由于 5 > 4 5 > 4 5>4,因此将 5 5 5 出栈,将 4 4 4 入栈, stack = [ 2 , 3 , 3 , 4 ] \textit{stack} = [2, 3, 3, 4] stack=[2,3,3,4],共删除 2 2 2 个元素。

    7. 下标 6 6 6 处的元素是 9 9 9,将 9 9 9 入栈, stack = [ 2 , 3 , 3 , 4 , 9 ] \textit{stack} = [2, 3, 3, 4, 9] stack=[2,3,3,4,9],共删除 2 2 2 个元素。

    8. 下标 7 7 7 处的元素是 6 6 6,由于 9 > 6 9 > 6 9>6,因此将 9 9 9 出栈,将 6 6 6 入栈, stack = [ 2 , 3 , 3 , 4 , 6 ] \textit{stack} = [2, 3, 3, 4, 6] stack=[2,3,3,4,6],共删除 3 3 3 个元素。

    9. 遍历结束之后,还需要删除 1 1 1 个元素才能得到长度为 4 4 4 的子序列,因此将 6 6 6 出栈, stack = [ 2 , 3 , 3 , 4 ] \textit{stack} = [2, 3, 3, 4] stack=[2,3,3,4],共删除 4 4 4 个元素。

    10. 最具竞争力的子序列是 [ 2 , 3 , 3 , 4 ] [2, 3, 3, 4] [2,3,3,4]

    代码

    class Solution {
        public int[] mostCompetitive(int[] nums, int k) {
            int length = nums.length;
            int remove = length - k;
            Deque<Integer> stack = new ArrayDeque<Integer>();
            for (int i = 0; i < length; i++) {
                int num = nums[i];
                while (!stack.isEmpty() && stack.peek() > num && remove > 0) {
                    stack.pop();
                    remove--;
                }
                stack.push(num);
            }
            while (remove > 0) {
                stack.pop();
                remove--;
            }
            int[] subsequence = new int[k];
            for (int i = k - 1; i >= 0; i--) {
                subsequence[i] = stack.pop();
            }
            return subsequence;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    复杂度分析

    • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 进行删除 n − k n - k nk 个元素的操作,由于每个元素最多入栈和出栈各一次,其中最多有 n − k n - k nk 个数字出栈,因此时间复杂度是 O ( n + ( n − k ) ) = O ( n ) O(n + (n - k)) = O(n) O(n+(nk))=O(n)

    • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。空间复杂度主要取决于栈空间,栈内元素个数不会超过 n n n

  • 相关阅读:
    磁盘检查清理修复命令
    SSK:超级键盘模拟器,调用底层,可模拟所有按键
    C++day04(类中特殊成员函数、匿名对象、友元、常成员函数和常对象、运算符重载)
    SkyEye:助力飞行器状态控制系统仿真
    【面试题】原型与原型链 进一步理解~
    每日一题|2022-11-7|816. 模糊坐标|字符串枚举|Golang
    consul 键值对操作命令
    我做的百度飞桨PaddleOCR .NET调用库
    强化学习入门
    交易所金融知识
  • 原文地址:https://blog.csdn.net/stormsunshine/article/details/121108144