Day06~242.有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和

105 阅读8分钟

摘要

本文主要介绍了哈希表理论基础,并附带了LeetCode上的几道题目,包括242.有效的字母异位词、349.两个数组的交集、202. 快乐数、1.两数之和的解题思路与示例代码。

1、哈希表理论基础

1.1 概念

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。

哈希表(Hash Table)是一种数据结构,它可以用于在常数时间复杂度内(平均情况下)执行插入、删除和查找操作。在Java中,哈希表通常是通过HashMap类实现的,HashMap基于哈希表的原理来存储键-值对。

以下是关于Java中的哈希表(HashMap)的一些关键特点和用法:

  1. 基本用法HashMap是Java集合框架中的一部分,用于存储键-值对。你可以使用put(key, value)方法添加键-值对,使用get(key)方法获取值,使用remove(key)方法删除键-值对。
  2. 键的唯一性:在一个HashMap中,键是唯一的。如果你尝试使用相同的键添加多个值,后面的值会覆盖先前的值。
  3. 性能HashMap提供了快速的查找性能,因为它使用了哈希函数将键映射到存储桶中,从而允许在常数时间内查找值。但是,性能可能会受到哈希碰撞的影响。
  4. 哈希碰撞:哈希碰撞是指不同的键被映射到相同的存储桶中。HashMap使用链表或红黑树等数据结构来解决碰撞问题,以保证性能。
  5. 初始容量和负载因子HashMap允许你指定初始容量和负载因子。初始容量是哈希表中存储桶的数量,默认为16。负载因子是一个浮点数,它表示当哈希表的大小达到容量乘以负载因子时,将触发哈希表的重新调整大小操作。通常,较低的负载因子可以减少哈希碰撞,但会增加内存开销。
  6. 线程安全性HashMap不是线程安全的,如果在多线程环境下使用,必须采取额外的同步措施或使用ConcurrentHashMap等线程安全的替代品。
  7. 遍历:你可以使用迭代器或Java 8的Lambda表达式来遍历HashMap中的键-值对。
  8. 性能注意事项:在使用HashMap时,要特别注意选择合适的哈希函数和合适的初始容量和负载因子,以免性能问题。

总之,Java中的哈希表(HashMap)是一种强大的数据结构,适用于许多应用场景,但要注意性能和线程安全性方面的考虑。

1.2 3种常用数据结构

哈希表是一种非常常见的数据结构,它在计算机科学中有着广泛的应用。哈希表通常基于哈希函数将键映射到值的数据结构。以下是哈希表的三种常用数据结构:数组、集合(Set)、映射(Map)。

  1. 数组

    • 数组是最简单的哈希表数据结构,也是最基本的数据结构之一。
    • 哈希表通过哈希函数将键直接映射到数组的索引位置。这种方式非常高效,查找时间复杂度为O(1)。
    • 数组只能用于处理键的范围有限且连续的情况,例如用于计数的情况。
  2. 集合(Set)

    • 集合是一种特殊的哈希表,它存储不重复的元素。
    • 集合只关心键(元素)的存在与否,不存储键值对。
    • 常见的集合数据结构有HashSet、LinkedHashSet和TreeSet。HashSet是基于哈希表的,查找时间复杂度为O(1)。
  3. 映射(Map)

    • 映射是一种常见的哈希表,它存储键值对。
    • 键值对中的键是唯一的,而值可以重复。
    • 常见的映射数据结构有HashMap、LinkedHashMap和TreeMap。HashMap是基于哈希表的,查找时间复杂度为O(1)。

这些哈希表数据结构在不同的场景下有不同的用途。数组通常用于处理简单的计数问题,集合用于存储一组唯一的元素,而映射则用于存储键值对,例如在存储配置信息、缓存数据等方面非常常见。选择适当的哈希表数据结构取决于你的需求和数据特性。

1.3 哈希表的操作

Java中的哈希表主要是通过HashMap类来实现的,它提供了一系列常用的操作。以下是Java中HashMap的一些常见操作:

  1. 插入键值对:使用put(key, value)方法向哈希表中插入键值对。如果该键已经存在,新的值将覆盖旧的值。

    HashMap<String, Integer> hashMap = new HashMap<>();
    hashMap.put("apple", 1);
    hashMap.put("banana", 2);
    
  2. 获取值:使用get(key)方法根据键获取值。

    int value = hashMap.get("apple"); // 获取键"apple"对应的值
    
  3. 删除键值对:使用remove(key)方法根据键删除键值对。

    hashMap.remove("apple"); // 删除键"apple"对应的键值对
    
  4. 判断键是否存在:使用containsKey(key)方法来检查键是否存在。

    boolean exists = hashMap.containsKey("banana"); // 检查键"banana"是否存在
    
  5. 获取键集合:使用keySet()方法获取哈希表中所有键的集合。

    Set<String> keys = hashMap.keySet(); // 获取键的集合
    
  6. 获取值集合:使用values()方法获取哈希表中所有值的集合。

    Collection<Integer> values = hashMap.values(); // 获取值的集合
    
  7. 遍历键值对:可以使用entrySet()方法获取包含键值对的Set,然后遍历它。

    for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
        String key = entry.getKey();
        int value = entry.getValue();
        // 处理键值对
    }
    
  8. 获取大小:使用size()方法获取哈希表中键值对的数量。

    int size = hashMap.size(); // 获取键值对数量
    
  9. 清空哈希表:使用clear()方法清空哈希表,删除所有键值对。

    hashMap.clear(); // 清空哈希表
    
  10. 默认值:如果键不存在时,可以使用getOrDefault(key, defaultValue)方法来获取一个默认值。

    int value = hashMap.getOrDefault("cherry", 0); // 获取键"cherry"对应的值,如果不存在返回默认值0
    

这些是HashMap类的一些常见操作,可以用于在Java中使用哈希表。根据具体需求,你可以选择不同的操作来管理和操作哈希表中的数据。

1.4 哈希Set的操作

哈希集(HashSet)是Java中的一种常见数据结构,它实现了Set接口,用于存储不重复的元素。以下是哈希集的一些常用操作:

  1. 添加元素:使用add(element)方法向哈希集中添加元素。如果元素已存在,添加操作将被忽略,因为哈希集不允许重复元素。
Set<String> hashSet = new HashSet<>();
hashSet.add("apple");
hashSet.add("banana");
  1. 移除元素:使用remove(element)方法从哈希集中移除指定元素。
hashSet.remove("banana");
  1. 检查元素是否存在:使用contains(element)方法检查哈希集中是否包含指定元素。
boolean contains = hashSet.contains("apple");
  1. 获取集合大小:使用size()方法获取哈希集的大小,即包含的不重复元素个数。
int size = hashSet.size();
  1. 遍历集合:可以使用迭代器或增强型for循环遍历哈希集中的元素。
for (String element : hashSet) {
    System.out.println(element);
}
  1. 清空集合:使用clear()方法清空哈希集,使其不包含任何元素。
hashSet.clear();
  1. 判断是否为空:使用isEmpty()方法检查哈希集是否为空。
boolean isEmpty = hashSet.isEmpty();

哈希集通常用于需要存储一组元素且不允许重复的情况。它的实现基于哈希表,因此添加、查找和移除元素的操作通常具有很快的平均时间复杂度。

2、242.有效的字母异位词

2.1 HashMap

思路

使用hashmap,key表示字母,value表示次数 首先遍历字符串 s存储字母元素并计数 然后遍历字符串 t如果不存在相应字母则返回false,反之给字母相应的计数减1,如果计数为0则删除元素 最后如果map为空则互为字母异位词

代码

    public boolean isAnagram(String s, String t) {
        Map<Character,Integer> map = new HashMap<>();
​
        for(int i=0; i<s.length(); i++) {
            char ch = s.charAt(i);
            Integer count = map.getOrDefault(ch, 0);
            map.put(ch, ++count);
        }
        
        for(int j=0; j<t.length(); j++) {
            char ch = t.charAt(j);
            if(map.containsKey(ch)) {
                Integer count = map.get(ch);
                map.put(ch, --count);
                if(count == 0) {
                    map.remove(ch);
                }
            } else {
                return false;
            }
        }
        return map.size() == 0;
    }

2.2 数组

思路

使用int数组,数组下标0-25代表a-z,数组的值代表次数 遍历字符串s,int数组中相应的元素 + 1;遍历字符串t,int数组中相应的元素 -1; 最后遍历int数组,如果互为字母异位词,则最后int数组中的元素值都为 0

  • s.charAt(i) - 'a':并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
  • arr[s.charAt(i) - 'a'] += 1 可以优化为 arr[s.charAt(i) - 'a']++

代码

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

3、349.两个数组的交集

3.1 思路

遍历num1, 定义一个set1存储nums1;遍历nums2, 定义set2存储交集元素,最后将set2转换为int数组返回

3.2 代码

    // 遍历num1, 定义一个set1存储nums1;遍历nums2, 定义set2存储交集元素,最后将set2转换为int数组返回
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> set1 = new HashSet<>();
        Set<Integer> set2 = new HashSet<>();
​
        for(int num1 : nums1) {
            set1.add(num1);
        }
​
        for(int num2 : nums2) {
            if(set1.contains(num2)) {
                set2.add(num2);
            }
        }
​
        int[] res = new int[set2.size()];
        int i = 0;
        for(Integer item : set2) {
            res[i++] = item;
        }
        return res;
    }

4、202. 快乐数

4.1 思路

1、判断一个数 n 是不是快乐数?

题目中说了会无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!

  • 定义一个set保存sum,如果无线循环,则set.contains(sum)==true,反之直到结果为1,证明其为快乐数

2、如何计算平方和?

示例:n = 19;

  • 从左到右,从低到高、num = (n % 10)、 计算平方和、n = (n /10) ,直至 n = 0,即 while(n > 0)

4.2 代码

    // 定义一个set保存sum,如果无线循环,则set.contains(sum)==true,反之直到结果为1,证明其为快乐数
    public boolean isHappy(int n) {
        Set<Integer> set = new HashSet<>();
​
        while(n != 1) {
            // 计算平方和,判断是否重复
            int sum  = getSum(n);
            if(set.contains(sum)) {
                return false;
            }
​
            // 继续循环,判断是否是快乐数
            set.add(sum);
            n = sum;
        }
        return true;
    }
​
    // 计算平方和
    public int getSum(int n) {
        int sum  = 0;
        while(n > 0) {
            int num = n % 10;
            sum += num * num;
​
            n = n / 10;
        }
        return sum;
    }

5、1.两数之和

5.1 暴力法

思路

暴力法,双层遍历nums,判断是否存在ums[i]+nums[j]==target; 取值范围:i=[0,len-1], j=[i+1, len]

代码

    public int[] twoSum(int[] nums, int target) {
        for(int i=0; i<nums.length-1; i++) {
            for(int j=i+1; j<nums.length; j++) {
                if(nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
​
            }
        }
        return new int[]{-1, -1};
    }

5.2 HashMap

思路

将遍历过的元素保存到hashmap,key表示nums[i], value表示 i; key表示遍历过的元素,判断是否存在 key + num[i] == target

  • 数组中同一个元素在答案里不能重复出现

代码

AC-1

两次遍历:一开始并没有想到一次遍历的方式,因为只操作一个数组,所以只需要遍历一次数组即可

    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int i=0; i<nums.length; i++) {
            map.put(nums[i], i);
        }
​
        for(int j=0; j<nums.length; j++) {
            int key = target - nums[j];
            if(map.containsKey(key)) {
               int i = map.get(key);
               if(i != j) {
                   return new int[]{i, j};
               }
            }
        }
        return new int[]{-1, -1};
    }
AC-2

一次遍历

    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
​
        for(int i=0; i<nums.length; i++) {
            int key = target - nums[i];
            if(map.containsKey(key)) {
               int j = map.get(key);
               return new int[]{i, j};
            }
            
            map.put(nums[i], i);
        }
        return new int[]{-1, -1};
    }

参考资料

代码随想录-有效的字母异位词

代码随想录-两个数组的交集

代码随想录-快乐数

代码随想录-两数之和