[LeetCode解题报告] 1464. 数组中两元素的最大乘积(BFPRT)

一、 题目

1. 题目描述

给你一个整数数组 nums,请你选择数组的两个不同下标 ij使 (nums[i]-1)*(nums[j]-1) 取得最大值。

请你计算并返回该式的最大值。

 

示例 1:

输入:nums = [3,4,5,2]
输出:12 
解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12 。 

示例 2:

输入:nums = [1,5,4,5]
输出:16
解释:选择下标 i=1 和 j=3(下标从 0 开始),则可以获得最大值 (5-1)*(5-1) = 16 。

示例 3:

输入:nums = [3,7]
输出:12

 

提示:

  • 2 <= nums.length <= 500
  • 1 <= nums[i] <= 10^3
Related Topics
  • 数组
  • 排序
  • 堆(优先队列)

  • 👍 35
  • 👎 0

2. 原题链接

链接: 1464. 数组中两元素的最大乘积

二、 解题报告

1. 思路分析

实际上是求数组里的top2。

  1. 当然可以排序取top2。
  2. 也可两个哨兵遍历一次。
  3. 但我就是要写BFPRT!
    BFPRT算法可以以最坏*O(n)*但常数很大的时间下,找出TOPK,且以k分割的两侧,左侧所有数都小于a[k],右侧所有数大于a[k]。
1. BFPRT的思想是基于快排里的划分(partion),我们知道每次划分可以找出一个pivot,它应在位置m上,使得m左侧全小于pivot],右侧全大于pivot。
2. 那么基于这个查找可以进行二分,当m<k,说明k在m右边,去右边找;反之亦然。当m=k,那么可以返回。
3. 我们知道,基于划分的快排最坏是可以退化成O(n^2),当然用这个思想找topk也有可能退化成O(n^2)。(每次只搞定一个数)
4. 而避免这种退化的有效方法是取pivot时,尽量避免取极大极小值,取到中位数是最划算的。
5. 实现快排时,对pivot的选取通常是[左端点,右端点,正中间点]中取中位数。
6. BFPRT的选取方案是:
	- 对区间划分成5个一组的子区间,对这个区间内插排(通常长度16以下插排最快)取中位数;
	- 然后递归地进行:对这些中位数取中位数。
	- 最终的中位数作为pivot。
7. 然后使用二分的思想去寻找即可。

那么为什么复杂度是*O(n)*呢?

8. 首先要证明每次取得中位数,它的位置很靠中间:一定在30%~70%这个位置之间,也就是说最多有70%的数比它小,最多有70%的数比它大。
9. 每次find_mid时,找到的是中位数数组的中位数,这个中位数数组的长度是n//5,
	- 这个数组里有n//10个数比它小,
	- 而这些比它小的数都是原来5人组的中间值,所以每组里还有俩比它小的数n//10*2,所以共有n*3//10个数比它小。
10.因此,最坏是按70%的速度退化的。相当于每次只能搞定30%。 
11.时间复杂度 T(N) = T(N/5) + T(7N /10) + O(N)=T(9N/10)+O(n)。
12.套主项定理:a=1,b=10/9,f(n)=O(n)明显对数log(10/9,1)结果小于1,因此得:T(n) = O(n)
13.可以参考算法导论第九章。

对了,我试了,还是排序快!

主项定理

2. 复杂度分析

最坏时间复杂度O(n)

3. 代码实现

线段树

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        def insert_sort(arr,l,r):
            for i in range(l,r):
                x = arr[i+1]
                j = i 
                while x < arr[j] and j >= l:
                    arr[j+1] = arr[j]
                    j -= 1
                arr[j+1] = x
        def find_mid(arr,l,r):
            if l+1 >= r:
                return l 
            c = l
            for i in range(l,r-5,5):
                insert_sort(arr,i,i+4)
                arr[c],arr[i+2] = arr[i+2],arr[c]
                c += 1
            x = l+(r-l+1)//5*5
            insert_sort(arr,x,r)
            m = (x+r)//2
            arr[c],arr[m] = arr[m],arr[c]
            return find_mid(arr,l,c)        
        def partion(arr,l,r):
            pivot = arr[l]
            while l < r:
                while arr[r] >= pivot and l < r:
                    r -= 1
                arr[l] = arr[r]
                while arr[l] <= pivot and l < r:
                    l += 1
                arr[r] = arr[l]
            arr[l] = pivot
            return l                   
        def bfprt(arr,k,l,r):            
            # 在[l,r]里找到一个数,这个数应放在下标k,使k左边都小于arr[k],k右边都大,那么这个数就是第k小(从0开始数)               
            find_mid(arr,l,r)  # 把中位数放到l上作为pivot
            m = partion(arr,l,r)  # 把pivot放在应在的位置m上,同时使m左边都小于arr[m],m右边都大于等于arr[m]
            if m == k:
                return arr[m]
            elif m > k:
                return bfprt(arr,k,l,m-1)
            else:
                return bfprt(arr,k,m+1,r)            
        n = len(nums)
        bfprt(nums,n-2,0,n-1)
        # print(nums)
        return (nums[n-1]-1) * (nums[n-2]-1)

三、 本题小结

  1. 主项定理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值