【做题笔记】1887. 使数组元素相等的减少操作次数(4种解法)

本文介绍了一种算法问题,目标是通过一系列操作使数组中的所有元素相等,并详细解析了四种不同的解题思路及其对应的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

给你一个整数数组 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 中的所有元素相等:

  1. largest = 5 下标为 0 。nextLargest = 3 。将 nums[0] 减少到 3 。nums = [3,1,3] 。
  2. largest = 3 下标为 0 。nextLargest = 1 。将 nums[0] 减少到 1 。nums = [1,1,3] 。
  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 中的所有元素相等:

  1. largest = 3 下标为 4 。nextLargest = 2 。将 nums[4] 减少到 2 。nums = [1,1,2,2,2] 。
  2. largest = 2 下标为 2 。nextLargest = 1 。将 nums[2] 减少到 1 。nums = [1,1,1,2,2] 。
  3. largest = 2 下标为 3 。nextLargest = 1 。将 nums[3] 减少到 1 。nums = [1,1,1,1,2] 。
  4. 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;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值