前言
记录 LeetCode 刷题时遇到的 原地哈希算法 相关题目
41.缺失的第一个正数
学到了一种新的思想:原地哈希,简单点说就是在原数组上进行哈希操作,从而做到 O(1) 的空间复杂度下也能实现哈希
对于这道题,设 nums 数组的长度为 len,所以它最多能包含 len 个数,当它包含了 [1,len] 时,缺失的第一个正数就是 len + 1;否则 [1,len] 中有一个数没有包含在数组中的话,那它 缺失的第一个正数一定是 [1,len] 范围内的,根据这一点就可以进行原地哈希:
让数组中出现的每一个数 i 交换到 nums[i - 1] 的位置上,即如果数组中有出现 1,就让他到下标 0 的位置上…有出现 len,就让他到 len - 1 的位置上,遍历一遍将所有元素处理完后,再遍历一遍数组,第一个满足 nums[index] != index + 1 的下标 index 就是对应于缺失的第一个正数,即 index + 1
class Solution {
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for(int i = 0;i < len;i++){
//将当前的nums[i]交换到正确的位置上时,可能新的nums[i]不在正确的位置上,
//如[-1,4,3,1],遍历到4时,要把4交换到下标3的位置,即得到[-1,1,3,4],
//此时交换过来的1应该交换到下标0的位置,所以要继续交换得到[1,-1,3,4]
//继续往后遍历到3跟4就在正确的位置上,然后再遍历一遍数组,找到第一个下标i上nums[i] != i + 1的,返回i + 1即可
while(nums[i] >=1 && nums[i] <= len && nums[nums[i] - 1] != nums[i]){
swap(nums,nums[i] - 1,i);
}
}
for(int i = 0;i < len;i++){
if(nums[i] != i + 1){
return i + 1;
}
}
return len + 1;
}
void swap(int[] nums,int i,int j){
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
}
442. 数组中重复的数据
看到空间复杂度才想到原地哈希的做法:首先,哈希表要满足的映射关系是 nums[nums[i] - 1] = nums[i],即元素 1 要在下标 0 的位置上,元素 2 要在下标 1 的位置上,…,以此类推,元素 n 要在下标 n - 1 的位置上
整个算法的思路是,遍历数组每个元素 nums[i],那么它应该放的位置是下标 nums[i] - 1 的位置,如果这个位置放的元素不等于 nums[i] (因为会有重复元素),那么就把 nums[i] 跟其交换,直到 nums[i] - 1 的位置上放的元素的值就等于 nums[i],这时还要考虑 nums[i] - 1 是否就等于i,不是的话才说明存在两个相等的元素
为了防止获取到两个相等的元素,每添加一个重复元素就把它的值置为 -1
public List<Integer> findDuplicates(int[] nums) {
int n = nums.length;
int tmp;
List<Integer> res = new ArrayList<Integer>();
for(int i = 0;i < n;i++){
//下面这个循环是确保下标nums[i] - 1的位置上的元素值就等于nums[i]
while(nums[i] != -1 && nums[nums[i] - 1] != nums[i]){
tmp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = tmp;
}
//确保了nums[i] - 1的位置上的元素值等于nums[i],还得nums[i] - 1 != i,才说明是两个重复元素
//否则如果一开始nums[i]就放在i - 1的位置上就会判断错误
if(nums[i] != -1 && nums[i] - 1 != i){
res.add(nums[i]);
//置为-1防止添加了两个重复元素
nums[i] = -1;
}
}
return res;
}
287. 寻找重复数
看到数据都是 [1,n] 的范围下意识就想用原地哈希 (虽然效率不是最高的):
数组大小为 n,数据范围 [1,n],那么我们可以把 1 放到下标 0 的位置,2 放到下标 1 的位置,以此类推。遍历数组元素 nums[i],尝试循环把 nums[i] 放到下标 nums[i] - 1 的位置,即跟 nums[i] - 1 上的数交换,直到 i 上放置的就是数值 i + 1。不过有可能遇到重复数,即下标 nums[i] - 1 上的数就等于 nums[i],因此循环需要一个终止条件,即 nums[nums[i] - 1] == nums[i]。当循环终止后,我们就可以借助这种情况来判断是不是遇到了重复数
class Solution {
public int findDuplicate(int[] nums) {
for(int i = 0;i < nums.length;i++){
while(nums[i] - 1 != i && nums[nums[i] - 1] != nums[i]){
swap(nums,i,nums[i] - 1);
}
//判断是不是遇到了重复数,是的话返回
if(nums[i] - 1 != i && nums[nums[i] - 1] == nums[i]){
return nums[i];
}
}
return -1;
}
public void swap(int[] nums,int i,int j){
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
}
268. 丢失的数字
在 nums 数组上进行原地哈希,让索引 i 的位置上放置数字 i,那么可能会遇到数字 n,而数组最大只能放置 n - 1,此时我们不处理 n,当处理完整个数组,再遍历一遍数组,遇到 nums[i] != i 的时候,缺失的数字就是 i 了。否则的话,说明我们没有遇到 n,缺失的自然就是 n
class Solution {
public int missingNumber(int[] nums) {
for(int i = 0;i < nums.length;i++){
while(nums[i] != nums.length && nums[i] != i){
swap(nums,nums[i],i);
}
}
for(int i = 0;i < nums.length;i++){
if(nums[i] != i){
return i;
}
}
return nums.length;
}
public void swap(int[] nums,int i,int j){
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
}