力扣每日一题:哈希表求数之和汇总

力扣每日一题:哈希表汇总

文章目录

  • 力扣每日一题:哈希表汇总
    • 哈希表理论
    • 哈希函数
    • 常见的三种哈希结构
    • 题目汇总
      • 两数之和
      • 三数之和
      • 四数之和

哈希表理论

参考:https://programmercarl.com/%E5%93%88%E5%B8%8C%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E5%B8%B8%E8%A7%81%E7%9A%84%E4%B8%89%E7%A7%8D%E5%93%88%E5%B8%8C%E7%BB%93%E6%9E%84
定义:哈希表,英文名字为Hash table,也叫做散列表,直白来讲其实数组就是一张哈希表。哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图所示:
力扣每日一题:哈希表求数之和汇总_第1张图片
**功能:**一般哈希表都是用来快速判断一个元素是否出现集合里。

哈希函数

哈希函数,把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引下标快速知道这位同学是否在这所学校里了。哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
力扣每日一题:哈希表求数之和汇总_第2张图片

常见的三种哈希结构

当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。

  • 数组
  • set (集合)
  • map(映射)
    这里数组就没啥可说的了,我们来看一下set。在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:
    力扣每日一题:哈希表求数之和汇总_第3张图片
    std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。

当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
那么再来看一下map ,在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
其他语言例如:java里的HashMap ,TreeMap 都是一样的原理。可以灵活贯通。
虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,但是std::set、std::multiset 依然使用哈希函数来做映射,只不过底层的符号表使用了红黑树来存储数据,所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。
这里在说一下,一些C++的经典书籍上 例如STL源码剖析,说到了hash_set hash_map,这个与unordered_set,unordered_map又有什么关系呢?
实际上功能都是一样一样的, 但是unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,hash_set,hash_map 是C++11标准之前民间高手自发造的轮子。
力扣每日一题:哈希表求数之和汇总_第4张图片

题目汇总

两数之和

题目描述
力扣每日一题:哈希表求数之和汇总_第5张图片
本题呢,则要使用map,那么来看一下使用数组和set来做哈希法的局限。
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value在保存数值所在的下标。C++中map,有三种类型:
力扣每日一题:哈希表求数之和汇总_第6张图片
代码:

//空间复杂度O(n)
//时间复杂度O(n)
class Solution {
    public int[] twoSum(int[] nums, int target) {
        if(nums==null||nums.length==0) return new int [0];    //当数组长度为0或者为空返回空数组
        int n = nums.length;    //数组长度
        //定义哈希表
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i<n;i++){
            int x = nums[i];    //定义下标为i的数为x,固定第i个数
            if(map.containsKey(target-x))   //查找如果哈希表中存在目标数减去x的数,那就是所求的另外一个整数
            {
                int index = map.get(target-x);  //获取另外一个整数
                return new int[]{i,index};  //结果返回这两个数组成的数组
            }
            map.put(x,i);   //如果不满足if条件,就输出第i个数和它的下标
        }
        return new int[0];  //否则返回空数组
    }
}

三数之和

题目描述
力扣每日一题:哈希表求数之和汇总_第7张图片
其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是 O ( n 2 ) O(n^2) O(n2),也是可以在leetcode上通过,但是程序的执行时间依然比较长 。接下来我来介绍另一个解法:双指针法,这道题目使用双指针法 要比哈希法高效一些。

//双指针方法,用哈希不好进行剪枝操作,虽然时间复杂度为O(n^2)但是还是很费时间
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);   //数组先进行排序小到大
        
        for(int i = 0;i<nums.length;i++){   //如果第一个数大于0就直接返回数组
            if(nums[i]>0){
                return result;
            }

            if(i>0 && nums[i] == nums[i-1]){
                continue;   //去重,去掉重复的数
            }

            int left = i+1; //定义左指针
            int right = nums.length - 1;    //定义右指针
            
            while(right > left){
                int sum = nums[i] + nums[left] + nums[right];
                if(sum > 0){
                    right--;
                }else if(sum < 0){
                    left++;
                }else{
                    result.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    // 去重放在找到三元组之后

                    while(right>left && nums[right]==nums[right-1]) right--;
                    while(right>left && nums[left]==nums[left+1]) left++;

                    // 找到之后双指针收缩
                    right--;
                    left++;
                }
            }
        }
        return result;
    }
}

四数之和

原理同三数之和
题目描述
力扣每日一题:哈希表求数之和汇总_第8张图片
解题思路
0.先数组由小到大排序;
1.先两层遍历得到nums[k]+nums[i]的值,并去重;
2.然后,定义双指针left,right,
3.计算sum =nums[k]+nums[i]+nums[left]+nums[right];
4.将sum与目标数相比,大于则right左移,right–,小于left右移,left++;
5.赵铎四元组之后去重
6.找到结果之后双指针同时收缩。
7.返回结果。

代码

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();     
        Arrays.sort(nums);   //数组从小到大排序
        for(int k = 0;k < nums.length;k++){ //第一个数
            if(k > 0 && nums[k] == nums[k-1]){  //去重
                continue;
            }
            for(int i = k + 1;i < nums.length;i++){    //第二个数
                if((i > k + 1) && nums[i-1] == nums[i]){   //去重
                    continue;
                }
            int left = i + 1; //定义左指针
            int right = nums.length - 1;    //定义右指针
            while(right > left){
                int sum = nums[k] + nums[i] + nums[left] + nums[right];
                if(sum > target){    //大于目标数
                    right--;
                }else if(sum  < target){  //小于目标数
                    left++;
                }else{  //等于目标数
                    result.add(Arrays.asList(nums[k],nums[i],nums[left],nums[right]));  
                    //添加到结果中,组成数组
                    while(left<right && nums[right]== nums[right - 1]) right--;
                    while(left<right && nums[left]== nums[left + 1]) left++;
                     // 找到答案双指针同时收缩
                    left++;
                    right--;
                }
            } 
        }
    }
    return result;
    }

}

你可能感兴趣的:(力扣刷题,leetcode,算法)