一次遍历获取数组最大值和第二大值(最小值和第二小值)

前言

最近做了两道题,有了一点想法,记录一下

 

 

问题

问题一:递增的三元子序列

给你一个整数数组 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大的数字,就代表找到了递增子序列。

 

posted @ 2022-01-13 11:40  r1-12king  阅读(1732)  评论(0编辑  收藏  举报