题目描述
给你一个整数数组 nums ,你的目标是令 nums 中的所有元素相等。完成一次减少操作需要遵照下面的几个步骤:
找出 nums 中的 最大 值。记这个值为 largest 并取其下标 i (下标从 0 开始计数)。如果有多个元素都是最大值,则取最小的 i 。
找出 nums 中的 下一个最大 值,这个值 严格小于 largest ,记为 nextLargest 。
将 nums[i] 减少到 nextLargest 。
返回使 nums 中的所有元素相等的操作次数。(提示:1 <= nums.length <= 5 * 104;
1 <= nums[i] <= 5 * 104)
示例 1:
输入:nums = [5,1,3]
输出:3
解释:需要 3 次操作使 nums 中的所有元素相等:
- largest = 5 下标为 0 。nextLargest = 3 。将 nums[0] 减少到 3 。nums = [3,1,3] 。
- largest = 3 下标为 0 。nextLargest = 1 。将 nums[0] 减少到 1 。nums = [1,1,3] 。
- largest = 3 下标为 2 。nextLargest = 1 。将 nums[2] 减少到 1 。nums = [1,1,1] 。
示例 2:
输入:nums = [1,1,1]
输出:0
解释:nums 中的所有元素已经是相等的。
示例 3:
输入:nums = [1,1,2,2,3]
输出:4
解释:需要 4 次操作使 nums 中的所有元素相等:
- largest = 3 下标为 4 。nextLargest = 2 。将 nums[4] 减少到 2 。nums = [1,1,2,2,2] 。
- largest = 2 下标为 2 。nextLargest = 1 。将 nums[2] 减少到 1 。nums = [1,1,1,2,2] 。
- largest = 2 下标为 3 。nextLargest = 1 。将 nums[3] 减少到 1 。nums = [1,1,1,1,2] 。
- largest = 2 下标为 4 。nextLargest = 1 。将 nums[4] 减少到 1 。nums = [1,1,1,1,1] 。
题目来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reduction-operations-to-make-the-array-elements-equal
解题思路一:暴力解法
用map统计数组各数字的个数(数字,数字个数)
然后将MAP的key排序,计算出层级N,
计算每一层次数:规律(N-1) * 当前层的个数,所有层的次数相加就是全部
代码
public int reductionOperations(int[] nums) {
/**
* 用map统计数组各数字的个数(数字,数字个数)
* 然后排序,计算出层级N,
* 计算每一层次数:(N-1) * 当前层的个数,所有层的次数相加就是全部
*/
//统计数组的个数的map
HashMap<Integer, Integer> countMap = new HashMap<Integer, Integer>();
for (int n : nums) {
if (countMap.get(n) == null) {
countMap.put(n, 1);
} else {
countMap.put(n, countMap.get(n) + 1);
}
}
if (countMap.size() > 1) {
//排序key,得出每个key所属层级
List<Integer> sort = new ArrayList<>();
sort.addAll(countMap.keySet());
Collections.sort(sort);
//遍历获取每一层级的次数
int total = 0;
for (int i = 1; i < sort.size(); i++) {
//i * countMap.get(sort.get(i)) 是当前层需要操作的次数
total += i * countMap.get(sort.get(i));
}
return total;
}
return 0;
}
解题思路二:排序解法
/**
* 先排序
*
* 每二层的所有数降到第一层操作次数都是1
* 每三层的所有数降到第一层操作次数都是 2,全部次数是二层全部数2相加
* 每N层的所有数降到第一层操作次数都是 N-1,全部次数是N层全部数N-1相加
* 如5,3, 5, 3, 1 ,6 排序后就是 1,3,3,5,5,6
* 1、1已经是最低了,没有更低的不需要降,因此循环条件下标从1开始,第二层
* 2、每二层的所有数降到第一层操作次数都是1, 即第一个3一次(差值diff =1),第2个3一次 diff =1
* 3、每三层的所有数降到第一层操作次数都是 2,第一个5降到1需要2次(diff =diff+1),第2个5降到1需要2次(diff 不变)
* 3、每四层的所有数降到第一层操作次数都是 3,6降到1需要3次(diff =diff+1)
* 因此得出规律,每个元素的操作次数每次都是排序后数组与前一个数不同时+1,否则与前一个元素的次数相同,所有的操作次数相加就是全部次数
*/
来源:力扣
public int reductionOperations2(int[] nums) {
//排序
Arrays.sort(nums);
//每个元素的操作次数
int diff = 0;
//总的操作次数
int count = 0;
for (int i =1; i < nums.length; i++) {
//重复的元素diff不变
if (nums[i] != nums[i - 1]) {
//前一个数不同+1
diff++;
}
count+=diff;
}
return count;
}
解题思路三:排序解法
/**
* 先排序
* 每一层降到下一层操作次数:就是此层级个数 = length-最后一条不重复的下标
* 所有层的次数相加就是全部
*
* 如5,3, 5, 3, 1 排序后就是 1,3,3,5,5
* 1、最高层的5降到3,需要2次, 即第一个5一次,第2个5一次,次数正好是 数组长度减去倒数第二个5的下标
* 2、3降到1,需要4次(因为前面的5已经降到3了),次数是数组长度减去倒数第4个3的下标
* 3、1已经是最低了,没有更低的不需要降,因此循环条件下标>0
*/
来源:成都R哥/王总/R神
public int reductionOperations3(int[] nums) {
//排序
Arrays.sort(nums);
int count = 0;
for (int i = nums.length - 1; i > 0; i--) {
//重复的元素跳过计算
if (nums[i] != nums[i - 1]) {
//数组长度-重复元素的倒数最后一个下标
count += (nums.length) - i;
}
}
return count;
}
解题思路四:原数组不排序做法
思路:使用freq数组记录原数组每个数字出现个数(nums数组的具体数是freq的下标,个数是值),然后从后往前遍历freq数组,对于每个出现次数大于1的数字,往前面找比当前数字小的,然后在结果次数上加上当前数字出现的频率,且把比当前数字小的数字频率也加上当前数字出现的频率,直到末尾。( 这里有个优化的点,在每次找到比当前数字小的数字后,可以把外层扫描下标直接定位到比当前数字小的数字下标处。)
来源:大佬别哥/别神
public int reductionOperations4(int[] nums) {
int[] freq = new int[50001];
//操作次数
int res = 0;
//数组遍历,记录个数
for (int num : nums) {
//数组的具体值是freq的下标,个数是值
freq[num] += 1;
}
int i = freq.length - 1;
out:
for (; i >= 0; i--) {
if (freq[i] != 0) {
inner:
//找比i更小的下标
for (int j = i - 1; j >= 0; j--) {
if (freq[j] != 0) {
//每次原数组的数字i降到j的操作次数是全部i的个数,即freq[i]的值 操作次数+freq[i]
res += freq[i];
//i的操作次数已经统计,i相当于已经全部降为j,j的个数加上i的
freq[j] += freq[i];
i = j + 1;
//跳出内循环 inner
break inner;
}
}
}
}
return res;
}
}