18.<tag-数组和原地哈希>-lt.41-缺失的第一个正数 + 剑指 Offer 03- 数组中重复的数字 1.1

什么是原地哈希? 就是在数组中建立像哈希表的那种映射关系(key-value 等价于 index-nums[index] ),

通过使用数组代替哈希表, 能够大大减少空间复杂度, 具体是怎么样的映射关系, 还是要根据题目要求自己设定;
缺点就是容易混乱, nums[nums[i]] 这样式儿的, 还是那句话, 多在草纸上写写画画!

lt.41-缺失的第一个正数

[案例需求]
在这里插入图片描述

[思路分析]

  • 仔细第一遍这个题目, 就会发现这题的目的无非就是验证数组中是否包含1,2,3,4,5,…N(N为数组的长度), 因为要求是不在数组的最小数, 所以返回的就是1~nums.length这些数中, 不在数组中的数罢了.
  • 最容易想到的方法就是哈希法, 把nums中的数全部放入到集合中, 然后遍历1~nums.length, 找到1~nums.length不存于哈希表的第一个数, 便于缺失的最小数.

[代码实现]

class Solution {
    public int firstMissingPositive(int[] nums) {
      //哈希表法
        Set<Integer> set = new HashSet<>();
        int len = nums.length;

        //把nums的数全部放入到set中, set会对这些数, 排序和去重
        //省心呀. 
        for(int i = 0; i < len; i++){
            set.add(nums[i]);
        }

        //这样我们只需要比较一下在 1-len的范围内, 哪些数不在set中了.(返回第一个数即可)
        for(int i = 1; i <= len; i++){
            if(!set.contains(i)return i;
        }

        //如果1-len都在set内, 返回的是len + 1;
        return len + 1;
    }
}

比较容易错的一点就是: len + 1;

这意味着 1-len的数都在nums中, 所以我们缺失的最小正数应该是len + 1;
在这里插入图片描述
时间复杂度: 这里 N 表示数组的长度。第 1 次遍历了数组,第 2 次遍历了区间 [1, len] 里的元素。
空间复杂度: 一个哈希表HashSet, O(n)

[思路分析二, 把数组当做是哈希表]

  • 由于题目要求我们「只能使用常数级别的空间」,而要找的数一定在 [1, N + 1] 左闭右闭(这里 N 是数组的长度)这个区间里。因此,我们可以就把原始的数组当做哈希表来使用。事实上,哈希表其实本身也是一个数组;
  • 我们要找的数就在 [1, N + 1] 里,最后 N + 1 这个元素我们不用找。因为在前面的 N 个元素都找不到的情况下,我们才返回 N + 1;
  • 那么,我们可以采取这样的思路:就把 1 这个数放到下标为 0 的位置, 2 这个数放到下标为 1 的位置,按照这种思路整理一遍数组。然后我们再遍历一次数组,第 1 个遇到的它的值不等于下标的那个数,就是我们要找的缺失的第一个正数。
  • 这个思想就相当于我们自己编写哈希函数,这个哈希函数的规则特别简单,那就是数值为 i 的数映射到下标为 i - 1 的位置

题解动画: 点我

// 原地哈希(把数组当做哈希表来用)解法
class Solution {
    public int firstMissingPositive(int[] nums) {
        /**
            遍历数组, 把数值为i的元素放置在索引为i-1的位置上, 
            再遍历一次数组, 如果当前索引index + 1 不等于 nums[index] 就说明是第一个缺失的整数, 为 index + 1
        
         */
        int len = nums.length;

        for(int i = 0; i < len; i++){
            while(nums[i] >= 1 && nums[i] <= len && nums[i] != nums[nums[i] - 1]){
                swap(nums, i, nums[i] - 1);
            }
        }

        //再次遍历, 找出第一个索引值+1 != nums[i]的数, 找不到就返回数组的长度+1
        for(int i = 0; i < len; i++){
            if(nums[i] != i + 1){
                return i + 1;
            }
        }

        return len + 1;
    }

    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

剑指 Offer 03- 数组中重复的数字

[案例需求]
在这里插入图片描述

[思路分析]

  • 三种解法务必都要熟练掌握, 最重要的是第三种原地哈希法, 利用数组来的index和nums[index]建立起来像哈希表的那种映射关系
  • 空间复杂度大大的减小, 好用!
    [代码实现一, 哈希表]
class Solution {
    public int findRepeatNumber(int[] nums) {
        //1. HashSet
        Set<Integer> hashSet = new HashSet<>();

        for(int i = 0; i < nums.length; i++){
            if(hashSet.contains(nums[i])) return nums[i];

            hashSet.add(nums[i]);
        }

        return -1;
    }
}

在这里插入图片描述

[代码实现二, 排序后遍历查找]

class Solution {
    public int findRepeatNumber(int[] nums) {
        //2. 普通解法
        Arrays.sort(nums);

        for(int i = 0, len = nums.length; i < len; i++){
            if(i < len - 1 && nums[i] == nums[i+1]){
                return nums[i];
            }
        }
        return -1;

    }
}

在这里插入图片描述

[代码实现三, 原地哈希法]

//3. 原地哈希

class Solution {
    public int findRepeatNumber(int[] nums) {
       
        //3. 原地交换!!// 原地哈希
        // 用数组模仿哈希表的映射方式 (i, nums[i])
        int len = nums.length;
        for(int i = 0; i < len; i++){
            while(nums[i] != i){

                 if(nums[nums[i]] == nums[i]) return nums[i];
                swap(nums, i, nums[i]);
            
            }
        }
        return -1;
    }

    public void swap(int[] nums, int left, int right){
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

在这里插入图片描述

posted @   青松城  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示