leetcode
leetcode 1501 ~ 1550
找出最具竞争力的子序列

找出最具竞争力的子序列

难度:

标签:

题目描述

代码结果

运行时间: 90 ms, 内存: 28.2 MB


/*
 * 思路:虽然Stream API并不是为这种需要索引的操作而设计的,
 * 但我们可以使用一个辅助栈和普通循环来实现这个问题的解。
 */
import java.util.*;
import java.util.stream.*;

public class Solution {
    public int[] mostCompetitive(int[] nums, int k) {
        Stack<Integer> stack = new Stack<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && stack.peek() > nums[i] && stack.size() + n - i > k) {
                stack.pop();
            }
            if (stack.size() < k) {
                stack.push(nums[i]);
            }
        }
        return stack.stream().mapToInt(i -> i).toArray();
    }
}

解释

方法:

题解采用单调栈的思路来解决问题。具体方法是遍历输入数组 nums,使用栈(stack)存储潜在的最具竞争力的子序列。对于每个元素 num,首先检查栈顶元素是否大于当前元素 num,并且检查是否还有足够的剩余元素(remain)可以从栈中移除。如果条件满足,则将栈顶元素弹出,这样做是为了保持栈中的元素尽可能小以形成最具竞争力的子序列。此外,每弹出一个元素,remain就减少1,表示可以丢弃的元素数减少。最后,将当前元素 num 压入栈中。遍历结束后,栈中可能的元素数量大于 k,因此只取前 k 个元素作为结果。这种方法确保了在满足长度为 k 的条件下,得到的子序列是最小的。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么题解中选择使用单调栈来求解最具竞争力的子序列,而不是其他数据结构如堆或优先队列?
单调栈被用于这种问题是因为它能有效地维护一个递增(或递减)的元素序列,并且可以在O(1)时间复杂度内对栈顶元素进行增加或删除操作。这一点对于当前问题尤为重要,因为我们需要依据当前元素与栈顶元素的比较来决定是否需要移除栈顶元素以保证子序列的竞争力。使用堆或优先队列虽然可以快速找到最小或最大元素,但在删除非顶部元素时通常需要O(log n)的时间复杂度,这会增加整体的时间成本。此外,堆和优先队列不支持像栈一样轻松地移除序列中间的元素,这是解决此问题时必需的。
🦆
题解中提到,当栈顶元素大于当前元素并且还可以移除元素时就弹出栈顶元素,这种操作是否会导致某些情况下无法得到真正的最具竞争力的子序列?
题解中的单调栈策略确保了在每一步都尽可能地保持子序列元素的最小性。当栈顶元素大于当前元素时,移除栈顶元素可以使得当前的子序列更小,从而更具竞争力。这种策略假设后续元素能填补被移除元素的位置而不损害总体大小,这是基于输入数组的完整遍历来保证的。然而,确实存在一些极端情况(尽管非常罕见),如果后续没有足够小的元素来替代被移除的元素,可能会导致子序列不是最优的。但在大多数情况下,这种策略是有效且能够获得最具竞争力的子序列的。
🦆
在题解的while循环中,对remain的条件检查(remain > 0)是如何确保最终的栈中仍然能保留足够的元素以满足长度为k的要求?
在题解中,remain变量表示可以从栈中移除的元素数量,它是由总元素数减去所需子序列长度k得到的。每次从栈中弹出一个元素时,remain减1。这个检查确保只有当还有足够的元素可以被移除时才进行弹出操作。这样做的目的是为了保证即使进行了栈顶元素的移除,数组中仍有足够多的剩余元素可以用来填补栈,确保最终栈的长度至少为k。如果remain为0,则意味着已经没有多余的元素可以移除,此时停止弹出操作,以确保栈的长度。

相关问题