问题描述
小U在公司年会上运气极佳,赢得了一等奖。作为一等奖得主,他有机会在一排红包中做两次切割,将红包分成三部分,要求第一部分和第三部分的红包总金额相等。他可以获得的金额是第一部分红包的总金额。帮小U计算出他能从这些红包中拿到的最大奖金金额。
解析
该问题的要求就是计算前n项和和后n项和相等的最大数目,而这显然可以用双指针的方式来进行处理。不过这里先说一下另外一种解法,因为在我写完双指针解法后让AI检查代码时他给我的解法。
- 首先通过前缀和数组
prefix_sum和后缀和数组suffix_sum来存储从左到右的累积和和从右到左的累积和。 - 然后通过比较前缀和与后缀和来找到一个分割点。具体来说,如果
prefix_sum[i-1] == suffix_sum[i+1],那么说明在该位置可以将红包分为两个和相等的部分。 - 最后返回最大满足条件的红包总额。
MarsCode AI给出的解法
def solution(redpacks):
n = len(redpacks)
if n < 3:
return 0
# 计算前缀和数组
prefix_sum = [0] * n
prefix_sum[0] = redpacks[0]
for i in range(1, n):
prefix_sum[i] = prefix_sum[i-1] + redpacks[i]
# 计算后缀和数组
suffix_sum = [0] * n
suffix_sum[n-1] = redpacks[n-1]
for i in range(n-2, -1, -1):
suffix_sum[i] = suffix_sum[i+1] + redpacks[i]
max_amount = 0
for i in range(1, n-1):
if prefix_sum[i-1] == suffix_sum[i+1]:
max_amount = max(max_amount, prefix_sum[i-1])
return max_amount
这种方法在遍历整个数组时,比较的是 prefix_sum[i-1] 和 suffix_sum[i+1],并且从位置 1 到 n-1 都要遍历一次。虽然这个时间复杂度是 O(n),但不太高效,特别是在给出红包数组非常大的时候。优点是易于理解。
双指针
核心想法是从两端逼近中心,同时计算前n项和和后n项和,直到双指针相遇。
因为我们都知道一个简单的计算b>0时,a+b>a,所以当左边的和小于右边的和时,我们移动左指针。左边和大于
右边和时,我们移动右指针。相等则记录和的值,并同时移动指针。这样直到指针相遇就可以计算出最大的相等红包。
def solution(redpacks):
# Please write your code here
idx_left=0
idx_right=len(redpacks)-1
sum_left=redpacks[idx_left]
sum_right=redpacks[idx_right]
result=0
while idx_left!=idx_right and idx_left<idx_right:
if sum_left==sum_right:
result=sum_left
idx_left+=1
idx_right-=1
sum_left+=redpacks[idx_left]
sum_right+=redpacks[idx_right]
elif sum_left<sum_right:
idx_left+=1
sum_left+=redpacks[idx_left]
elif sum_left>sum_right:
idx_right-=1
sum_right+=redpacks[idx_right]
return result
这样我们只需要遍历一遍红包数组,且通过双指针优化了比较的效率,避免了不必要的重复计算。并且不需要开辟额外的数组空间。
总结
| 数组 | 双指针 | |
|---|---|---|
| 时间复杂度 | O(n) | O(n) |
| 时间复杂度 | O(n) | O(1) |
所以在以后遇到相似的问题的时候,可以使用双指针来优化运行效率。