【问题标题】:Minimum number of operations to make pair sums of array equal使数组的对和相等的最小操作数
【发布时间】:2021-03-27 02:46:42
【问题描述】:

给你一个偶数长度的整数列表。考虑一个操作,您可以在 nums 中选择任意数字并使用 [1, max(nums)] 之间的值对其进行更新。返回所需的操作数,使得对于每个 i,nums[i] + nums[n - 1 - i] 等于相同的数字。问题可以贪婪地解决。

注意:n 是数组的大小,max(nums) 是 nums 中的最大元素。

例如:nums = [1,5,4,5,9,3] 预期的操作是 2。

解释:maxnums 是 9,所以我可以将 nums 的任何元素更改为 [1, 9] 之间的任何数字,这需要一次操作。

  • 在索引 0 处选择 1 并将其更改为 6
  • 在索引 4 处选择 9 并将其更改为 4。

现在这使得 nums[0] + nums[5] = nums[1] + nums[4] = nums[2] + nums[3] = 9。我们更改了 2 个数字,这需要我们进行 2 次操作这是此输入的最小值。


我使用的方法是找到总和的中位数,然后用它贪婪地找到运算次数。 让我们根据给定的条件找到数组的所有和。

  • 可以通过 nums[i] + nums[n-1-i] 计算总和。

  • 设 i = 0,nums[0] + nums[6-1-0] = 4。

  • i = 1,nums[1] + nums[6-1-1] = 14。

  • i = 2,nums[2] + nums[6-1-2] = 9。

将这些总和存储在一个数组中并对其进行排序。 sums = [4,9,14] 排序后。现在从总和中找到中位数,即 9,因为它是中间元素。

现在我使用这个中位数来平衡总和,我们可以找到操作的数量。我还添加了用于计算操作次数的代码。

int operations = 0;
for(int i=0; i<nums.size()/2; i++) {
    if(nums[i] + nums[nums.size()-1-i] == mid)
        continue;
        
    if(nums[i] + nums[nums.size()-1-i] > mid) {
        if(nums[i] + 1 <= mid || 1 + nums[nums.size()-1-i] <= mid) {
            operations++;
        } else {
            operations += 2;
        }
    } else if (maxnums + nums[nums.size()-1-i] >= mid || nums[i] + maxnums >= mid) {
        operations++;
    } else {
        operations += 2;
    }
}

此示例的总操作数为 2,这是正确的。

这里的问题是,在某些情况下,选择中位数会给出错误的结果。例如,nums = [10, 7, 2, 9, 4, 1, 7, 3, 10, 8] 需要 5 次操作,但如果选择中位数 (16),我的代码会给出 6。

选择中位数不是最佳方法吗?谁能帮忙提供一个更好的方法?

【问题讨论】:

  • 我不明白要求。以[1,5,4,5,9,3]的变换为例,你得带我一步一步来。
  • @GilbertLeBlanc 我在示例中添加了一些解释。如果您需要更多详细信息,请告诉我。
  • 为什么是中位数?模式不是更有用吗,即最常出现的总和,这意味着大多数数字对不必调整?
  • @tobias_k 对于 nums = [10, 7, 2, 9, 4, 1, 7, 3, 10, 8],总和为 [5, 5, 16, 17, 18] 和如果选择 5,因为它出现了两次,操作数仍然是 6。如何? (10 + 8 > 5) (7 + 10 > 5) (9 + 7 > 5) 都需要 2 次操作来改变并使和等于 5,因此总共需要 6 次操作。
  • 嗯,我不知道有什么证明之类的,但取中位数似乎是合理的,因为它似乎是最接近 sum 的所有其他元素的可能值。

标签: arrays algorithm


【解决方案1】:

我认为以下应该可行:

  • 迭代数对
  • 对于每一对,计算该对的总和,以及通过仅更改 一个 值可以获得的最小和最大总和
  • 在开始一个需要较少更改的新“区域”时使用 -1 更新字典/地图,当该区域结束时使用 +1
  • 迭代该字典中的边界并更新所需的总更改以找到需要最少更新的总和

Python 中的示例代码,将9 作为示例的最佳总和,需要更改5

from collections import defaultdict

nums = [10, 7, 2, 9, 4, 1, 7, 3, 10, 8]
m = max(nums)
pairs = [(nums[i], nums[-1-i]) for i in range(len(nums)//2)]
print(pairs)

score = defaultdict(int)
for a, b in map(sorted, pairs):
    low = a + 1
    high = m + b
    score[low] -= 1
    score[a+b] -= 1
    score[a+b+1] += 1
    score[high+1] += 1
print(sorted(score.items()))

cur = best = len(nums)
num = None
for i in sorted(score):
    cur += score[i]
    print(i, cur)
    if cur < best:
        best, num = cur, i
print(best, num)

总复杂度应该是 O(nlogn),创建字典需要 O(n),排序需要 O(nlogn),迭代字典中的排序值需要 O(n)。 (不要使用数组,否则如果max(nums) &gt;&gt; len(nums),复杂度可能会更高)

【讨论】:

  • 你是在 O(N) 中排序吗?
  • @SamSegers 很好,那么 O(nlogn)。
  • 分数上的迭代如何保证 curr 反映所有需要的更改? (最好是根据单个分数更新,但 curr 只反映以前看到的对。如果某些对的范围在当前之后开始呢?)
  • @גלעדברקן 不确定我是否理解您的问题。 score,通过构造,包括一对“只需要一个/零变化”区域开始或结束的所有“边界”。并且cur 会针对score 中的每个值进行更新,即所有更改。我错过了什么吗?
  • num 中的print(best, num) 是什么?
【解决方案2】:

(已更新接收更多信息)

最佳总和必须是以下之一:

  • 一对的总和 -> 因为您可以保留该对的两个数字
  • 一对的最小值 + 1 -> 因为它是可能的最小总和,您只需更改该对的 1 个数字
  • 一对的最大值 + 总的最大值 -> 因为它是可能的最大总和,您只需更改该对的 1 个数字

因此,有 N 个可能的总和。

可以通过多种方式计算此最优总和的操作总数

O(N²) 很简单。如果您想确认其他解决方案是否有效,您可以很容易地实施它。

做到O(N log N)

  • 获得所有可能的最优总和O(N)
  • 对于每个可能的总和,您可以计算 occ 具有该精确总和的对数,因此不需要任何操作。 O(N)
  • 对于所有其他对,您只需要知道它是否需要 1 或 2 次操作才能达到该总和。如果一对中的最小者太大而无法达到与可能的最小数字的总和,或者当对中的最大者太小而无法达到具有最大可能数字的总和时,则为 2。许多数据结构都可以用于此(BIT、Tree、..)。我只是使用了一个排序列表并应用了二进制搜索(虽然没有经过详尽的测试)。 O(N log N)

java 中的示例解决方案:

int[] nums = new int[] {10, 7, 2, 9, 4, 1, 7, 3, 10, 8};
// preprocess pairs: O(N)
int min = 1
    , max = nums[0];
List<Integer> minList = new ArrayList<>();
List<Integer> maxList = new ArrayList<>();
Map<Integer, Integer> occ = new HashMap<>();
for (int i=0;i<nums.length/2;i++) {
    int curMin = Math.min(nums[i], nums[nums.length-1-i]);
    int curMax = Math.max(nums[i], nums[nums.length-1-i]);
    min = Math.min(min, curMin);
    max = Math.max(max, curMax);
    minList.add(curMin);
    maxList.add(curMax);
    // create all pair sums
    int pairSum = nums[i] + nums[nums.length-1-i];
    int currentOccurences = occ.getOrDefault(pairSum, 0);
    occ.put(pairSum, currentOccurences + 1);
}
// sorting 0(N log N)
Collections.sort(minList);
Collections.sort(maxList);
// border cases 
for (int a : minList) {
    occ.putIfAbsent(a + max, 0);
}
for (int a : maxList) {
    occ.putIfAbsent(a + min, 0);
}

// loop over all condidates O(N log N)
int best = (nums.length-2);
int med = max + min;
for (Map.Entry<Integer, Integer> entry : occ.entrySet()) {
    int sum = entry.getKey();
    int count = entry.getValue();
    int requiredChanges = (nums.length / 2) - count;
    if (sum > med) {
        // border case where max of pair is too small to be changed to pair of sum
        requiredChanges += countSmaller(maxList, sum - max);
    } else if (sum < med) {
        // border case where having a min of pair is too big to be changed to pair of sum
        requiredChanges += countGreater(minList, sum - min);
    }
    System.out.println(sum + " -> " + requiredChanges);
    best = Math.min(best, requiredChanges);
}
System.out.println("Result: " + best);
}

// O(log N)
private static int countGreater(List<Integer> list, int key) {
 int low=0, high=list.size();
 while(low < high) {
     int mid = (low + high) / 2;
     if (list.get(mid) <= key) {
        low = mid + 1;
    } else {
        high = mid;
    }
 }
 return list.size() - low;
}

// O(log N)
private static int countSmaller(List<Integer> list, int key) {
 int low=0, high=list.size();
 while(low < high) {
     int mid = (low + high) / 2;
     if (list.get(mid) < key) {
        low = mid + 1;
    } else {
        high = mid;
    }
 }
 return low;
}

【讨论】:

  • 这也是我的第一个想法,但如果最常见的总和 小于 比其他一对中的较小数字,它就行不通了。
  • 是的,当我开始讨论这个问题时,我只阅读了问题的第一部分。现在用新信息更新它。
【解决方案3】:

只是提供一些理论——我们可以很容易地证明所需更改的上限是n / 2,其中n 是元素的数量。这是因为每一对都可以在 one 中更改为1 + Cmax(nums) + C 之间的任何值,其中C 是一对中的两个元素中的任何一个。对于最小的C,我们可以绑定到最大的max(nums) + 1;而对于最大的C,我们可以将1 + max(nums)绑定到最低。

由于这两个界限在最坏情况下是相等的,因此我们可以保证存在一些解决方案,其中最多 N / 2 发生变化,至少有一个 C(数组元素)保持不变。

由此我们得出结论,最优解要么 (1) 至少有一对元素都没有改变,而其余元素只需要每对改变一次,或者 (2) 我们的最优解有 n / 2 变化,如上所述.

因此,我们可以继续测试每个现有配对作为候选者的单一或零变化可能性。我们可以遍历每对包含两到三个可能性的排序列表,并标有每个成本和索引。 (此页面上的其他作者提供了类似的方法和代码。)

【讨论】:

    猜你喜欢
    • 2022-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-20
    • 1970-01-01
    • 2011-02-13
    相关资源
    最近更新 更多