[LeetCode]-原地哈希

前言

记录 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];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值