LeetCode 2170. 使数组变成交替数组的最少操作数

170 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情

本题的意思光看题目可能一时半会理解不了,我们可以通过题目提供的用例来分析一下:

[3,1,3,2,4,3]

以上面的数组为例,若要满足题目要求,则:

  • 当i = 2时,下标0和2的元素相同
  • 当i = 3时,下标1和3的元素相同
  • 当i = 4时,下标2和4的元素相同
  • 当i = 5时,下标3和5的元素相同

由此得出结论,奇数下标的元素必须相同,偶数下标的元素必须相同。

还需要满足nums[i - 1] != nums[i],则:

  • 当i = 1时,下标0和1的元素不相同
  • 当i = 2时,下标1和2的元素不相同
  • 当i = 3时,下标2和3的元素不相同
  • 当i = 4时,下标3和4的元素不相同
  • 当i = 5时,下标4和5的元素不相同

由此得出结论,奇数下标和偶数下标的元素必须不相同。

题目的要求是给你一个数组,通过改变数组中某些位置上的元素值使得数组满足刚才得出的两个结论,而且要求是最少操作数。 那么怎样能够保证最少操作数,当数组中的元素大部分都不需要被修改时,它的操作数就是最少的,所以我们需要找出奇数下标和偶数下标中个数最多的元素值,保持这些最多的元素值不变,而去改变那些个数较少的元素值即可保证操作数最少。

还是以[3,1,3,2,4,3]举例,先列举出数组中奇数下标和偶数下标的元素及其元素个数:

  • 奇数下标:元素1,个数为1;元素2,个数为1;元素3,个数为1
  • 偶数下标:元素3,个数为2;元素4,个数为1

先看偶数下标,在偶数下标中,个数最多的元素为3,此时只需操作1次(让剩余的元素4变换为3)即可保证偶数下标的元素相同,操作数最少; 再看奇数下标,因为三种元素的个数均为1,所以随意指定个数最多的元素即可,比如指定元素1,那么就需要将剩下的2个元素均变换为1,此时操作2次,操作数最少; 将两个操作数相加即可得到最终的操作数1 + 2 = 3,我们反向思维想一想,整个数组中只有奇数下标和偶数下标中个数最多的元素不需要变换,那么操作数就应该等于剩下的元素个数,5 - 2 - 1 = 3也能得到正确结果。

公式为数组总长度 - 偶数下标个数最多的元素个数 - 奇数下标个数最多的元素个数。

还需要考虑其它情况,比如题目中给出的用例:

[1,2,2,2,2]

首先仍然是找出奇数下标和偶数下标的元素及其元素个数:

  • 奇数下标:元素2,个数为2
  • 偶数下标:元素1,个数为1;元素2,个数为2

此时偶数下标个数最多的元素值为2,所以应该将剩下的元素变换为2,而奇数下标只有一个元素,无需变换,变换后的结果为:

[2,2,2,2,2]

但显然这违背了奇数下标和偶数下标的元素值不相同的结论,所以,当奇数下标和偶数下标中个数最多的元素值相同时,我们就需要找出个数第二多的元素值,使用它进行替换,并进行分情况讨论:

  • 若是将奇数下标剩余的元素替换为个数第二多的元素,那么操作数就等于`数组长度 - 奇数下标个数第二多的元素个数 - 偶数下标个数最多的元素个数
  • 若是将偶数下标剩余的元素替换为个数第二多的元素,那么操作数就等于`数组长度 - 偶数下标个数第二多的元素个数 - 奇数下标个数最多的元素个数

此时得到两个情况的操作数,取其中的较小值即可。

综上所述,代码如下:

public static int minimumOperations(int[] nums) {
    // 元素个数小于1,无需操作
    if (nums.length < 2) {
        return 0;
    }
    Map<Integer, Integer> odd = new HashMap<>();
    Map<Integer, Integer> even = new HashMap<>();
    // 统计数组的奇数下标和偶数下标对应的元素个数
    for (int i = 0; i < nums.length; i++) {
        if (i % 2 != 0) {
            odd.put(nums[i], odd.getOrDefault(nums[i], 0) + 1);
        } else {
            even.put(nums[i], even.getOrDefault(nums[i], 0) + 1);
        }
    }
    // 得到奇数下标的最大值及次大值
    int oddMax = 0;
    int oddSecondMax = 0;
    int oddMaxKey = 0;
    for (Integer oddKey : odd.keySet()) {
        if (oddMax < odd.get(oddKey)) {
            oddSecondMax = oddMax;
            oddMax = odd.get(oddKey);
            oddMaxKey = oddKey;
        } else if (oddSecondMax < odd.get(oddKey)) {
            oddSecondMax = odd.get(oddKey);
        }
    }
    // 得到偶数下标的最大值及次大值
    int evenMax = 0;
    int evenSecondMax = 0;
    int evenMaxKey = 0;
    for (Integer evenKey : even.keySet()) {
        if (evenMax < even.get(evenKey)) {
            evenSecondMax = evenMax;
            evenMax = even.get(evenKey);
            evenMaxKey = evenKey;
        } else if (evenSecondMax < even.get(evenKey)) {
            evenSecondMax = even.get(evenKey);
        }
    }
    // 若奇数下标与偶数下标最多的元素值不相同
    if (oddMaxKey != evenMaxKey) {
        return nums.length - oddMax - evenMax;
    } else {
        // 若相同,分情况讨论
        return Math.min((nums.length - oddSecondMax - evenMax), (nums.length - oddMax + -evenSecondMax));
    }
}

此题得解。