算法刷题Day5~Day6 - 哈希表part1|哈希表基础|LC.242 有效字母的异位词|LC349. 两个数组的交集|LC202. 快乐数|LC1. 两数之和

哈希表基础

哈希表Hash Table是一种存储键值对的数据结构,最简单的例子就是数组,我们通过数组的index索引来直接访问数组中存储的元素。哈希表的主要功能就是用了快速判断一个元素是否出现在集合里。

哈希函数Hash Function一个将键映射为数组索引的函数。

哈希表的大小是有限的,键值组合可能有很多,因此不同的键可能通过哈希函数会映射到同一个位置,这叫做哈希碰撞(Collision)。碰撞处理主要分为拉链法(Separate Chaining)即为哈希表的冲突位置建立一个链表,将碰撞元素都储存在链表中;以及开放地址法(Open Addressing)当发生碰撞时,通过某种策略(如线性探测、二次探测、双重哈希)寻找下一个空闲的槽来存储数据。

常见的哈希结构:

  1. 数组 Array:将键映射到数组的索引位置。数组中的每个位置称为一个槽
  2. 集合 Set / HashSet:用于存储不重复的元素,但是不能保证集合中的元素顺序
  3. 映射 Map / HashMap:用于存储键值对,每一个键都是唯一的并且与一个值相关连,但也不能保证键的顺序

LeetCode - 242. Valid Anagram 有效字母的异位词

输入两个英文字符串,只要两者所包含的英文字母一样(可能顺序不一样)即为异位词。这道题比较适用的哈希结构是数组。因为对于英文单词一共就26个,对于这种数据量小的情况可以直接生成一个长度为26的数组。因为字母的位置是可以直接确定的,我妈可以通过数组对应的索引直接查找是否出现过该字母。在遍历第一个单词时,对应的字母顺序在数组中的位置+1,遍历第二个单词时-1。如果最后所有元素都为0那么就证明两者为异位词。

class Solution {
    public boolean isAnagram(String s, String t) {
        int[] letterHash = new int[26];
        for (int i=0; i < s.length(); i++) {
            letterHash[s.charAt(i) - 'a']++;
        }
        for (int j=0; j < t.length(); j++) {
            letterHash[t.charAt(j) - 'a']--;
        }
        for (int c : letterHash) {
            if (c != 0) return false;
        }
        return true;
    }
}

但同时也需要注意,因为我们是按数组的索引为基础(0~26),每个字母的ASCII码值需要减去第一个字母'a'才能转换为0~26字母在数组中所对应的可用的数值。

LeetCode - 349. Intersection of Two Arrays 两个数组的交集

这题中数组的交集是不重复的。因为数组里的数值大小我们是无法确认的,这时候做哈希映射的时候用数组就不太合适了,因为确定不了一个固定的数组长度(leetcode上更新了数字大小的上限,其实是可以做的)。

而这题的本质也就是判断元素是否出现过,并且数值可能很大或是很分散,这种情况下就适合用Set。思路就是先把其中一个数组的元素记录到HashSet中,再遍历第二个数组中过的元素是否存在于HashSet,如果存在则加入result数组。

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> hs = new HashSet<>();
        Set<Integer> res = new HashSet<>();
        for (int i : nums1) {
            hs.add(i);
        }
        for (int j : nums2) {
            if (hs.contains(j)) {
                res.add(j);
            }
        }
        //convert to array
        int[] resArr = new int[res.size()];
        int idx = 0;
        for (int a : res) {
            resArr[idx++] = a;
        } 
        return resArr;
    }
}

这里注意因为我们想要的结果是去重的,result应该先存为HashSet结构自动去重并且最终再转换为数组结构

LeetCode - 202. Happy Number 快乐数

快乐数的定义是对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到最后的数字变为 1。这个题有可能会出现一直找不到的情况,即无限循环,因此这时候就要借助Set来判断是否出现过。注意while循环的条件,首先要判断不包含在set中的数字才进行循环,否则就代表重复了也就是出现无限循环的情况。

刚开始没有想到如何拆分数字中每位数字进行计算,看了卡尔的题解学习到了这个巧妙的方法。用余数来得到最后一位数字以及每次都除以10来去掉末尾数字,这样就可以用循环来算出总平方和。这里的num>0的循环条件并不会形成死循环,按照逻辑处理完所有数位后n会变成小数,但因为num定义为int,Java的除法机制自动丢弃小数部分,因此处理完之后num就是等于0的,退出循环。

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> hs = new HashSet<>();
        while (n != 1 && !hs.contains(n)) {
            hs.add(n);
            n = getNextNum(n);
        }
        return n == 1;
    }

    //helper function
    private int getNextNum(int num) {
        int sum = 0;
        while (num > 0) {
            int dec = num % 10;
            sum += dec * dec;
            num /= 10; //get next digit
        }
        return sum;
    }
}

LeetCode - 1. Two Sum 两数之和

这题同样是查找一个元素是否遍历过,因为在遍历每个数字的过程中,需要往前找是否可以相加等于target的数字。但是题目中最终要求的是返回两个数字对应的index,这时候光找到数字是不够的,因此需要存储键值对才能达到目的。注意这里键值对中key是数值而不是index,因为我们在查找的时候是根据差值diff来查找的,因此key为数组中的数值,value为index。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        HashMap<Integer, Integer> hm = new HashMap<>();
        for (int i=0; i < nums.length; i++) {
            int diff = target - nums[i];
            if (hm.containsKey(diff)) {
                res[0] = hm.get(diff);
                res[1] = i;
            }
            hm.put(nums[i], i);
        }
        return res;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值