通过最少操作次数使数组的和相等

标签: 贪心 数组 哈希表 计数

难度: Medium

给你两个长度可能不等的整数数组 nums1 和 nums2 。两个数组中的所有值都在 1 到 6 之间(包含 1 和 6)。

每次操作中,你可以选择 任意 数组中的任意一个整数,将它变成 1 到 6 之间 任意 的值(包含 1 和 6)。

请你返回使 nums1 中所有数的和与 nums2 中所有数的和相等的最少操作次数。如果无法使两个数组的和相等,请返回 -1 。

 

示例 1:

输入:nums1 = [1,2,3,4,5,6], nums2 = [1,1,2,2,2,2]
输出:3
解释:你可以通过 3 次操作使 nums1 中所有数的和与 nums2 中所有数的和相等。以下数组下标都从 0 开始。
- 将 nums2[0] 变为 6 。 nums1 = [1,2,3,4,5,6], nums2 = [6,1,2,2,2,2] 。
- 将 nums1[5] 变为 1 。 nums1 = [1,2,3,4,5,1], nums2 = [6,1,2,2,2,2] 。
- 将 nums1[2] 变为 2 。 nums1 = [1,2,2,4,5,1], nums2 = [6,1,2,2,2,2] 。

示例 2:

输入:nums1 = [1,1,1,1,1,1,1], nums2 = [6]
输出:-1
解释:没有办法减少 nums1 的和或者增加 nums2 的和使二者相等。

示例 3:

输入:nums1 = [6,6], nums2 = [1]
输出:3
解释:你可以通过 3 次操作使 nums1 中所有数的和与 nums2 中所有数的和相等。以下数组下标都从 0 开始。
- 将 nums1[0] 变为 2 。 nums1 = [2,6], nums2 = [1] 。
- 将 nums1[1] 变为 2 。 nums1 = [2,2], nums2 = [1] 。
- 将 nums2[0] 变为 4 。 nums1 = [2,2], nums2 = [4] 。

 

提示:

  • 1 <= nums1.length, nums2.length <= 105
  • 1 <= nums1[i], nums2[i] <= 6

Submission

运行时间: 68 ms

内存: 20.6 MB

class Solution:
    def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
        a = sum(nums1)
        b = sum(nums2)
        if a > b:
            a, b = b, a
            nums1, nums2 = nums2, nums1
        if len(nums1) > 6 * len(nums2) or len(nums2) > 6 * len(nums1):
            return -1 

        x = Counter(nums1)
        y = Counter(nums2)

        ans = 0
        de = b - a

        while de > 0:
            for i in range(1, 7):
                ct = x[i] + y[7 - i]
                val = ct * (6 - i)
                if val < de:
                    de -= val
                    ans += ct
                else:
                    return ans + ceil(de / (6 - i))
        return ans

Explain

首先计算两个数组的和,如果一个数组的和大于另一个数组,将它们交换,以便始终让a是较小的和。然后检查是否有可能通过修改使两个数组和相等,即检查数组长度的6倍是否大于另一个数组的长度,如果是则返回-1。使用Counter来计数每个数组中各数字的出现次数。然后计算两数组和之差de。通过从1到6遍历,尝试减少这个差距。对于每个数字i,计算可以通过将nums1中的i增加到6或将nums2中的7-i减少到1来改变差距的总次数。如果通过当前数字能够完全消除差距,则直接返回结果;如果不能,则减少差距并继续处理。这个过程中,ans累加操作次数,直到差距为0。

时间复杂度: O(n + m)

空间复杂度: O(1)

class Solution:
    def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
        a = sum(nums1) # 计算nums1的总和
        b = sum(nums2) # 计算nums2的总和
        if a > b: # 确保a是较小的和
            a, b = b, a
            nums1, nums2 = nums2, nums1
        if len(nums1) > 6 * len(nums2) or len(nums2) > 6 * len(nums1): # 检查是否有可能使两数组和相等
            return -1

        x = Counter(nums1) # 数组nums1的计数器
        y = Counter(nums2) # 数组nums2的计数器

        ans = 0 # 初始化操作次数
        de = b - a # 计算两数组和的差距

        while de > 0: # 当还有差距需要处理时
            for i in range(1, 7): # 遍历所有可能的数字
                ct = x[i] + y[7 - i] # 计算可以通过增加或减少改变差距的次数
                val = ct * (6 - i) # 计算通过当前数字最多可以改变的差距
                if val < de: # 如果不足以消除全部差距
                    de -= val
                    ans += ct # 更新操作次数和剩余的差距
                else: # 如果可以完全消除差距
                    return ans + ceil(de / (6 - i)) # 计算最终需要的操作次数并返回
        return ans # 如果没有剩余差距,返回操作次数

Explore

这个检查是为了确保在最极端的情况下(即一个数组的所有元素都是1,另一个数组的所有元素都是6),也有可能通过修改使两个数组的和相等。如果不满足这个条件,即使把一个数组的所有元素改为1,另一个数组的所有元素改为6,两个数组的元素个数的差距也太大,导致即便是在极端情况下也无法通过增减操作使两个数组的和相等。因此,这是一个基本的可行性检查,确保算法执行前的基本条件满足。

在算法中,`ceil(de / (6 - i))`表示为了消除剩余的差额`de`,在当前数字`i`可以提供的单次最大改变量`6-i`下,需要进行的最小操作次数。使用`ceil`函数是因为即使差额不能被`6-i`完全整除,也需要进行一次完整的操作来确保差额被完全消除。例如,如果差额为10,而`6-i`为5,则需要进行`ceil(10 / 5) = 2`次操作。这保证了在每一个步骤中我们都在进行最优的操作,即每次尽可能多地减少差额,从而确保操作次数是最少的。

选择从1到6遍历数字是因为数组中的元素值范围是这个区间内的整数。算法需要评估每个元素值的变化对总和差`de`的影响。通过考虑将nums1中的元素增加到6(最大值)或将nums2中的元素减少到1(最小值),我们可以最大化单次操作对差值的影响。这种策略高效的原因在于它直接针对最大可行的单次差值变化,从而减少了总的操作次数,加快了收敛速度,提高了算法的效率。