【问题标题】:minimum time to change arrays to make sums of two arrays equal更改数组以使两个数组之和相等的最短时间
【发布时间】:2021-01-26 22:04:53
【问题描述】:

输入是两个数组,每个数组的长度最长为 6,数组中的每个元素都可以是来自[1, 2, 3, 4, 5, 6] 的某个数字。返回最小数以更改数组以使两个数组之和相等。

例如A = [5, 4, 1, 2, 6, 5]B = [2];返回 6,因为我们可以在A 中掷五个骰子得到[1, 1, 1, 1, 1, 1],在B 中掷一个骰子得到[6];那么数组将具有相同的总和。

我的第一个想法是分别计算两个数组的和:Sum(A) = 23Sum(B) = 2

然后蛮力方法是计算使总和等于 2、3、4、...、23 所需的更改。

但是我觉得时间复杂度太高了。

这个问题的难点在于我们不知道我们尝试合并的目标总和是多少。

虽然在给定的示例中,A 的最小和为 6,B 的最大和值为 6,因此我们知道它们将在 6 处重叠,因此我们可以切割其他分支。

【问题讨论】:

  • 欢迎来到 SO!是否可以分享您尝试解决的问题,以便您从社区获得更快的答案?
  • 贪心算法在这里应该可以很好地工作。如果 sum(A) > sum(B),则从 A 中获取最大元素,从 B 中获取最小元素,然后更改具有更多“潜力”的元素,然后重复。这样,重复查找最小值/最大值的复杂度最多为 O(n²),并且可以通过先对数组进行排序来降低。
  • 你好,你换了那个更有“潜力”的,我们使排序失效,我们把修改后的元素调整到合适的位置,反复调整
  • 嗯,是的,您使排序无效,但您也知道您已经查看了哪些元素,因此您可以按排序顺序迭代元素,而不必关心曾经改变了。

标签: arrays algorithm


【解决方案1】:

这是代码背后的逻辑illustration

def changes_to_equal_sums(a, b):
    # organize into dict so we can determine what list has the bigger sum
    sums = {
        sum(a): a,
        sum(b): b
    }
    # get the bigger sum list and sort it in descending order
    big_sum_dice = sorted(sums[max(sums.keys())], reverse=True)
    # get the smaller sum list and sort it in ascending order
    small_sum_dice = sorted(sums[min(sums.keys())])
    # calculate the delta/distance we need to equalize between the two sums
    dist = sum(big_sum_dice) - sum(small_sum_dice)
    # count the number of changes we have made
    count = 0
    # pointer to the last index in the big_sum_dice list
    last_big = 0
    # pointer to the last index in the small_sum_dice list
    last_small = 0

    # a while loop that will run when the sums aren't equal and while we didn't run out of index range (of both lists)
    while sum(big_sum_dice) != sum(small_sum_dice) and len(big_sum_dice)+len(small_sum_dice) > last_small+last_big:

        # choose more efficient element to change -
        # the most efficient elements to change are:
        # - from the big_sum_list, the numbers closer to 6
        # - from the small_sum_list, the numbers closer to 1
        if len(small_sum_dice) <= last_small or big_sum_dice[last_big] - 1 > 6 - small_sum_dice[last_small]:
            # check if the margin of the given number allows us to finally equalize the sums
            if dist < big_sum_dice[last_big]:
                count+=1
                big_sum_dice[last_big] = big_sum_dice[last_big] - dist
            # if not so we change the margin to the extreme - which is 1
            else:
                dist = dist - (big_sum_dice[last_big] -1)
                big_sum_dice[last_big] = 1
                count +=1
                last_big +=1
        else:
            # check if the margin of the given number allows us to finally equalize the sums
            if dist < 6-small_sum_dice[last_small]:
                count+=1
                small_sum_dice[last_small] = small_sum_dice[last_small] + dist
            # if not so we change the margin to the extreme - which is 6
            else:
                dist = dist - (6 - small_sum_dice[last_small])
                small_sum_dice[last_small] = 6
                count+=1
                last_small+=1

    # in case of no possible option (for ex. [1] [1,1,1,1,1,1,1]) return -1
    if sum(big_sum_dice) != sum(small_sum_dice):
        return -1
    return count


# for ex. :

A = [5, 4, 1, 2, 6, 5]
B = [2]
print(changes_to_equal_sums(A,B))

【讨论】:

    【解决方案2】:

    正如你自己提到的一个标签,一个简单的解决方案是通过贪心算法获得的。 让我们假设 A 的总和大于 B 的总和。

    那么我们优先考虑修改A的最大元素, 和 B 的最小元素。这可以通过首先对这两个数组进行排序来执行(也可以使用最大堆和最小堆)。

    这是一个 C++ 代码。因为很简单,我想你会很容易理解的。

    #include <iostream>
    #include <vector>
    #include <numeric>
    #include <algorithm>
    
    int n_changes (std::vector<int> &A, std::vector<int> &B) {
        int sum_A = std::accumulate (A.begin(), A.end(), 0);
        int sum_B = std::accumulate (B.begin(), B.end(), 0);
        
        if (sum_A == sum_B) return 0;
        if (sum_A < sum_B) {
          std::swap (A, B);
          std::swap (sum_A, sum_B);
        }
        
        std::sort (A.begin(), A.end(), std::greater<int>());
        std::sort (B.begin(), B.end());
        int nA = A.size();
        int nB = B.size();
        
        int count = 0;
        int iA = 0;
        int iB = 0;
        int candidate_A, candidate_B;
        while (sum_A > sum_B) {
            if (iA < nA) candidate_A = A[iA]; else candidate_A = 1;
            if (iB < nB) candidate_B = B[iB]; else candidate_B = 6;
            if ((candidate_A == 1) && (candidate_B == 6))  break;
            count ++;
            if ((candidate_A -1) > (6 - candidate_B)) {
                iA++;
                sum_A += (1 - candidate_A);
            } else {
                iB++;
                sum_B += (6 - candidate_B);
            }
        }
        if (sum_A > sum_B) count = -1;
        return count;
    }
    
    int main() {
        std::vector<int> A = {5, 4, 1, 2, 6, 5};
        std::vector<int> B = {2};
    
        std::cout << n_changes (A, B) << "\n";
    }
    

    【讨论】:

    • @lamnguyen 感谢您的修改。我在文中提到“让我们假设 A 的总和大于 B 的总和”,但实际上最好这样。
    【解决方案3】:
    • 想想你根本解决不了的情况
    • 不对数组进行排序,而是构建 2 个每个大小为 6 的映射(构建和查找的时间为 O(N),查找时间为 O(1))+ 计算总和
    • 然后对差异应用贪心算法以移动最大步长等,直到差异为零(再次 O(N) 复杂度)

    总体复杂度为 O(N)

    Python 代码:

        def _cast_to_dict(A):
            initial_dict = {i: 0 for i in range(1, 7)}
            total_sum = 0
            for a in A:
                total_sum += a
                initial_dict[a] += 1
            return initial_dict, total_sum      
        
        def solution(A, B):
            if len(A) > 6 * len(B) or len(B) > 6 * len(A):
                 # Case where the problem is not solvable
                raise ValueError("Impossible to achieve")
            state_of_A, sum_A = _cast_to_dict(A)
            state_of_B, sum_B = _cast_to_dict(B)
        
            total_moves = 0
            if sum_A == sum_B:
                return 0
            elif sum_A < sum_B:
                state_of_A, state_of_B = state_of_B, state_of_A
                sum_A, sum_B = sum_B, sum_A
            # Now we can assume sum_A > sum_B
            # to reduce the diff, we can either increase B or decrease A
            diff = sum_A - sum_B
        
            print('DIFF %i' % diff)
            possible_jumps = {5: [(6, 1)], 4:[(6, 2), (5, 1)], 3:[(6, 3), (5, 2), (4, 1)], 2:[(6, 4), (5, 3), (4, 2), (3, 1)], 1:[(6, 5), (5, 4), (4, 3), (3, 2), (2, 1)]}
            while diff > 0:
                max_diff_to_jump = min(5, diff)
                for possible_jump in range(max_diff_to_jump, 0, -1):
                    is_jump_realized = False
                    print('Possible jump %i' % possible_jump)
                    for swap in possible_jumps[possible_jump]:
                        if state_of_A[swap[0]] > 0:
                            print('flipping A with (%s, %s)' % swap)
                            state_of_A[swap[0]] -= 1
                            state_of_A[swap[1]] += 1
                            diff -= possible_jump
                            total_moves += 1
                            is_jump_realized = True
                            break
                        elif state_of_B[swap[1]] > 0:
                            print('flipping B with (%s, %s).T' % swap)
                            state_of_B[swap[1]] -= 1
                            state_of_B[swap[0]] += 1
                            diff -= possible_jump
                            total_moves += 1
                            is_jump_realized = True
                            break                  
                    if is_jump_realized:
                            break
            return total_moves
    

    【讨论】:

      【解决方案4】:

      贪心算法应该可以很好地解决这个问题:

      • 确定哪些数组的和较大,哪些较小
      • 可选:对数组进行排序以加快执行以下步骤
      • 虽然总和不一样:
        • 从较大的数组中获取最大元素,从较小的数组中获取最小元素
        • 确定哪个具有更大的“潜力”来平衡总和,例如“较大”数组中的5 可以更改为1,或者较小数组中的3 可以更改为6
        • 选择具有更多“潜力”的元素并更改它(一直到 1 或 6,或根据需要)

      重复查找最小和最大元素的复杂度最多为 O(n²),并且可以通过对数组进行一次排序然后按顺序迭代元素来将其降低到 O(n logn)。 (您不必重新排序数组或重新计算总和,因为您知道哪些元素更改了多少,并且您不必再次查看这些元素。)

      【讨论】:

      • 当有一组固定的可能值时,可以轻松地在 O(n) 中排序,例如范围在 1 到 6 之间时(就像这里的情况一样)。排序甚至不是严格必要的:只需计算每种类型的值(这是在没有实际排序步骤的情况下计算排序)。尽管如果输入大小固定且很小(这里似乎也是这种情况),那么复杂性在很大程度上是没有意义的。
      猜你喜欢
      • 2020-05-24
      • 2022-10-23
      • 2017-01-23
      • 1970-01-01
      • 2019-08-06
      • 2021-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多