前言
最近做了兩道題,有了一點想法,記錄一下
問題
問題一:遞增的三元子序列
給你一個整數數組 nums ,判斷這個數組中是否存在長度為 3 的遞增子序列。
如果存在這樣的三元組下標 (i, j, k) 且滿足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否則,返回 false 。
示例 1:
輸入:nums = [1,2,3,4,5]
輸出:true
解釋:任何 i < j < k 的三元組都滿足題意
示例 2:輸入:nums = [5,4,3,2,1]
輸出:false
解釋:不存在滿足題意的三元組
示例 3:輸入:nums = [2,1,5,0,4,6]
輸出:true
解釋:三元組 (3, 4, 5) 滿足題意,因為 nums[3] == 0 < nums[4] == 4 < nums[5] == 6
方法一:雙向遍歷
創建兩個長度為 n 的數組 leftMin 和 rightMax,對於 0≤i<n,leftMin[i] 表示 nums[0] 到 nums[i] 中的最小值,rightMax[i] 表示 nums[i] 到 nums[n−1] 中的最大值。
得到數組 leftMin 和 rightMax 之后,遍歷 1≤i<n−1 的每個下標 i,如果存在一個下標 i 滿足 leftMin[i−1]<nums[i]<rightMax[i+1],則返回true,否則返回false。
此方法需要三次遍歷,並且需要O(n)的時間復雜度

1 class Solution { 2 public boolean increasingTriplet(int[] nums) { 3 int n = nums.length; 4 if (n < 3) { 5 return false; 6 } 7 int[] leftMin = new int[n]; 8 leftMin[0] = nums[0]; 9 for (int i = 1; i < n; i++) { 10 leftMin[i] = Math.min(leftMin[i - 1], nums[i]); 11 } 12 int[] rightMax = new int[n]; 13 rightMax[n - 1] = nums[n - 1]; 14 for (int i = n - 2; i >= 0; i--) { 15 rightMax[i] = Math.max(rightMax[i + 1], nums[i]); 16 } 17 for (int i = 1; i < n - 1; i++) { 18 if (nums[i] > leftMin[i - 1] && nums[i] < rightMax[i + 1]) { 19 return true; 20 } 21 } 22 return false; 23 } 24 }
方法二:貪心
a 始終記錄最小元素,b 為某個子序列里第二大的數。
接下來不斷更新 a,同時保持 b 盡可能的小。
如果下一個元素比 b 大,說明找到了三元組。
1 class Solution: 2 def increasingTriplet(self, nums): 3 a = float("inf") 4 b = float("inf") 5 6 for i in nums: 7 if i <= a: 8 a = i 9 elif i <= b: 10 b = i 11 else: 12 return True 13 14 return False
問題二:至少是其他數字兩倍的最大數
給你一個整數數組 nums ,其中總是存在 唯一的 一個最大整數 。
請你找出數組中的最大元素並檢查它是否 至少是數組中每個其他數字的兩倍 。如果是,則返回 最大元素的下標 ,否則返回 -1 。
示例 1:
輸入:nums = [3,6,1,0]
輸出:1
解釋:6 是最大的整數,對於數組中的其他整數,6 大於數組中其他元素的兩倍。6 的下標是 1 ,所以返回 1 。
示例 2:輸入:nums = [1,2,3,4]
輸出:-1
解釋:4 沒有超過 3 的兩倍大,所以返回 -1 。
示例 3:輸入:nums = [1]
輸出:0
解釋:因為不存在其他數字,所以認為現有數字 1 至少是其他數字的兩倍。
除了遍歷以外,很直觀的一個想法,為什么我們要將最大值與所有元素進行比較呢?如果我們找到第二大的元素,將它的兩倍的值與最大值進行比較,不就能證明最大值是否大於所有元素兩倍的值嗎?
正好上面那道題目的貪心就是記錄數組中的最小值和次小值,我們換一下判斷符號就可以了,於是,我寫下如下代碼:
1 class Solution: 2 def dominantIndex(self, nums: List[int]) -> int: 3 if len(nums) == 1: 4 return -1 5 6 a = float("-inf") 7 a_ind = -1 8 b = float("-inf") 9 10 11 for i, val in enumerate(nums): 12 if val > a: 13 a = val 14 a_ind = i 15 elif val >b: 16 b = val 17 18 19 if a>=2*b: 20 return a_ind 21 return -1
其中,a_ind是為了記錄最大值的下標,但是,這個方法在遇到某些測試用例的時候是通不過的。
由於求最大值和次大值 與 求最小值和次小值 的邏輯是一樣的,我們用求最小值和次小值來測試一下
驗證求求最小值和次小值
借助題目一的貪心策略代碼,如下:
class Solution: def getNum(self, nums): a = float("inf") b = float("inf") for i, val in enumerate(nums): if val <= a: a = val elif val <= b: b = val return a, b if __name__ == '__main__': s = Solution() nums = [1, 2, 7, 6] print(s.getNum(nums)) nums = [5, 6, 6, 7] print(s.getNum(nums)) nums = [5, 6, 1, 7] print(s.getNum(nums))
結果為:
>>>
(1, 2)
(5, 6)
(1, 6)
可以看出,在第三個測試用例中,最小值是1,但是次小值是6,結果是錯的,原因在於,更新最小值的時候,沒有把最小值更新給次小值,因此將核心代碼加上那個一行賦值即可
1 class Solution: 2 def getNum(self, nums): 3 a = float("inf") 4 b = float("inf") 5 6 for i, val in enumerate(nums): 7 if val <= a: 8 b = a 9 a = val 10 elif val <= b: 11 b = val 12 13 return a, b
上述題目二的正確解法也是如此,加上一行賦值即可,代碼如下:

1 class Solution: 2 def dominantIndex(self, nums: List[int]) -> int: 3 a = float("-inf") 4 a_ind = -1 5 b = float("-inf") 6 7 for i, val in enumerate(nums): 8 if val > a: 9 b = a 10 a = val 11 a_ind = i 12 elif val > b: 13 b = val 14 15 if a >= 2 * b: 16 return a_ind 17 return -1
這個問題解決了,另一個問題來了,為什么對於題目一,貪心解法沒有在更新最小值時,將最小值賦值給次小值,為怎么結果是對的呢?
原因很簡單,因為題目找的是有沒有符合要求的序列是否存在,而不是具體的值。
盡管下標不滿足要求,但不會對結果的true/false判斷造成影響,因為if的順序,次小值b其實記錄了一個額外的信息:自己前面有一個比自己小的元素。
例如,對於序列 [5, 6, 7, 1] , 一開始a=5,b=6,b可以等於6就已經代表了其前面出現過比它小的數組,當a更新為1時,不影響,這時候如果找到了比b大的數字,就代表找到了遞增子序列。