LeetCode-Python-1354. 多次求和构造目标数组(数学 + 模拟法 + 堆)

给你一个整数数组 target 。一开始,你有一个数组 A ,它的所有元素均为 1 ,你可以执行以下操作:

令 x 为你数组里所有元素的和
选择满足 0 <= i < target.size 的任意下标 i ,并让 A 数组里下标为 i 处的值为 x 。
你可以重复该过程任意次
如果能从 A 开始构造出目标数组 target ,请你返回 True ,否则返回 False 。

示例 1:

输入:target = [9,3,5]
输出:true
解释:从 [1, 1, 1] 开始
[1, 1, 1], 和为 3 ,选择下标 1
[1, 3, 1], 和为 5, 选择下标 2
[1, 3, 5], 和为 9, 选择下标 0
[9, 3, 5] 完成
示例 2:

输入:target = [1,1,1,2]
输出:false
解释:不可能从 [1,1,1,1] 出发构造目标数组。
示例 3:

输入:target = [8,5]
输出:true
 

提示:

N == target.length
1 <= target.length <= 5 * 10^4
1 <= target[i] <= 10^9

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/construct-target-array-with-multiple-sums
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

第一种思路:

从[1, 1, 1]开始正推到target有一些困难,因为选择过多而无从下手。

但是从target反推到[1, 1, 1]则较为简单,因为对于每一时刻的数组来说,其最大值必定是题意中的x。

因此可以利用这一关系,将target逐步倒推回[1, 1, 1]:

如果知道target的和为 s,  最大值target[max_idx]为 m, 则根据题目规定,m为上一时刻的数组元素之和,

同时,因为改变的只有一个值,其他值并未改变,则可以明确,未改变的元素之和为 s - m,

因而可以推出: 在上一时刻, target[max_idx] = m - (s - m),意为上一时刻数组之和,减去除target[max_idx]之外的元素之和。

动态找最大值的过程可以用最大堆实现,注意Python自带的是最小堆,所以需要手动取反实现最大堆。

缺点:无法通过[1,1000000000]的极端test case

时间复杂度:O(N + SlogN), S为max(target)

空间复杂度:O(N)

class Solution(object):
    def isPossible(self, target):
        """
        :type target: List[int]
        :rtype: bool
        """
        from heapq import *
        s = sum(target)
        target = [-item for item in target]
        heapify(target)
        while s > len(target):
            m = -heappop(target)
            new_m = m - (s - m)
            if new_m == m:
                break
            heappush(target, -new_m)
            s = s - m + new_m

        return not any([num != -1 for num in target])

第二种思路:

考虑一下优化,观察上述极端test case可以发现最大值始终都是1000000000,因此对其进行的多次 push 和 pop 操作是优点浪费的,

所以可以得出以下优化:

对最大值进行重复操作,直到其的值小于次大值为止,再将其push进堆。

缺点:还是超时

时间复杂度:O(N + logNlogS)

空间复杂度:O(N)

class Solution(object):
    def isPossible(self, target):
        """
        :type target: List[int]
        :rtype: bool
        """
        from heapq import *
        if len(target) == 1 and sum(target) != 1:
            return False
        s = sum(target)
        target = [-item for item in target]
        heapify(target)
        while s > len(target):
            # print target
            # 找当前最大的数和第二大的数
            m = -heappop(target)
            s_m = -heappop(target)
            # 更新 m 并更新 s
            new_m = m - (s - m)
            if m == new_m:
                break
            s = s - m + new_m
            while new_m > s_m:
                # 一直处理最大的数直到它比第二大的数小
                m = new_m
                new_m = new_m - (s - new_m)
                s = s - m + new_m

            heappush(target, -new_m)
            heappush(target, -s_m)

        return not any([num != -1 for num in target])

第三种思路:

现在问题已经不是出在对堆的操作的复杂度上了,而是每次1000000000 - 1直到1为止的过程过于缓慢,

需要考虑利用数学方法进行加速。

对于一个进行重复操作的最大值来说,在它不再是最大值之前,它每次操作减去的值都是固定的,为其他数之和,

所以我们可以直接计算出每次减去的差值 diff,然后一次性减去 diff 的某个倍数,一步到位,使得之前的最大值不再为最大值。

            # 找当前最大的数和第二大的数
            m = -heappop(target)
            s_m = -target[0]

            # 更新 m
            diff = s - m             
            if not diff:
                break
            new_m = m - (max(1, (m - s_m) / diff) * diff)

以上代码就在模拟这个一步到位的过程 ,先计算出每次要减的差值 diff,再算一下要减去diff的个数为:

max(1, (m - s_m) / diff)),因为至少需要减一次,最多需要将最大值的新值变得比次大值小。

时间复杂度:O(N + logNlogS)

空间复杂度:O(N)

class Solution(object):
    def isPossible(self, target):
        """
        :type target: List[int]
        :rtype: bool
        """
        from heapq import *
        if len(target) == 1:
            return target[0] == 1
   
        s = sum(target)
        target = [-item for item in target]
        heapify(target)
        
        while s > len(target):
            # 找当前最大的数和第二大的数
            m = -heappop(target)
            s_m = -target[0]

            # 更新 m 并更新 s
            diff = s - m             
            if not diff:
                break
            new_m = m - (max(1, (m - s_m) / diff) * diff)
            s = s - m + new_m

            heappush(target, -new_m)

        return not any([num != -1 for num in target])
发布了722 篇原创文章 · 获赞 106 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/qq_32424059/article/details/104572944