1. 问题描述:
给你一个下标从 0 开始的数组 nums ,该数组由 n 个正整数组成。如果满足下述条件,则数组 nums 是一个交替数组 :
nums[i - 2] == nums[i] ,其中 2 <= i <= n - 1 。
nums[i - 1] != nums[i] ,其中 1 <= i <= n - 1 。
在一步操作中,你可以选择下标 i 并将 nums[i] 更改为任一正整数。返回使数组变成交替数组的最少操作数 。
示例 1:
输入:nums = [3,1,3,2,4,3]
输出:3
解释:
使数组变成交替数组的方法之一是将该数组转换为 [3,1,3,1,3,1] 。
在这种情况下,操作数为 3 。
可以证明,操作数少于 3 的情况下,无法使数组变成交替数组。
示例 2:
输入:nums = [1,2,2,2,2]
输出:2
解释:
使数组变成交替数组的方法之一是将该数组转换为 [1,2,1,2,1].
在这种情况下,操作数为 2 。
注意,数组不能转换成 [2,2,2,2,2] 。因为在这种情况下,nums[0] == nums[1],不满足交替数组的条件。
提示:
1 <= nums.length <= 10 ^ 5
1 <= nums[i] <= 10 ^ 5
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-operations-to-make-the-array-alternating/
2. 思路分析:
因为最终需要使得操作次数最少,所以我们尽可能修改那些出现次数较少的元素,由于相邻两个位置的元素不能够相等,所以我们可以使用哈希表统计一下奇数位置与偶数位置各个数字的出现次数,这里可以分为两种情况,第一种情况为奇数位置与偶数位置出现次数最多的两个数字不相等,这样我们将奇数位置其余数字修改为当前出现次数最多的那个数字,偶数位置的其余数字也修改为当前出现次数最多的那个数字,这种情况比较好处理;第二种情况是奇数位置与偶数位置出现次数最多的数字相等,此时我们不能像第一种情况那样直接修改其余数字为出现次数最大的数字,因为这样修改会出现相邻两个位置的元素相等的情况,我们肯定是将奇数或者偶数位置出现次数最大的那个数字修改为对应位置出现次数次大的那个元素,所以其实对应两种情况,对于奇数位置来说我们可以将其余数字修改为出现次数次大的那个数字,对于偶数位置也是类似的,我们在这两种情况中选择操作次数最少的情况即可,也即两种情况取一个min,不管是奇数位置还是偶数位置,出现次数最大的那个数字肯定是存在的,但是出现次数次大的那个数字可能不存在,例如[2,2,2,2,3],如果次大值不存在那么我们使用0来代替,此时结果是正确的,例如上面的例子我们尝试将奇数位置的2,2替换为次大值(位置从0开始),出现次大次数的数字不存在那么出现的次数为0,此时将2,2修改为出现次数次大对应的操作次数为2;我们可以令a1,a2,b1,b2为偶数位置与奇数位置对应的出现最大次数与次大次数,其中a表示偶数位置,b表示奇数位置,对于偶数位置替换其余数字为最大值,奇数位置其余数字替换为次大值,操作次数为(x + 1) / 2 - a1 + x / 2 - b2,其中(x + 1) / 2表示向上取整(数组长度为奇数的时候那么偶数位置的长度为(x + 1) / 2,位置从0开始),第二种情况为偶数位置替换其余数字为次大值,奇数位置为最大值,操作次数为(x + 1) / 2 - a2 + x / 2 - b1,两种情况取一个min即可,另外一种想法是我们可以使用数组总长度减去奇数位置替换为次大值与偶数位置替换为次大值中两种替换情况的较大值,减去最大值那么剩余的操作次数肯定是最少的。因为需要对数字出现次数从大到小排序,对于Go语言来说可以使用sort.Sort()或者sort.Slice()函数对结构体列表进行排序,可以参照博文中对结构体排序的方法,一般来说使用sort.Slice()函数比较方便。
3. 代码如下:
package main
import (
"fmt"
"sort"
)
// 定义一个结构体方便后面排序(类似于python的列表嵌套元组类型, 元组相当于是struct可以封装多个元素 只是python的排序可以直接调用sort函数)
type pair struct{ count, value int }
// type定义类型, mp属于结构体列表
type mp []pair
// 因为需要对结构体排序其中一种实现方法是实现sort接口中的下面定义的三个方法, 或者是调用sort.Slice()函数
func (p mp) Len() int { return len(p) }
// Less函数中定义按照数字出现的次数从大到小排序
func (p mp) Less(i, j int) bool { return p[i].count > p[j].count }
func (p mp) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// 求解两个int类型数字的最小值
func getMin(a, b int) int {
if a < b {
return a
} else {
return b
}
}
func minimumOperations(nums []int) int {
if len(nums) == 1 {
return 0
}
// count属于map的切片, 长度为2, count[0]存储偶数位置的数字出现次数, count[1]存储奇数位置的数字出现次数
count := []map[int]int{{}, {}}
for i := 0; i < len(nums); i++ {
count[i&1][nums[i]] += 1
}
// fmt.Println(count)
var (
a []pair
b []pair
)
// 遍历偶数位置的map将其添加到struct列表中方便后面的排序
for k, v := range count[0] {
a = append(a, pair{v, k})
}
for k, v := range count[1] {
b = append(b, pair{v, k})
}
// 结构体排序需要实现sort接口中的三个方法, mp属于结构体列表
sort.Sort(mp(a))
sort.Sort(mp(b))
// 也可以使用下面的方法进行排序
//sort.Slice(a, func(i, j int) bool {
// return a[i].value < a[j].value
//})
x := len(nums)
if a[0].value != b[0].value {
return (x+1)/2 - a[0].count + x/2 - b[0].count
} else {
res := 1000000
// a1, a2, b1, b2, a存储偶数位置, b存储奇数位置,a1表示最大次数, a2表示次大次数, b也是类似, 如果没有次大那么出现次数次大的次数为0结果也是正确的
a1, a2, b1, b2 := a[0].count, 0, b[0].count, 0
if len(a) > 1 {
a2 = a[1].count
}
if len(b) > 1 {
b2 = b[1].count
}
// 求解奇数位置与偶数位置修改为次大值的最小操作次数
res = getMin(res, (x+1)/2-a1+x/2-b2)
res = getMin(res, x/2-b1+(x+1)/2-a2)
return res
}
}
python:
from typing import List
class Solution:
def minimumOperations(self, nums: List[int]) -> int:
# 长度为1需要特殊处理一下否则后面会出现越界的问题
if len(nums) == 1: return 0
# count用来记录偶数位置与奇数位置的出现次数, count[i]属于一个字典
count = [dict() for i in range(5)]
for i in range(len(nums)):
x = nums[i]
# count[0]记录偶数位置, count[1]记录奇数位置
if x not in count[i & 1]:
count[i & 1][x] = 1
else:
count[i & 1][x] += 1
# 使用sorted函数对字典从大到小排序, 最终返回类型是列表类型, 类型中的每一个元素是元组类型
a, b = sorted(count[0].items(), key=lambda x: x[1], reverse=True), sorted(count[1].items(), key=lambda x: x[1], reverse=True)
a1, a2, b1, b2 = a[0][1], 0, b[0][1], 0
if len(a) > 1: a2 = a[1][1]
if len(b) > 1: b2 = b[1][1]
if a[0][0] != b[0][0]:
return len(nums) - a1 - b1
# 使用数组总长度将去替换情况的较大值那么剩余的操作次数肯定最少
return len(nums) - max(a1 + b2, a2 + b1)