leetcode 402.移掉K位数字/leetcode 5614. 找出最具竞争力的子序列(单调栈/dfs)
- 题目描述——leetcode 5614找出最具竞争力的子序列
给你一个整数数组 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]
- 解法一:超时的dfs
为什么一想到用dfs呢?因为最近正在练习dfs模板....觉得这道题和全排列是一个道理,只是每次排列需要在当前元素索引后面的位置搜索元素,然后再对结果进行排序,排序后第一个肯定是我们要找的数组(思路很垃圾,因为不会优化....)
那么这里套的就是dfs解决数字全排列的模板,菜鸡的我只会套模板....
List<List<Integer>> res = new LinkedList<>(); /* 主函数,输入一组不重复的数字,返回它们的全排列 */ List<List<Integer>> permute(int[] nums) { // 记录「路径」 LinkedList<Integer> track = new LinkedList<>(); backtrack(nums, track); return res; } // 路径:记录在 track 中 // 选择列表:nums 中不存在于 track 的那些元素 // 结束条件:nums 中的元素全都在 track 中出现 void backtrack(int[] nums, LinkedList<Integer> track) { // 触发结束条件 if (track.size() == nums.length) { res.add(new LinkedList(track)); return; } for (int i = 0; i < nums.length; i++) { // 排除不合法的选择 if (track.contains(nums[i])) continue; // 做选择 track.add(nums[i]); // 进入下一层决策树 backtrack(nums, track); // 取消选择 track.removeLast(); } }
遍历nums数组,每次搜索索引i+1后面的数组,然后找到所有的排列,这里vis可以用来判断当前元素是否被用掉。
class Solution: def mostCompetitive(self, nums: List[int], k: int) -> List[int]: self.res = list() vis = [0] * len(nums) r = list() def dfs(nums, u, r, vis): # r = list if u == k: self.res.append(copy.deepcopy(r)) return for i in range(0, len(nums)): if vis[i] == 0: r.append(nums[i]) vis[i] = 1 dfs(nums[i + 1:], u + 1, r, vis[i+1:]) vis[i] = 0 r.pop() dfs(nums, 0, r, vis) self.res.sort() return self.res[0]
超时了,看大佬的解答,这道题正确的打开方式其实是单调栈!不过还是期待哪位dfs大佬优化一个不超时的dfs版本。
- 解法二:单调栈
为什么想到用单调栈?
这里可能要说起leetcode 402题了,移除K位数字。
我们看看这道题题目:
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。 注意: num 的长度小于 10002 且 ≥ k。 num 不会包含任何前导零。 示例 1 : 输入: num = "1432219", k = 3 输出: "1219" 解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。 示例 2 : 输入: num = "10200", k = 1 输出: "200" 解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。 示例 3 : 输入: num = "10", k = 2 输出: "0" 解释: 从原数字移除所有的数字,剩余为空就是0。
移除k个数字,和剩余k个数字,都是同样的道理。就相当于剩余length-k个数字或者是k个数字具有单调性质,那么我们是不是可以用一个单调栈始终维护一个单调的序列,每次被栈pop出去的就是被移除的,被栈push进去的就是需要留下的。
伪代码的实现是这样的:
<!-- 单调栈伪代码 --> for (遍历这个数组) { if (栈顶元素小于等于当前比较元素) { 入栈; } else { while (栈不为空 && 栈顶元素大于当前元素&&还有能减去的元素) { 栈顶元素出栈; 更新结果; } 当前数据入栈; } while(如果还要元素需要删除){ 栈顶元素出栈; } }
直接看leetcode 5614. 找出最具竞争力的子序列的代码
class Solution: def mostCompetitive(self, nums: List[int], k: int) -> List[int]: stack = list() count = len(nums) - k #需要被移除的数 for i in range(0, len(nums)): if not stack or nums[i] >= stack[-1]: stack.append(nums[i]) else: while stack and nums[i] < stack[-1] and count != 0: stack.pop() count -= 1 stack.append(nums[i]) while count != 0: stack.pop() count -= 1 return stack
那么leetcode 402.移掉K位数字是不是就很简单了?
每次只要遍历的元素大于栈顶,就入栈,小于栈顶且需要删除元素,那么栈顶就出栈,将此元素入栈,这样pop掉的元素正好是删除的个数,如果删除的元素小于k,则继续将栈顶的元素pop出去。
后面为啥要判断res是不是为空,是为了通过‘100’这样的测试用例
class Solution: def removeKdigits(self, num: str, k: int) -> str: num = [int(i) for i in num] stack = list() for i in range(0, len(num)): if not stack or stack[-1] < num[i]: stack.append(num[i]) else: while stack and stack[-1] > num[i] and k != 0: stack.pop() k -= 1 stack.append(num[i]) while k != 0: stack.pop() k -= 1 res = (''.join(str(i) for i in stack)).lstrip('0') if res: return res else: return '0'
参考链接:https://leetcode-cn.com/problems/find-the-most-competitive-subsequence/solution/js-mo-ni-yi-ge-dan-diao-di-zeng-zhan-by-akumu213/