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 = newLinkedList<>(); /* 主函式,輸入一組不重複的數字,返回它們的全排列 */ 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/