如果用一个哈希表对数字进行计数,之后统计出现次数为0的数字是很容易的,但是如何用O(n)时间复杂度且不使用额外空间来解决此题呢?
提示里说利用输入数组完成,还是没想到怎么做,于是看了官解。
由于nums的数字范围都在[1,n]中,我们可以利用这一范围之外的数字,来表示该数是够存在。
具体做法就是遍历nums,每遇到一个数字x,就让nums[x-1]增加n,最后找谁没大于n,就是没存在过。
至于为什么让x-1下标+n而不是x下标呢?因为nums数组中数组范围在1~n,而数字%n的余数范围在0~n-1,所以需要让下标映射上
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer>res=new ArrayList<>();
int n=nums.length;
for(int i=0;i<n;i++){
int x=(nums[i]-1)%n;
nums[x]+=n;
}
for(int i=0;i<n;i++){
if(nums[i]<=n)
res.add(i+1);
}
return res;
}
}
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)
和上一题类似,如果用一个哈希表对数字进行计数,之后统计出现次数为0的数字是很容易的,但是如何不修改数组 nums且不使用额外空间来解决此题呢?
定义cnt[i] 为nums数组中<=i 的数有多少个,以[1,3,4,2,2]为例,列出每个数字的cnt值:
可以发现cnt是单调递增的,因此我们可以利用二分法来解题,对1~n进行二分法查找,找到cnt[i]>nums[i]的最小 i。
class Solution {
public int findDuplicate(int[] nums) {
int n=nums.length;
int l=1,r=n,res=-1;
//二分法遍历数字1~n
while(l<=r){
int cnt=0,mid=(l+r)/2;
//找小于mid的数
for(int i=0;i<n;i++){
if(nums[i]<=mid)
cnt++;
}
if(cnt<=mid)
l=mid+1;
else{
r=mid-1;
res=mid;
}
}
return res;
}
}
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( 1 ) O(1) O(1)
对nums数组建图,每个位置 i 连一条 i->nums[i]边,如下图所示:
由于存在重复数字,那么这个位置一定至少有两条指向他的边,因此一定存在环,我们要找到的就是环的入口,等价于142. 环形链表 II 问题,使用快慢指针解决。
判断有环?
快指针每次走两步,慢指针每次走一步,能相遇就有环。
入环点在哪?
找到相遇位置后,固定 fast 指针,slow 指针从头结点开始,两个人一步一步走,相遇点即入环点。(可以证明)
class Solution {
public int findDuplicate(int[] nums) {
int n=nums.length;
int slow=0,fast=0;
//slow走一步,fast走两步
do{
slow=nums[slow];
fast=nums[nums[fast]];
}while(slow!=fast);
//找入环点
slow=0;
while(slow!=fast){
slow=nums[slow];
fast=nums[fast];
}
return slow;
}
}
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)