JAVA链表基础——代码随想录 哈希表

哈希表Hash table

基础知识

哈希表是根据关键码的值而直接进行访问的数据结构。

哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。

哈希表1

一般哈希表都是用来快速判断一个元素是否出现集合里。

存储原理:散列函数即哈希函数

把关键码的值映射映射为哈希表上的索引,通过查询索引下标快速知道该元素是否存在。

哈希表2

哈希碰撞

如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞

哈希表3

一般哈希碰撞有两种解决方法, 拉链法和线性探测法。

拉链法

刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了。其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

哈希表4

线性探测法

使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题

例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:

哈希表5

常见的三种哈希结构

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

  • 数组
  • set (集合)
  • map(映射)

LeetCode 242. 有效的字母异位词

给定两个字符串 st,编写一个函数来判断 t 是否是 s 的字母异位词。

**注意:**若 st 中每个字符出现的次数都相同,则称 st互为字母异位词。

输入: s = "anagram", t = "nagaram"
输出: true
输入: s = "rat", t = "car"
输出: false
  • 一开始的思路:没有思路,我都没看懂

  • 题解:统计每个字母的出现次数就好。设置一个一维数组来存储出现次数。用ascii码来记录相对位置。 只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII。最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。最后如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。

string.charAt(i):charAt()是JAVA中常用的字符串方法,其作用返回一个字符串的指定位置的字符,索引是从[0,length-1]。

class Solution {
    public boolean isAnagram(String s, String t) {
        int[] record = new int[26];
        for (int i = 0; i < s.length(); i++){
            record[s.charAt(i) - 'a']++;
        }
        for (int i = 0; i < t.length(); i++){
            record[t.charAt(i) - 'a']--;
        }   
        for (int count = 0; count < record.length; count++){
            if(record[count] != 0){
                return false;
            }
        }
        
        return true;
    }
}
  • string获取长度时是string.length(),数组获取长度是record.length,一个有括号一个没有,因为一个是属性一个是函数调用。

LeetCode 349. 两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

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

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的
  • HashSet的学习:

    import java.util.HashSet;

    • 添加元素:使用 add() 方法,重复的元素不会被添加
    public class RunoobTest {
        public static void main(String[] args) {
        HashSet<String> sites = new HashSet<String>();
            sites.add("Google");
            sites.add("Runoob");
            sites.add("Taobao");
            sites.add("Zhihu");
            sites.add("Runoob");  // 重复的元素不会被添加
            System.out.println(sites);
        }
    }
    
    • 判断元素是否存在:使用 contains() 方法来判断元素是否存在于集合当中

    • 删除元素:使用 remove() 方法来删除集合中的元素

    • 删除集合中所有元素:使用 clear() 方法

    • 计算大小:使用 size() 方法

    • 迭代 HashSet:使用 for-each 来迭代 HashSet 中的元素

for (String i : sites) {
	System.out.println(i);
    }
  • 题解:使用HashSet完成。HashSet的特性是,相同的元素添加不进HashSet,先利用for-each把nums1的元素存储,再for-each判断nums2的元素是否存在于nums1的HashSet中。
import java.util.HashSet;
import java.util.Set;
class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> record = new HashSet<>();
        Set<Integer> record1 = new HashSet<>();
        for(int i:nums1){
            record.add(i);
        }

        for(int i:nums2){
            if(record.contains(i)){
                record1.add(i);
            }
            
        }
        return record1.stream().mapToInt(Integer::intValue).toArray();
    }
}
  • HashSet的初始化方法:
    • Set record = new HashSet<>();
  • record1.stream().mapToInt(Integer::intValue).toArray();把HashSet转化为数组

这俩都没看懂,看完JAVA基础再来纠结这个。

LeetCode 202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

  • 一开始的思路:

    • 完全想不到该怎么用哈希表实现,感觉这道题就是用最普通的方法循环实现。但是写的过程中发现了死循环这个问题。
  • 题解:

    • 确实是用最普通的循环实现的,但是题目里有个重点:sum可能无限循环,但始终变不到 1,最普通的循环可能会死循环出不来,除非人为设置循环次数。
    • 所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是已经开始循环了,return false;否则一直找到sum为1为止。
    • 判断这个sum是否重复出现,就是快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
class Solution {
    public boolean isHappy(int n) {
        Set<Integer> record = new HashSet<>();
        int sum = 0;
        while(sum != 1){
            sum = 0;
            while(n != 0){
                int num = n % 10;
                sum = sum + num * num;
                n = n / 10;
            }
            if(record.contains(sum)){
                return false;
            }
            else{
                record.add(sum);
                n = sum;
            }
        }
        return true;
    }
}

LeetCode 1. 两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
  • 一开始的思路:

    • 我写过这道题笑死,因为这是第一题,但是我忘了当时怎么写的了,反正没用哈希表,因为我还不会这东西。当时好像是暴力解法,直接循环求的。
    • 还是没想明白这该怎么用哈希表。是哈希表存储两数的和吗?两数和存储了有什么用?
  • 题解:利用HashMap ,查找target和数组元素的差值

  • HashMap 的学习:

    • HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
    • HashMap 是无序的,即不会记录插入的顺序。
    • HashMap 的 key 与 value 类型可以相同也可以不同,可以是字符串(String)类型的 key 和 value,也可以是整型(Integer)的 key 和字符串(String)类型的 value。
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7JLEho0C-1677158325226)(null)]
  • 添加元素:添加键值对(key-value)可以使用 put() 方法

  • 访问元素:使用 get(key) 方法来获取 key 对应的 value

  • 删除元素:使用 remove(key) 方法来删除 key 对应的键值对(key-value)

  • 删除所有键值对(key-value)可以使用 clear()方法

  • 计算大小:使用 size() 方法

  • 迭代 HashMap:使用 for-each 来迭代 HashMap 中的元素。、

    • 如果只想获取 key,可以使用 keySet() 方法,然后可以通过 get(key) 获取对应的 value
    • 如果你只想获取 value,可以使用 values() 方法
// 引入 HashMap 类      
import java.util.HashMap;

public class RunoobTest {
    public static void main(String[] args) {
        // 创建 HashMap 对象 Sites
        HashMap<Integer, String> Sites = new HashMap<Integer, String>();
        // 添加键值对
        Sites.put(1, "Google");
        Sites.put(2, "Runoob");
        Sites.put(3, "Taobao");
        Sites.put(4, "Zhihu");
        System.out.println(Sites); // {1=Google, 2=Runoob, 3=Taobao, 4=Zhihu}
        System.out.println(Sites.get(3)); //Taobao
        Sites.remove(4);// {1=Google, 2=Runoob, 3=Taobao}
        System.out.println(Sites.size()); //3
        // 输出 key 和 value
        for (Integer i : Sites.keySet()) {
            System.out.println("key: " + i + " value: " + Sites.get(i));
        }
        // 返回所有 value 值
        for(String value: Sites.values()) {
          // 输出每一个value
          System.out.print(value + ", ");
        }
        
    }
}
  • 题解:

    • 遍历数组的元素,将每个元素加入HashMap时,判断target与该元素差值为几,再去HashMap中寻找是否存在该值,若存在则返回,若不存在则将该元素添加到HashMap中。

      过程一

      过程二

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // key: 数组的值;value:数组的值对应的位置
        Map<Integer, Integer> record= new HashMap();
        int[] position = new int[2];
        for(int i = 0; i < nums.length; i++){
            int temp = target - nums[i];
            if(record.containsKey(temp)){
                
                position[0] = i;
                // get():获取指定 key 对应对 value
                position[1] = record.get(temp);
                return position;
            }
            
            else{
                record.put(nums[i], i);
            }
        }
        return position;
    }
}

本题其实有四个重点:

  • 为什么会想到用哈希表?
    • 要快速查找该元素是否存在。题解的思路是查找target和当前数组元素的差,该差值是否存在于已经寻找过的数组元素中
  • 哈希表为什么用map?
    • 哈希结构一共有数组array、集合set、映射map三种结构。
    • array是普通数组
    • set哈希值数量少、分散,则数组会造成很大的空间浪费,因此可以使用set
    • map存储key和value两个值,
  • 本题map是用来存什么的?
    • 存储已经计算过的数组元素,每个元素都考虑是否和在他之前的元素能够构成和为target。
  • map中的key和value用来存什么的?
    • key用来存储数组元素的值;value用来存储数组元素对应下标

LeetCode 454. 四数相加 II

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
  • 一开始的思路:我是彩笔,我只会循环暴力求解
  • 题解:
    • 一个HashMap存储第一、第二数组元素加起来的和以及其出现次数
    • 然后遍历第三、第四数组,如果第三第四数组元素之和等于负的第一、第二数组元素和,则把HashMap的次数加起来
  • 注意,map的初始化:
    • Map<Integer, Integer> record= new HashMap<>();行
    • HashMap<Integer, Integer> record= new HashMap<>();不行
    • HashMap<int, int> record= new HashMap<>();不行
    • HashMap> record= new HashMap();不行
class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer, Integer> record= new HashMap<>();
        
        for(int n1 : nums1){
            for(int n2 : nums2){
                if(record.containsKey(n1 + n2)){
                    record.put(n1 + n2, record.get(n1 + n2) + 1);
                }
                else{
                    record.put(n1 + n2, 1);
                }
            }
        }
        int count = 0;
        for (int n3 : nums3){
            for(int n4 : nums4){
                if(record.containsKey(-(n3 + n4))){
                    count = count + record.get(-(n3 + n4));
                }
            }
        }
        return count;

    }
}

LeetCode 383. 赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

  • 一开始的解答:同有效异位词
class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] record = new int[26];
        for(int i = 0; i < ransomNote.length(); i++){
            record[ransomNote.charAt(i) - 'a']++;
        }
        for(int i = 0; i < magazine.length(); i++){
            record[magazine.charAt(i) - 'a']--;
        }
        for(int i = 0; i < record.length; i++){
           if (record[i] > 0){
               return false;
           }
        }
        return true;
    }
}

LeetCode 15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

  • 一开始的思路:仿照四数之和,两两组合求和(a+b),再搜索剩下元素是否存在等于-(a+b)的元素。但是因为不能重复,也就是说我用key-value是和-次数的方法存储是不行的。必须记录每两个数的和再去寻找第三个数,约等于两个for循环,而且用不上哈希结构。

  • 题解:双指针法(我属实是没想到这能双指针)。思考一下如何双指针,一个指向第一个元素,一个指向待加的元素,我寻思这也得三个指针啊。(果然是三个指针,只是一个固定另外两个移动。)

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NsRuX6tV-1677158324782)(https://code-thinking.cdn.bcebos.com/gifs/15.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.gif)]

  • 很巧妙的解法我悟了大师。首先把数组排序,设置三个指针,i,left,right。i从下标为0开始,left是i+1, right为数组最后一位。

  • 固定第一位数a,第二位b与第三位c相加。初始情况,第二位b是除了第一位以外最小的数,第三位c是最大的数。若a+b+c>0,和大了要变小就只能左移right,若a+b+c<0,和小了要变大就只能右移left。a+b+c=0时,记录三元组。

  • 为什么数组要排序?为了去重。如果是无序数组的话,每找到一个三元组,都必须与已经找到的三元组每个元素都比较一遍来去掉重复的三元组。如果是有序数组,那么如果第一个元素不相同,那么就算一个新的三元组。

  • 去重操作:在排完序的数组中,重复的可能只能是[-1,-1,-1,-1,0,0,0,0,1,1,1,1,1]这种可能性。所以,重复三元组可以依靠判断临近元素是否和指针元素相同来判断。当相同时,则右移/左移直到元素不同。

  • 简化流程:nums[i]>0时就不用往后找了,因为三元组的和一定>0,不可能存在符合条件的三元组。

  • 注意,我们要找的是不重复的三元组,而不是三元组中没有重复的元素。所以存在[0,0,0]这种情况。

  • 三个数,每个数的去重逻辑不同!

    • i: nums[i] == nums[i - 1]还是nums[i] == nums[i + 1]?nums[i] == nums[i + 1]时,nums[i + 1]是left,也就相当于判断i与left对应元素是否相同。即判断三元组中没有重复的元素。存在[0,0,0]这种情况所以这个地方会报错。nums[i] == nums[i - 1]的逻辑是,当我走到下一个还没开始寻找left和right的位置时,我判断这个位置的元素是否和我上一次循环的元素相同,为了解决第一个元素没有上一个元素的问题需要在条件语句里面加一个i > 0。
    • left:left=left右边的数时,直接移位即可。
    • right:right=right右边的数时,直接移位即可。
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        for(int i = 0; i< nums.length; i++){
            for(int j = i + 1; j< nums.length; j++){
                if(nums[i] > nums[j]){
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }
        }

        for(int i = 0; i< nums.length; i++){
            // nums[i]>0时就不用往后找了,因为三元组的和一定>0,不可能存在符合条件的三元组。
            if (nums[i] > 0) {
                return result;
            }
            // i的去重逻辑
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.length - 1;

            // 这个while终止条件是怎么来的?当i,left,right不是三个不同的数时,即left与right重叠时退出循环
            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]));
                    // left right的去重逻辑
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    right--;
                    left++;
                }

            }
        }
        return result;
    }
}

LeetCode 18. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

  • 一开始的题解:我觉得还是双指针法。虽然还没想好怎么双指针。再嵌套一层for?固定a,b,移动c,d?我觉得可行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值