• LeetCode 945. 使数组唯一的最小增量


    我的LeetCode:https://leetcode-cn.com/u/ituring/

    我的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操作数;

    1. Arrays.sort(A)排序;
    2. 顺次比较并记录将后一个数变为前一个数+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操作的次数是相同数的个数;

    1. 创建新数组new int[40001],然后将A中每个数作为下标进行统计;
    2. 遍历新数组,1中统计到的相同数大于0的,其-1后的数就是这些数需要进行+1操作的数,并把这些+1操作后的数累加给下一个统计数,通过每次-1来使得最终数都不相同;
    3. 遍历完后需要再检查一下最大下标数的个数,若大于1,其中-1个数都需要进行+1操作,直接使用1-n的求和公式即可;
    • 时间复杂度:O(N) N=max(A.length,max(A))
    • 空间复杂度:O(40001)即O(1)

    Tips:第一步的统计其实隐含了排序,利用了自然数的特性,下标天然有序是数组很容易被忽略的一个特性,比如字母(通过char的 -'a'操作)转数组去统计就避免了额外排序;

    思路3-路径压缩;(来自LeetCode评论区,很秀...)

    1. 创建新数组new int[80000](初始化值-1),因为路径压缩不同于方法2中的统计(或者说是统计压缩),这里压缩的是+1的操作,但是+1后的数需要新数组去记录,若A中所有值都是39999,最后的最大数将是79999;
    2. 开始遍历并进行路径点位记录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];
    		}
    	}
    }
    
    
  • 相关阅读:
    如何用nginx将vue部署到本地
    数组中引用类型的去重
    关于element 上传文件el-upload
    ----vue3.0 如何拿到 有ref属性的元素;
    关于 el-form rules校验
    x-www-form-urlencoded 传参
    哈哈 v-model 传递参数给子组件
    记 el-tabs el-tab-pane 中嵌套 router-view出现的问题
    nginx跨域
    css3---flex三剑客
  • 原文地址:https://www.cnblogs.com/izhoujie/p/12548529.html
Copyright © 2020-2023  润新知