天天看点

散列的一些算法题

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

关于散列表更加详细可以看这里散列及散列函数

下面我们通过几个leetcode上的题目加深对散列的理解和使用

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素

给定 nums = [2, 7, 11, 15], target = 9  
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]      

暴力解法

暴力双循环找出符合target - nums[i] = nums[j]条件的 i和j

/**
     * 暴力解
     * 【O(n^2)】
     * @param nums
     * @param target
     * @return
     */
    public int[] twoSum1(int[] nums, int target) {
        for(int i = 0; i < nums.length;i++){
            for(int j = i+1; j < nums.length;j++){
                if(target-nums[j]==nums[i]){
                    return new int[] { i, j };
                }
            }
        }
        throw new IllegalArgumentException("不存在这两个数");
    }      

基于散列解法

因为他要求返回的是索引,且相加两数不是同一个数,所以可以使用一个hashMap将nums中每一个数的数值和对应的索引存到散列表中,数值作为key,索引作为value,遍历哈希表找出符合条件且索引不同的数即可

/**
     * 一次哈希表
     * O(n)
     * @param nums
     * @param target
     * @return
     */
    public int[] twoSum3(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0;i<nums.length;i++){
            int k = target - nums[i];
            if(map.containsKey(k) && map.get(k) != i){
                return new int[]{map.get(k) , i };
            }
            map.put(nums[i],i);
        }
        throw new IllegalArgumentException("不存在这两个数");
    }      

去重问题

给定一个整数数组,判断是否存在重复元素。

如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。

输入: [1,2,3,1]
输出: true
输入: [1,2,3,4]
输出: false      

基于hashSet

关于去重问题其实有更方便的做法就是使用set,set是不包含重复元素的嘛,比较使用set前后的长度差,不相等则有重复值

/**
     * 基于set
     * 去重后判断前后长度是否一致
     * @param nums
     * @return
     */
    public boolean containsDuplicate2(int[] nums) {
        Set<Integer> set = new HashSet<>();
        int len = nums.length;
        for(int i : nums){
            set.add(i);
        }
        return set.size() != len;
    }      

基于hashMap

如果我们使用的散列表,在存放元素时出现同一个元素映射到散列表的一个位置,就是属于散列冲突问题,那么当出现冲突时,就是出现重复元素的时候

/**
     * 基于hashmap
     * @param nums
     * @return
     */
    public boolean containsDuplicate(int[] nums) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i<nums.length;i++){
            if(map.containsKey(nums[i])){
                return true;
            }
            map.put(nums[i],i);
        }
        return false;
    }      

滑动窗口问题

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],

并且 i 和 j 的差的绝对值最大为 k。

输入: nums = [1,2,3,1], k = 3
输出: true

输入: nums = [1,2,3,1,2,3], k = 2
输出: false      

其实这是一类跟去重问题很相似的问题,但是他要求的是在窗口内去重,或者在窗口内找出符合条件的元素

暴力解

/**
     * 暴力解,维护一个长度是k的滑动窗口
     * @param nums
     * @param k
     * @return
     */
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        for(int i = 0;i < nums.length;i++){
            //相当于 j  ---  相隔k  --- i
            for(int j = Math.max(i - k,0);j < i;j++){
                if(nums[j] == nums[i]){
                    return true;
                }
            }
        }
        return false;
    }      

基于hashMap

/**
     * hashmap
     */
    public boolean containsNearbyDuplicate3(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < nums.length;i++){
            // i 一定大于 map.get(nums[i])
            if(map.containsKey(nums[i]) && i - map.get(nums[i]) <= k){
                return true;
            }
            map.put(nums[i],i);
        }
        return false;
    }      

两数组交集问题

基于set

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]      

输出结果中的每个元素一定是唯一的。

我们可以不考虑输出结果的顺序。

/**
     * 暴力解法(很低效)
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] intersection(int[] nums1, int[] nums2) {
        // 1.去重
        Set<Integer> set1 = new HashSet<>();
        Set<Integer> set2 = new HashSet<>();
        for(int i : nums1){
            set1.add(i);
        }
        for(int i : nums2){
            set2.add(i);
        }
        ArrayList<Integer> list = new ArrayList<>();
        // 2.探寻
        for(int i : set1){
            for(int j : set2){
                if(i == j){
                    list.add(i);
                }
            }
        }
        int[] result = new int[list.size()];
        for(int i = 0;i<list.size();i++){
            result[i] = list.get(i);
        }
        return result;
    }      

使用内置函数

/**
     * 使用内置函数
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] intersection1(int[] nums1, int[] nums2) {
        // 1.去重
        Set<Integer> set1 = new HashSet<>();
        Set<Integer> set2 = new HashSet<>();
        for(int i : nums1){
            set1.add(i);
        }
        for(int i : nums2){
            set2.add(i);
        }
        ArrayList<Integer> list = new ArrayList<>();
        //取交集
        set1.retainAll(set2);
        for(int i : set1){
            list.add(i);
        }
        int[] result = new int[list.size()];
        for(int i = 0;i<list.size();i++){
            result[i] = list.get(i);
        }
        return result;
    }      

基于map

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]      

说明:

输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。

/**
     * 使用hashMap
     * @param nums1
     * @param nums2
     * @return
     */
    public int[] intersect(int[] nums1, int[] nums2) {
        //统计重复次数
        int k = 0;
        Map<Integer,Integer> map = new HashMap<>();
        //将nums数值和出现的次数存入map中
        for(int i = 0;i<nums1.length;i++){
            if(map.containsKey(nums1[i])){
                map.put(nums1[i],map.get(nums1[i]) + 1);
            }else{
                map.put(nums1[i],1);
            }
        }
        //找出重复数,存到list,并减少相应的重复次数
        List<Integer> list = new LinkedList<>();
        for(int j = 0;j<nums2.length;j++){
            if(map.containsKey(nums2[j]) && map.get(nums2[j]) > 0){
                list.add(nums2[j]);
                map.put(nums2[j],map.get(nums2[j])-1);
            }
        }
        //最后,将list中的值放入数组中
        int count = list.size();
        int[] aux = new int[count];
        for(int i = 0; i < count; i++){
            aux[i] = ((LinkedList<Integer>) list).poll();
        }
        return aux;
    }