题目
给定一个整数数组 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]
最具竞争力。
示例 2:
输入:nums = [2,4,3,3,5,4,9,6]
, k = 4
输出:[2,3,3,4]
提示:
1 <= nums.length <= 105
0 <= nums[i] <= 109
1 <= k <= nums.length
思路
为了找到最具竞争力的子序列,我们可以使用单调栈(Monotonic Stack)的策略。单调栈是一种保持元素顺序的栈结构,在这个问题中,我们需要维护一个递增栈,以确保子序列的最小化竞争力。
主要思路如下:
- 遍历数组
nums
,并在每一步中确保栈中的元素保持递增顺序。 - 如果当前元素比栈顶元素小,并且栈中的元素数目加上剩余的元素数目大于
k
,则弹出栈顶元素。 - 将当前元素入栈,前提是栈的大小小于
k
。
mostCompetitive函数
int* mostCompetitive(int* nums, int numsSize, int k, int* returnSize) {
*returnSize = k;
int* res = (int*)malloc(sizeof(int) * k);
int top = -1; // 栈顶指针,表示当前子序列的最后一个元素的位置
for (int i = 0; i < numsSize; i++) {
// 如果当前元素比栈顶元素小,并且栈中元素数目加上剩余的元素数目大于k,则弹出栈顶元素
while (top >= 0 && nums[i] < res[top] && top + numsSize - i > k - 1) {
top--;
}
// 如果当前栈的大小小于k,则将当前元素入栈
if (top < k - 1) {
res[++top] = nums[i];
}
}
return res;
}
returnSize
用于记录返回数组的大小,并将其设置为 k。
为存储最终结果的数组 res
分配了 k 个整数的内存空间。
top
初始化为 -1,表示栈为空,后续将用于指示栈顶元素的位置。
时间复杂度分析
-
for 循环: 该循环遍历了整个输入数组
nums
,时间复杂度为 O(n),其中 n 是数组nums
的长度。 -
while 循环: 在每次遍历中,while 循环最多执行栈中元素的数量(最多 k 次),因为每次循环都可能将栈顶元素弹出,最多进行 k 次操作。在最坏情况下,每个元素都需要进栈或出栈一次,所以 while 循环的总体时间复杂度为 O(n)。
综上所述,代码的总体时间复杂度为 O(n)。
空间复杂度分析
-
res 数组: 空间复杂度为 O(k),其中 k 是返回数组的大小,也是最终结果数组的长度。
-
top 变量: 使用了一个整数变量来表示栈顶指针,不占用额外的空间,因此空间复杂度为 O(1)。
综上所述,代码的总体空间复杂度为 O(k)。
这段代码的时间复杂度是线性的,因为它只对输入数组进行了一次线性遍历。而空间复杂度取决于返回数组的大小 k
。