原题:https://leetcode.com/problems/set-mismatch/
要注意的是给出的数组是不一定排序的,如果用普通的hashmap这样的做法,当然极容易做,扫描一遍,把统计出来的次数为2和为0的数字返回即可。这样时间复杂度当然是O(n)并且,也不太可能再降低了,但是这样的空间复杂度是O(n),有没有更好的方案降低空间复杂度呢?
我的习惯依然是在图上画画,并且从比较简单的case开始,以求反应本质:例如从
1,2,3,4变成
1,2,2,4的情况
这种操作可以看成是把而向右(假定上述数字是排了序的)复制2并覆盖了3,其结果是使得这个数组的总和少了1(因为有个3变成了2)
如果发生的是向左复制并覆盖,例如变成了
2,2,3,4
则数组总和是增大了的,(因为有个1变成了2)
注意到题干是说顺序不保证,但是其实没有关系,顺序不影响总和。
所以其实我们可以在意念中认为存在那么一个对应的拍好序的数组,从总和前后的变化我们可以了解到是发生了向左覆盖还是向右覆盖,并且总和变化的绝对值可以告诉我们向左或者向右覆盖发生在下标距离多远的两个位置之间,例如上述变化从2到3或者从2到1,总和之之差都是1,所以我们可以知道这种覆盖的源和目标下标之差为1,就称之为“步长”吧,知道了步长,则任意知道其中一个点,就可以知道另一个点,是不是这个道理?
所以下一步我们就来求一个点即可,哪个点比较好球呢?missing的数字比较好求,现在给你个数组说是质保函1~N的整数,并且miss了一个?咋办?用位运算就很简单,整个数组过一遍,设置位,为0的那个位对应的即为missing的数字。
知道了missing数字,步长和方向,duplicate的数字自然就可以推算出来。
因为用了bitmap,所以整个上述操作比用map更省空间。代码如下:
package com.example.demo.leetcode;
import java.util.BitSet;
public class SetMisMatch {
/**
* sample Input: nums = [1,2,2,4]
* Sample Output: [2,3]
*
* @param nums
* @return
*/
public int[] findErrorNums(int[] nums) {
BitSet bs = new BitSet();
// 0: duplicate num, 1: miss
int[] ret = new int[2];
// step 1: find the missing number
for(int i=0;i<nums.length;i++){
bs.set(nums[i]);
}
int missNum = 0;
for(int j=1;j<=nums.length;j++){
if(!bs.get(j)){
missNum = j;
break;
}
}
// step 2: find the replaced number
int subTotal = 0;
for(int z=1;z<=nums.length;z++){
subTotal+=(nums[z-1]-z);
}
ret[1] = missNum;
ret[0] = missNum+subTotal;
return ret;
}
public static void main(String[] args) {
// edge case 1: empty array
// edage case 2: single arr
int[] nums = {1,2,2,4};
SetMisMatch demo = new SetMisMatch();
int[] ret = demo.findErrorNums(nums);
System.out.println(ret[0 ]);
System.out.println(ret[1]);
}
}
网上还看到一种奇技淫巧的做法,用数组符号来表示缺失的数字,可以参考,但不推荐奇技淫巧,对迅速产生思维无溢
https://labuladong.gitbook.io/algo/gao-pin-mian-shi-xi-lie/que-shi-he-zhong-fu-de-yuan-su