- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。
拼接数组的最大分数
原题目如下:
给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,长度都是 n 。
你可以选择两个整数 left 和 right ,其中 0 <= left <= right < n ,接着 交换 两个子数组 nums1[left...right] 和 nums2[left...right] 。
- 例如,设
nums1 = [1,2,3,4,5]和nums2 = [11,12,13,14,15],整数选择left = 1和right = 2,那么nums1会变为[1,12,13,4,5]而nums2会变为[11,2,3,14,15]。
你可以选择执行上述操作 一次 或不执行任何操作。
数组的 分数 取 sum(nums1) 和 sum(nums2) 中的最大值,其中 sum(arr) 是数组 arr 中所有元素之和。
返回 可能的最大分数 。
子数组 是数组中连续的一个元素序列。arr[left...right] 表示子数组包含 nums 中下标 left 和 right 之间的元素 (含 下标 left 和 right 对应元素 ) 。
示例 1:
输入: nums1 = [60,60,60], nums2 = [10,90,10]
输出: 210
解释: 选择 left = 1 和 right = 1 ,得到 nums1 = [60,90,60] 和 nums2 = [10,60,10] 。
分数为 max(sum(nums1), sum(nums2)) = max(210, 80) = 210 。
示例 2:
输入: nums1 = [20,40,20,70,30], nums2 = [50,20,50,40,20]
输出: 220
解释: 选择 left = 3 和 right = 4 ,得到 nums1 = [20,40,20,40,20] 和 nums2 = [50,20,50,70,30] 。
分数为 max(sum(nums1), sum(nums2)) = max(140, 220) = 220 。
示例 3:
输入: nums1 = [7,11,13], nums2 = [1,1,1]
输出: 31
解释: 选择不交换任何子数组。
分数为 max(sum(nums1), sum(nums2)) = max(31, 3) = 31 。
提示:
n == nums1.length == nums2.length1 <= n <= 10^51 <= nums1[i], nums2[i] <= 10^4
解析
从题目的描述中可以看出,我们需要遍历不同的起点和终点,并将两个数组对应位置的子数组进行交换,然后再对每一个数组求和,计算其结果,从而得到得分。
题目思路已经有了,主要是如何实现。
如果直接按照上述思路进行模拟,很自然的,时间复杂度是O(N^2),必然无法达到时间要求。
肯定要进行一些优化,考虑到两个数组交换,全都交换和全不交换是一样的等价的,交换前一半和交换后一半是一样的等价的,所以在交换上,是可以进行优化裁剪的。
但是如何从左边到右边逐个遍历起点和终点,依然是耗时最久的环节。
换一个思路,两个数组都是在求和,而他们的总和是不变的,因为只是互换了数字,两个数组的总和是固定的,所以交换来交换去,无非是一些增增减减。可以先算算看两个数组的对位差是多少,来回交换就是在起始的sum和的基础上,用对位差进行微调。
比如以题目的例子为例nums1 = [60,60,60], nums2 = [10,90,10],nums1的起始和是180,nums2的起始和是110.计算一下对位的差是:[50, -30, 50],要得到结果最大,就是看对位差连续n个数字之和最大或者最小。在竞赛的时候没有仔细想到底是最大还是最小,不过不重要,都给算了,取一个最大即可。
这个例子不够明显,我们看第二个例子nums1 = [20,40,20,70,30], nums2 = [50,20,50,40,20],这两个数组的对位差是 [-30, 20, -30, 30, 10],而对于对位差,可以看出来,最后两个30+10才是和最大的,因此就互换这两个位置的原数组即可。
现在问题来到了,如果对一个对位差数组delta,找到连续n个数字之和最大。这个问题是一个典型的动态规划问题,而且是一维的,比较简单。可以较为容易的写出状态转移方程,如下:
dp[i+1] = max(dp[i]+delta[i+1], delta[i+1])
到第i+1个位置为止,sum求和最大的连续子序列之和是dp[i+1],它要么是delta[i+1]本身,要么是前序求和的结果加上delta[i+1],此时并不需要知道从哪一位开始,只需要知道到i+1位的和就行了。
得到dp数组之后,直接max一下就能知道最大和是多少了,直接加回到起始数组的和上。
同理,之前提到过,不知道应该是 nums1-nums2还是 nums2-nums1,因此做两遍,时间复杂度不增加,不会影响通过情况
这样就会在原有数组和的基础上在增加两个和,一个四个数字,选一个最大的即可。如果选出来的最大数组是原有数组和,说明这时候的交换方法是一个都不换,或者全部都换,他俩是等价的。
class Solution:
def maximumsSplicedArray(self, nums1: List[int], nums2: List[int]) -> int:
sum1 = sum(nums1)
sum2 = sum(nums2)
sum3 = sum1
sum4 = sum2
delta = [nums1[i] - nums2[i] for i in range(len(nums1))]
print(delta)
length = len(delta)
dp = [0] * length
dp[0] = delta[0]
for i in range(1, length):
dp[i] = max(delta[i], dp[i-1] + delta[i])
max_dp = max(dp)
if max_dp > 0:
sum4 = sum2 + max_dp
delta2 = [nums2[i] - nums1[i] for i in range(len(nums1))]
print(delta2)
length = len(delta2)
dp2 = [0] * length
dp2[0] = delta2[0]
for i in range(1, length):
dp2[i] = max(delta2[i], dp2[i-1] + delta2[i])
max_dp2 = max(dp2)
if max_dp2 > 0:
sum3 = sum1 + max_dp2
print(sum1, sum2, sum3, sum4)
return max(sum1, sum2, sum3, sum4)
后记
比较开心,在周赛的时候能够顺利地一次性通过第三题,以往都是两道题就结束了比赛。想比于过去的周赛,第298场和299场的题目质量较高,难度适中,不是过分拗难度的那种,但是又不放水,适合绝大多数朋友参与。不像有的场次,一个DFS走到死,三道题都是DFS。也有的时候,三道DP动态规划,二维三维,故意增加难度。不太贴近于实际工作场景。像本题,很有可能是从实际的工作场景中抽象出来的,也可以将这种解题思路在日常工作中得到使用,是个很好的赛题。
同时也让自己反思一下,在日常的工作中,是否是for循环套着for循环,一套就是三四层,QPS扛不住了就申请加机器,而不是从代码层面入手,考虑一下算法是否可以优化。
参加周赛不只是为了做题,而是为了能够举一反三,真正做到对自己的能力有所提升。