给你一个整数数组 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])