「贪心算法」问题选讲-3(自己的草稿,内容不严谨,不用看)

这一节我们再来看一个使用贪心算法解决的问题。

这道题是「力扣」第 945 题:使数组唯一的最小增量。

给定整数数组 A,每次 move 操作将会选择任意 A[i],并将其递增 1

返回使 A 中的每个值都是唯一的最少操作次数。

示例 1:

输入:[1,2,2]
输出:1
解释:经过一次 move 操作,数组将变为 [1, 2, 3]。

示例 2:

输入:[3,2,1,2,1,7]
输出:6
解释:经过 6 次 move 操作,数组将变为 [3, 4, 1, 2, 5, 7]。
可以看出 5 次或 5 次以下的 move 操作是不能让数组的每个值唯一的。

提示:

  1. 0 <= A.length <= 40000
  2. 0 <= A[i] < 40000

分析:

题目说:

消除重复元素的办法,只能自增。

那么在这个数组里,最小的那个元素(如果唯一的话),一定不需要自增。例如:示例 1 最小元素 1

同理,最大的那个元素(如果唯一的话),也不需要自增。例如:示例 2 最大元素 7(这句话回过头来看是不严谨的,但是不妨碍我们找到求解这个问题的思路)。

题目又说:

返回使 A 中的每个值都是唯一的最少操作次数。

这句话告诉我们对于相同的数,自增 1 的操作次数是有上限的,加到一定程度就没有必要再加了。

因此,对原始数组进行排序,大概可以解决这个问题。我们看示例 2。

这个算法每一步只用看当前的情况,当前最优,全局就最优,所以是「贪心算法」。证明贪心算法主要有 2 个途径:一是数学归纳法,二是反证法。

这里用反证法相对容易。

「贪心算法」最优性证明:

假设当前算法的决策不是最优解。
现在假设一个新算法得到的解是最优解,这个新算法是:

  • 如果新算法是:修改原始算法的第 1 个步骤,将 move 数 - 1甚至减去更大的数,其它步骤不变,肯定不符合要求,因为题目说了,只能增加。

  • 那么这个新算法就只能在原始算法的第 1 个步骤里,再多增加 1,由于这个问题的特点,之前的数多了 1,它会影响到后面所有的阶段的决策。

例如:原来被 2 占的位置,现在被 3 占用了,那么原来 3 占用的位置,就得换成 4。

由于这种连锁的效应,这个新算法一定不会比原始算法得到的解更好,与最开始的假设新算法的最优性矛盾。因此原始算法是最优解。

参考代码 1

import java.util.Arrays;

public class Solution {

    public int minIncrementForUnique(int[] A) {
        int len = A.length;
        if (len == 0) {
            return 0;
        }

        Arrays.sort(A);
        // 打开调试
        // System.out.println(Arrays.toString(A));

        int preNum = A[0];
        int res = 0;
        for (int i = 1; i < len; i++) {
            // preNum + 1 表示当前数「最好」是这个值

            if (A[i] == preNum + 1) {
                preNum = A[i];
            } else if (A[i] > preNum + 1) {
                // 当前这个数已经足够大,这种情况可以合并到上一个分支
                preNum = A[i];
            } else {
                // A[i] < preNum + 1
                res += (preNum + 1 - A[i]);
                preNum++;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] A = new int[]{1, 2, 2};
        // int[] A = new int[]{3, 2, 1, 2, 1, 7};
        int res = solution.minIncrementForUnique(A);
        System.out.println(res);
    }
}

复杂度分析:

  • 时间复杂度: O ( N log ⁡ N ) O(N\log N) O(NlogN),这里 N N N 是数组的长度。主要消耗在排序算法上。
  • 空间复杂度: O ( 1 ) O(1) O(1)

我们已经和大家讲解了「贪心算法」的 3 个问题,依然是要和大家再强调一下,如果只是应付平时的面试和笔试,是没有必要去把「贪心算法」的证明弄清楚。只需要有一个感性的认识。

我们学习过的「选择排序」其实就应用了「贪心算法」,在基础算法领域的「选择排序」、「合并 K 个链表」、「哈夫曼编码(Huffman Coding)」、「最小生成树」问题都是经典的应用「贪心算法」解决的问题。

学习「贪心算法」,还是重在使用上,在使用的过程中去理解「局部最优,整体最优」的思想,通过「贪心」的策略,又快又好地解决这问题。

同时还是要特别强调一点,贪心是有前提的,贪心虽然有时候看起来显而易见,但不是每个问题都能用。

练习

1、「力扣」第 23 题:合并 K 个排序链表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值