LeetCode每日一题-- 1673. 找出最具竞争力的子序列

123 阅读4分钟

本题为数组类型题目,中等难度,解题方法运用到单调栈贪心

知识回顾:

一.单调栈

A.基本概念:

单调栈是一种常用的数据结构,通常用于解决与区间最值相关的问题。其核心思想是利用栈来维护一个单调递增或单调递减的序列。

B.常用于解决如下问题:

  1. 寻找下一个更大元素(Next Greater Element)
  2. 寻找下一个更小元素(Next Smaller Element)
  3. 寻找区间最大值或最小值

C.基本思想:

  1. 遍历待处理的元素序列,对于每个元素,执行以下步骤:
  2. 将当前元素与栈顶元素比较,如果当前元素大于(或小于)栈顶元素,则栈顶元素出栈,直到栈为空或栈顶元素不再满足单调性要求。
  3. 将当前元素入栈。
  4. 继续遍历下一个元素。

二·贪心

A.基本概念:

贪心算法是一种常用的算法范式,通常用于解决最优化问题。其核心思想是通过每一步的局部最优选择,来达到全局最优解。

B.常用于解决如下问题:

  1. 最小生成树问题
  2. 最短路径问题
  3. 区间调度问题
  4. 分数背包问题等

C.基本思想:

  1. 确定问题的贪心选择性质:首先,需要确定问题具有贪心选择性质,即通过局部最优选择可以达到全局最优解。这一步通常需要通过数学证明或直觉来确定。
  2. 构建解决方案:根据问题的特点,确定每一步的贪心选择。这一步通常包括确定每一步的最优解决方案,以及如何通过局部最优解来达到全局最优解。
  3. 求解最优解: 通过迭代或递归等方法,执行贪心选择,直到得到最终的解决方案。

题目内容:

给你一个整数数组 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

题目详解:

由题目可知,我们需要找到数组中指定长度中最有竞争力的子序列。由给出的示例易得,最有竞争力的子序列,序列排序下来,前面的元素需要尽可能的小,于是我们可以用贪心的思路。数组的元素我们在遍历的过程中我们从小到大历程去遍历它,遍历过程中,我们遇到小的元素便把它放进去,把大的抛出,这样的思路,其实就是单调栈。还有本题中,我们限制了子序列的长度为k,所以不能无限制的去进行单调栈的操作。

解题代码(C++):

class Solution {
public:
    vector<int> mostCompetitive(vector<int>& nums, int k) {
        int n=nums.size();    //定义数组的长度n
        int del=n-k;     //最多可从数组中删除的元素数量
        vector<int> ans;    //使用数组模拟栈
        for(int x:nums){
            while(ans.size()&&ans.back()>x&&del){   //满足数组长度与可删除元素数量大于零,并且栈底大于遍历的元素x
                ans.pop_back();   //弹出栈底元素
                del--;           //可减元素数量减一
            }
            ans.emplace_back(x);       //将x入栈
        }
        ans.resize(k);        //可能数组的长度大于k,根据题目要求,我们截取前k个
        return ans;
    }
};

注意:x入栈使用的是emplace_back,是 C++ 中用于在容器(例如 std::vectorstd::list 等)的末尾直接构造新元素的方法。与 push_back 不同,emplace_back 允许你通过参数直接构造新元素,而不需要先创建一个临时对象。

具体来说,emplace_back 接受的参数与容器中元素类型的构造函数参数相匹配,并使用这些参数直接在容器的末尾构造一个新元素。

例如,假设有一个 std::vector<int> 容器:

cppCopy Code
std::vector<int> vec;

使用 push_back 添加元素时:

cppCopy Code
vec.push_back(10); // 添加一个值为 10 的元素

而使用 emplace_back 添加元素时:

cppCopy Code
vec.emplace_back(10); // 直接在容器末尾构造一个值为 10 的元素

emplace_back 的优势在于可以避免创建临时对象,从而提高效率,尤其是在容器存储的是复杂类型(如自定义类)或移动语义优化的情况下。

需要注意的是,emplace_back 的参数是用于构造新元素的参数,而不是新元素本身的值。