我的LeetCode刷题源码[GitHub]:https://github.com/izhoujie/Algorithmcii
LeetCode 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 操作是不能让数组的每个值唯一的。
提示: 0 <= A.length <= 40000 0 <= A[i] < 40000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-increment-to-make-array-unique
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
思路1-先排序,再从左向右累加每两个临近数需要的+1操作数;
- Arrays.sort(A)排序;
- 顺次比较并记录将后一个数变为前一个数+1数所需要的操作数;
例子:假如排序后是1123455那么从第二个数开始:
- 第一次:1223455 此时move+=2-1
- 第二次:1233455 此时move+=3-2
- 第三次:1234455 此时move+=4-3
- 第四次:1234555 无需操作
- 第五次:1234565 此时move+=6-5
- 第六次:1234567 此时move+=7-6
其中: - 时间复杂度:O(NlogN) N=A.length
- 空间复杂度:O(1)
思路2-先统计顺次进行操作数的累加,每次需要累加+1操作的次数是相同数的个数;
- 创建新数组new int[40001],然后将A中每个数作为下标进行统计;
- 遍历新数组,1中统计到的相同数大于0的,其-1后的数就是这些数需要进行+1操作的数,并把这些+1操作后的数累加给下一个统计数,通过每次-1来使得最终数都不相同;
- 遍历完后需要再检查一下最大下标数的个数,若大于1,其中-1个数都需要进行+1操作,直接使用1-n的求和公式即可;
- 时间复杂度:O(N) N=max(A.length,max(A))
- 空间复杂度:O(40001)即O(1)
Tips:第一步的统计其实隐含了排序,利用了自然数的特性,下标天然有序是数组很容易被忽略的一个特性,比如字母(通过char的 -'a'操作)转数组去统计就避免了额外排序;
思路3-路径压缩;(来自LeetCode评论区,很秀...)
- 创建新数组new int[80000](初始化值-1),因为路径压缩不同于方法2中的统计(或者说是统计压缩),这里压缩的是+1的操作,但是+1后的数需要新数组去记录,若A中所有值都是39999,最后的最大数将是79999;
- 开始遍历并进行路径点位记录findPath,这是个递归方法,可能比较绕,单独分析下:
private int findPath(int i) { // 初次遇到点位,记录值并返回,此时j=0 if (path[i] == -1) { path[i] = i; return i; } else { // 若i有记录,则向后找path[i] + 1位置的值,并最终递归更新路径值 path[i] = findPath(path[i] + 1); return path[i]; } }
对于例子:A{1,1,2,3,5,5,2},对应的路径数组初始化的路径值均为-1
- 0下标1的路径值为-1,执行后更新为1并返回1,此时move+=1-1,对应1不需要+1操作;
- 1下标1的路径值因为已经被标记为1了,所以往后找1+1的路径值,此时找到的2的路径值为-1,更新路径1和2的值都为2并返回,最后move+=2-1,对应1需要1次+1操作;
- 2下标2的路径值为2,往后找2+1的路径值得到-1,此时将路径1,2,3的路径值都更新为3,最后move+=3-2,对应2需要1次+1操作;
- 3下标3的路径值为3,往后找3+1的路径值得到-1,此时将路径1,2,3,4的路径值都更新为4,最后move+=4-3,对应3需要1次+1操作;
- 4下标5的路径值为-1,执行后更新为5并返回5,此时move+=5-5,对应5不需要+1操作;
- 5下标5的路径值为5,往后找5+1的路径值得到-1,此时将路径1,2,3,4,5,6的路径值都更新为6,最后move+=6-5,对应5需要1次+1操作;
- 6下标2的路径值为6,往后找6+1的路径值得到-1,此时将路径1,2,3,4,5,6,7的路径值都更新为7,最后move+=7-2,对应2需要5次+1操作;
所谓的路径压缩其实是记录了当前已遍历数经过+1操作后中的最大数,便于后面根据路径直接找到最大数并在此基础上计算需要+1的次数
算法源码示例
package leetcode; import java.util.Arrays; /** * @author ZhouJie * @date 2020年3月22日 下午8:04:55 * @Description: 945. 使数组唯一的最小增量 * */ public class LeetCode_0945 { } class Solution_0945 { /** * @author: ZhouJie * @date: 2020年3月22日 下午8:08:06 * @param: @param A * @param: @return * @return: int * @Description: 1-先排序,再从左向右累加每两个临近数需要的+1操作数; * 时间复杂度:O(NlogN) N=A.length * 空间复杂度:O(1) * */ public int minIncrementForUnique_1(int[] A) { int len = 0, move = 0; if (A == null || (len = A.length) < 2) { return move; } Arrays.sort(A); for (int i = 1; i < len; i++) { // 若当前值小于等于前一个值,说明需要进行+1操作,+1操作的次数就等于差值再+1,此外还需要更新当前值为前一个值+1 if (A[i] <= A[i - 1]) { move += A[i - 1] - A[i] + 1; A[i] = A[i - 1] + 1; } } return move; } /** * @author: ZhouJie * @date: 2020年3月22日 下午8:15:17 * @param: @param A * @param: @return * @return: int * @Description: 2-先统计再由小到大进行操作数的累加,每次需要累加+1次数的是相同数的个数-1; * 其实统计这一步隐含了排序(自然数的特性),这也是比1方法快的关键原因 * 时间复杂度:O(N) N=max(A.length,max(A)) * 空间复杂度:O(40001)即O(1) */ public int minIncrementForUnique_2(int[] A) { int move = 0; if (A == null || A.length < 2) { return move; } // 因为最大数是3999,若+1为40000,需要用到40000索引 int[] statistics = new int[40001]; // 记录最大数,用作遍历statistics的右边界 int max = 0; for (int i : A) { statistics[i]++; max = Math.max(max, i); } max++; // max是A中最终可能的最大值 for (int i = 0; i < max; i++) { // 若A中statistics[i]的个数大于1,说明statistics[i]-1个数需要进行+1操作, // 这一步只是给statistics[i]-1个数各进行了一次+1操作,后续的+1交给statistics[i+1]去完成(递归) if (statistics[i] > 1) { move += statistics[i] - 1; statistics[i + 1] += statistics[i] - 1; } } // 若statistics[max]的个数大于1,则statistics[max]-1个数(记为n个)需要进行+1操作; // 这n个数依次需要进行+1的次数为1、2、3、4....n,即对1-n求和,直接使用求和公式 if (statistics[max] > 1) { int n = statistics[max] - 1; move += n * (n + 1) / 2; } return move; } /** * @author: ZhouJie * @date: 2020年3月22日 下午8:36:13 * @param: @param A * @param: @return * @return: int * @Description: 1-路径压缩;(来自LeetCode评论区,很秀...) * 时间复杂度:O(N) N=A.length * 空间复杂度:O(80000)即O(1) * 因为findPath每次可能更改多个点位的值,所以效率没有方法2高 */ // 若A中所有数都相等且都为39999,则+1操作完成时,最大值将为79999 int[] path = new int[80000]; public int minIncrementForUnique_3(int[] A) { int move = 0; if (A == null || A.length < 2) { return move; } // -1为空地址标记,与A中数不同即可 Arrays.fill(path, -1); for (int i : A) { int j = findPath(i); move += j - i; } return move; } /** * @author: ZhouJie * @date: 2020年3月22日 下午8:49:55 * @param: @param i * @param: @return * @return: int * @Description: 路径压缩核心 * */ private int findPath(int i) { // 初次遇到点位,记录值并返回,此时j=0 if (path[i] == -1) { path[i] = i; return i; } else { // 若i有记录,则向后找path[i] + 1位置的值,并最终递归更新路径值 path[i] = findPath(path[i] + 1); return path[i]; } } }
来源:https://www.cnblogs.com/izhoujie/p/12548529.html