一次遍歷獲取數組最大值和第二大值(最小值和第二小值)


前言

最近做了兩道題,有了一點想法,記錄一下

 

 

問題

問題一:遞增的三元子序列

給你一個整數數組 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 }
View Code

 

方法二:貪心

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
View Code

 

這個問題解決了,另一個問題來了,為什么對於題目一,貪心解法沒有在更新最小值時,將最小值賦值給次小值,為怎么結果是對的呢?

原因很簡單,因為題目找的是有沒有符合要求的序列是否存在,而不是具體的值。

盡管下標不滿足要求,但不會對結果的true/false判斷造成影響,因為if的順序,次小值b其實記錄了一個額外的信息:自己前面有一個比自己小的元素。

例如,對於序列 [5, 6, 7, 1] , 一開始a=5,b=6,b可以等於6就已經代表了其前面出現過比它小的數組,當a更新為1時,不影響,這時候如果找到了比b大的數字,就代表找到了遞增子序列。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM