【问题描述】

给定整数数组 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)

先排序,再依次遍历数组元素,若当前元素小于等于它前一个元素,则将其变为前一个数+1。

class Solution {public int minIncrementForUnique(int[] A) {// 先排序Arrays.sort(A);int move = 0;// 遍历数组,若当前元素小于等于它的前一个元素,则将其变为前一个数+1for (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;}}
2. 注意最后一个的推导 计数排序 O(N)
class Solution {public int minIncrementForUnique(int[] A) {// counter数组统计每个数字的个数。//(这里为了防止下面遍历counter的时候每次都走到40000,所以设置了一个max,这个数据量不设也行,再额外设置min也行)int[] counter = new int[40001];int max = -1;for (int num: A) {counter[num]++;max = Math.max(max, num);}// 遍历counter数组,若当前数字的个数cnt大于1个,则只留下1个,其他的cnt-1个后移int move = 0;for (int num = 0; num <= max; num++) {if (counter[num] > 1) {int d = counter[num] - 1;move += d;counter[num + 1] += d;}}// 最后, counter[max+1]里可能会有从counter[max]后移过来的,counter[max+1]里只留下1个,其它的d个后移。// 设 max+1 = x,那么后面的d个数就是[x+1,x+2,x+3,...,x+d],// 因此操作次数是[1,2,3,...,d],用求和公式求和。int d = counter[max + 1] - 1;move += (1 + d) * d / 2;return move;}
}
3. 线性探测法(含路径压缩) O(N) 神仙解法
  • 把原数组映射到一个地址不冲突的区域 ,和解决hash冲突的线性探测法比较相似
  • 直接线性探测可能会由于冲突导致反复探测耗时太长 -> 考虑探测的过程中进行路径压缩
  • 经过某条路径最终探测到一个空位置x后,将这条路径上的值都变成空位置所在的下标x,那么假如下次探测的点又是这条路径上的点,则可以直接跳转到这次探测到的空位置x,从x开始继续探测。

下面用样例2:[3, 2, 1, 2, 1, 7],来模拟一遍线性探测的过程。

模拟的过程中用int move来记录操作数(即要求的增量数)。

step1: 插入3:

因为3的位置是空的,所以直接放入3即可。(此时数组变成了上图,红色表示本次的更改)

move = 0 保持不变;

step2: 插入2:

因为2的位置是空的,所以直接放入2即可。(此时数组变成了上图,红色表示本次的更改)

move = 0 保持不变;

step3: 插入1:

因为1的位置是空的,所以直接放入1即可。(此时数组变成了上图,红色表示本次的更改)

move = 0 保持不变;

step4: 插入2:

此时我们发现2的位置已经有值了,于是继续向后探测,直到找到空位4,于是2映射到了4。

⚠️并且!!我们要对刚刚走过的路径2->3->4进行压缩,即将他们的值都设置为本次探测到的空位4(那么下次探测就可以直接从4往后找了~~)。

(此时数组变成了上图,红色表示本次的更改)

move = move + 4 - 2 = 2;

step5: 插入1:

此时我们发现1的位置已经有值了,于是向后探测,探测到了2,发现2的位置也有值了,但是由于2在上次的过程中存了上次的空位4,所以我们直接跳转到4+1即从5开始探测就行了(而不需要重复走一遍2->3->4这条路径喽!),此时我们发现5是个空位,因此将1映射到5,并且对刚刚走过的路径1->2->5进行路径压缩 即 使其都映射到5!

(此时数组变成了上图,红色表示本次的更改)

move = move + 5 - 1 = 6;

step6: 插入7:

因为7的位置是空的,所以直接放入7即可。(此时数组变成了上图,红色表示本次的更改)

move = 6 保持不变;

以上,最终move为6。

class Solution {int[] pos = new int [80000];public int minIncrementForUnique(int[] A) {Arrays.fill(pos, -1); // -1表示空位int move = 0;// 遍历每个数字a对其寻地址得到位置b, b比a的增量就是操作数。for (int a: A) {int b = findPos(a); move += b - a;}return move;}// 线性探测寻址(含路径压缩)private int findPos(int a) {int b = pos[a];// 如果a对应的位置pos[a]是空位,直接放入即可。if (b == -1) { pos[a] = a;return a;}// 否则向后寻址// 因为pos[a]中标记了上次寻址得到的空位,因此从pos[a]+1开始寻址就行了(不需要从a+1开始)。b = findPos(b + 1); //递归pos[a] = b; // ⚠️寻址后的新空位要重新赋值给pos[a]哦,路径压缩就是体现在这里。return b;}
}
4. 贪心算法 时间复杂度:O(Nlog N) 空间复杂度:O(1)
 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 + 1res += (preNum + 1 - A[i]);preNum++;}}return res;}

【总结】

  1. 思维过于局限 ,统计相同的数字分别+1,使用两层循环导致超时。
  2. 思维不跳跃,没有整体把握,第二种方法和第三种方法看了半天。

转载来自: https://leetcode-cn.com/problems/minimum-increment-to-make-array-unique/solution/ji-shu-onxian-xing-tan-ce-fa-onpai-xu-onlogn-yi-ya/

[Leedcode][JAVA]第[945]题相关推荐

  1. [Leedcode][JAVA][第105题][从前序与中序遍历序列构造二叉树][栈][递归][二叉树]

    [问题描述][中等] 根据一棵树的前序遍历与中序遍历构造二叉树.注意: 你可以假设树中没有重复的元素.例如,给出前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = ...

  2. [Leedcode][JAVA][第470题][Ran7()实现Rand10()]

    [问题描述][Leedcode][JAVA][第470题][Ran7()实现Rand10()] 已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 ...

  3. [Leedcode][JAVA][第45题][跳跃游戏 II][贪心算法]

    [问题描述][Leedcode][JAVA][第45题][跳跃游戏 II] 输入: [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2.从下标为 0 跳到下标为 1 的位置 ...

  4. [Leedcode][JAVA][第300题][最长上上子序列][动态规划][压缩空间]

    [问题描述][中等] 给定一个无序的整数数组,找到其中最长上升子序列的长度.示例:输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它 ...

  5. [Leedcode][JAVA][第680题][验证回文字符串Ⅱ][贪心][递归]

    [问题描述][第680题][验证回文字符串Ⅱ][简单] 给定一个非空字符串 s,最多删除一个字符.判断是否能成为回文字符串.示例 1:输入: "aba" 输出: True 示例 2 ...

  6. [Leedcode][JAVA][第210 题][课程表 II][拓扑排序][BFS][DFS][有向图]

    [问题描述][第210 题][课程表 II][中等] 现在你总共有 n 门课需要选,记为 0 到 n-1.在选修某些课程之前需要一些先修课程. 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用 ...

  7. [Leedcode][JAVA][第25题][K个一组反转链表][链表][递归]

    [问题描述][第25题][K个一组反转链表][困难] 时间复杂度:O(N^2) 空间复杂度:O(1) ```java 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表.k 是一个正整数, ...

  8. [Leedcode][JAVA][第560题][和为K的子数组][Hashmap][数组]

    [问题描述][第560题][和为K的子数组][中等] 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数.示例 1 :输入:nums = [1,1,1], k = 2 输 ...

  9. [Leedcode][JAVA][第102题][二叉树的层序遍历][递归][迭代][BFS]

    [问题描述][第102题][二叉树的层序遍历][中等] 给你一个二叉树,请你返回其按 层序遍历 得到的节点值. (即逐层地,从左到右访问所有节点).示例: 二叉树:[3,9,20,null,null, ...

最新文章

  1. 最新的ES 5.0路由算法底层实现
  2. 集成ACEGI 进行权限控制
  3. IOS-input元素光标偏移乱跑,是什么原因
  4. android sha1是签名么,Android获取SHA1和MD5签名
  5. 缕一缕c#可null类型
  6. 生态和能力是国内自研操作系统发展的关键
  7. 防火墙配置十大任务之十,构建虚拟防火墙
  8. smith标准型_线性系统理论(八)多项式矩阵Smith-McMillan标准型计算方法
  9. 机器学习面试问题大概梳理(转)
  10. VUE axios发送cookie
  11. Brush、Color、String相互转换
  12. Django APIView源码解析
  13. 16qam matlab 误码率,16qam的误码率公式
  14. 计算机程序员求职信英语作文,电脑程序员英文求职信
  15. springboot集成easypoi实现excel多sheet导出,并设置表头样式
  16. 基于链表和禁忌搜索启发式算法实现非一刀切二维矩形排样算法
  17. python判断字符串是字母 数字 大小写
  18. OSChina 周六乱弹 ——手机进化史?程序员用啥手机?
  19. android 多用户卸载,多用户方案实现Android免ROOT卸载预装软件的思路
  20. 网站白名单,ip白名单

热门文章

  1. 【APIO2015】完跪记
  2. Error applying BeanValidation relational constraints错误的解决
  3. 各种推荐资料汇总。。。
  4. jQuery选择元素
  5. 使用vue 刷新页面后state数据被清空的问题(刷新总是跳转到登陆页面)
  6. 面试必备:HashMap底层数据结构?jdk1.8算法优化,hash冲突,扩容等问题
  7. Android 人脸实名验证demo——腾讯人脸核身·云智慧眼
  8. 如何手动修改oracle表空间,ORACLE数据库创建和修改表空间
  9. java socket 浏览器_Socket实现Java和浏览器交互。
  10. 异常(Exception )