(Java)leetcode-945 Minimum Increment to Make Array Unique(使数组唯一的最小增量)

题目描述

给定整数数组 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 操作是不能让数组的每个值唯一的。

提示:

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

思路1:排序 O(nlogn)

首先将数组进行排序,然后从左到右遍历数组:

  • 如果当前元素大于上一个元素,保持不变;
  • 如果当前元素小于等于上一个元素,就需要增加当前元素,使其大于上一个元素。

时间复杂度:O(nlogn),主要的复杂度在排序上。

class Solution {
    public int minIncrementForUnique(int[] A) {
        // 先排序
        Arrays.sort(A);
        int move = 0;
        // 遍历数组,若当前元素小于等于它的前一个元素,则将其变为前一个数+1
        for (int i = 1; i < A.length; i++) {
            if (A[i] <= A[i - 1]) {
                int pre = A[i];
                A[i] = A[i - 1] + 1;
                move += A[i] - pre;
            }
        }
        return move;
    }
}

执行用时:16 ms, 在所有 Java 提交中击败了67.56%的用户
内存消耗:47.1 MB, 在所有 Java 提交中击败了100.00%的用户

思路2:计数 O(n+k)

上面方法中,排序需要 O(nlogn) 的时间,比较昂贵。我们尝试不进行排序的方法。

例如输入 [3, 2, 1, 2, 1, 7],计数之后有两个 1 和两个 2。我们先看最小的数,两个 1 重复了,需要有一个增加到 2,这样 2 的数量变成了三个。在三个 2 中,又有两个需要增加到 3,然后又出现了两个 3…… 以此类推,可以计算出需要增加的次数。

我们可以用 map来计数。不过既然题目中说明了整数的范围在 0 到 40000 之间,我们不妨直接用一个大小为 40000 的数组做计数。

需要注意的是,虽然整数的范围是 0 到 40000,但是由于整数还会因为增加而变大,超出 40000 的范围。例如极端的情况:所有数都是 39999。所以需要对整数中最大的数单独处理。代码如下:

public int minIncrementForUnique(int[] A) {
    int[] count = new int[40000];
    int max = 0;
    for (int a : A) {
        count[a]++; // 计数
        max = Math.max(max, a); // 计算数组中的最大值
    }
    
    int res = 0;
    for (int j = 0; j < max; j++) {
        if (count[j] > 1) {
            // 有 count[j] - 1 个数需要增加
            res += count[j] - 1; 
            // 增加后这些数将被统计到下一个位置中
            count[j+1] += count[j] - 1;
        }
    }
    
    // count[max] 单独计算,是因为可能超出 40000 的边界
    if (count[max] > 1) {
        int d = count[max] - 1; 
        // 有 d 个数需要增加
        // 分别增加为 max + 1, max + 2, ... max + d
        // 使用等差数列公式求和
        res += (1 + d) * d / 2;
    }
    
    return res;
}


执行用时:4 ms, 在所有 Java 提交中击败了98.67%的用户
内存消耗:46.7 MB, 在所有 Java 提交中击败了100.00%的用户

这种解法的时间复杂度不能简单地写成 O(n)。设 n 为数组元素的个数,k 为数组元素的可能取值个数(本期中 k = 40000),这个算法的时间复杂度是 O(n + k)。

作者:nettee

对比

如果 k 比较小的话,计数方法的时间复杂度可以认为是 O(n),比排序方法要快。这道题 k 取值 40000 算比较小的数,所以用计数方法会很快。如果 k 的值很大,就不能用计数方法。

虽然计数方法不需要排序,但我们可以把它看成是一种计数排序。计数排序的时间复杂度恰好也是 O(n + k)。所以实际上两种方法都是用的排序,只不过一个是普通排序,一个是计数排序。我们在算法书中学过,计数排序这种排序方法是非比较排序,可以突破 O(nlogn) 的复杂度下限。但是它会对输入的性质有所要求,例如要求 k 比较小。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值