代码随想录算法训练营第42期 第六天| 242.有效的字母异位词 、 349. 两个数组的交集 、202. 快乐数、1. 两数之和

一、哈希表介绍

哈希表法有三大类:数组,set,map。

1.数组  

适用于存储密集的分布

2.set

HashSet:  

特点:
基于 HashMap 实现。
使用哈希表存储元素。
允许 null 元素。
不保证顺序,插入顺序和迭代顺序可能不同。
线程不安全。

增查效率:
插入效率:平均 O(1),最坏情况下 O(n)(当哈希冲突严重时)。
查找效率:平均 O(1),最坏情况下 O(n)(当哈希冲突严重时)。

底层实现:基于 HashMap 实现,内部使用哈希表来存储元素。哈希表通过散列函数将元素的键值映射到表中的位置,以实现快速的插入和查找操作。


TreeSet:  

特点:
基于 TreeMap 实现。
使用红黑树(自平衡二叉搜索树)存储元素。
不允许 null 元素。
保持元素的自然顺序或通过 Comparator 指定的顺序。
线程不安全。

增查效率:
插入效率:O(log n),因为底层是红黑树结构,每次插入需要保持树的平衡。
查找效率:O(log n),因为红黑树的查找效率是对数级别。

底层实现:基于 TreeMap 实现,内部使用红黑树(自平衡二叉搜索树)来存储元素。红黑树在插入和查找操作中保持平衡,以确保操作的时间复杂度为 O(log n)。


LinkedHashSet:   

特点:
基于 HashSet 实现。
使用 LinkedHashMap 存储元素,保持插入顺序。

允许 null 元素。
保持元素插入顺序。
线程不安全。

增查效率:
插入效率:平均 O(1),与 HashSet 类似,因为底层也是基于哈希表实现的。
查找效率:平均 O(1),与 HashSet 类似。

底层实现:基于 HashSet 实现,并且通过双向链表维护插入顺序。内部使用 LinkedHashMap 来存储元素,除了使用哈希表来存储数据外,还维护了一个链表以记录插入顺序。


 

常用方法:

set.add("apple");   //添加元素
set.contains("apple");  //查询是否包含元素
set.remove("apple");   //移除元素
set.size();     //获得集合的大小
set.isEmpty();  //查询集合是否为空
set.clear();    //清除集合

3.Map

HashMap


特点:
基于哈希表实现,允许 null 键和值。
无序存储,不保证顺序。

线程不安全。


增查效率:
插入效率:平均 O(1),最坏情况 O(n)(当哈希冲突严重时)。
查找效率:平均 O(1),最坏情况 O(n)(当哈希冲突严重时)。


底层实现:
使用数组和链表(Java 8 以后使用红黑树)存储数据。
通过散列函数将键值映射到数组的位置

TreeMap


特点:
基于红黑树(自平衡二叉搜索树)实现。
有序存储,键按自然顺序或 Comparator 排序。

不允许 null 键。
线程不安全。


增查效率:
插入效率:O(log n),因为每次插入需要保持树的平衡。
查找效率:O(log n),因为红黑树的查找效率是对数级别。


底层实现:
使用红黑树存储数据。
通过比较器或键的自然顺序来维护顺序。

LinkedHashMap


特点:
基于哈希表和双向链表实现。
有序存储,按插入顺序或访问顺序。

允许 null 键和值。
线程不安全。


增查效率:
插入效率:平均 O(1),与 HashMap 类似。
查找效率:平均 O(1),与 HashMap 类似。


底层实现:
使用哈希表存储数据,同时维护一个双向链表以记录插入或访问顺序。

ConcurrentHashMap


特点:
线程安全的哈希表,适用于高并发场景。
不允许 null 键和值。


增查效率:
插入效率:O(1),分段锁机制使其在高并发下性能优越。
查找效率:O(1),与 HashMap 类似。


底层实现:
使用分段锁和哈希表存储数据,保证线程安全。
每个分段包含一个独立的哈希表和锁,以减少锁竞争。

Hashtable


特点:
线程安全的哈希表,老版实现类,不推荐使用。
不允许 null 键和值。


增查效率:
插入效率:O(1),但由于使用全表锁,性能较低。
查找效率:O(1),但由于使用全表锁,性能较低。


底层实现:
使用哈希表存储数据,所有方法都同步,保证线程安全。
每次操作都会锁住整个哈希表,性能较差。

常用方法:

map.put("one", 1);   //增加one->1的键值对
map.get("one");      //获取key为one键值对的value
map.containsKey("one");   //查询key是否包含one
map.containsValue(1);    //查询value是否包含1
map.remove("one");      //移除key为one的键值对
map.size();     //获取map的大小
map.isEmpty();   //查询map是否为空
map.clear();    //清除map表

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

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

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

学习链接:有效的字母异位词 

状态:做出来了

时间复杂度:O(n)       空间复杂度:O(n)

细节之处:1

思路:由于元素分布密集,直接开一个26位的数组record,里面是a-z的映射,record记录s中各类小写字母出现的次数,然后再遍历t,在相应位置减去,因为如果俩个是字母异位词,record26都会为0. 

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']++;  //1 因为是存储小写字母,直接线性存储就行
		}
		for(int i=0;i<t.length();i++)
		{
			record[t.charAt(i)-'a']--;
		}
		for(int count:record){
			if(count!=0)return false;
		}
		return true;


    }
}

三、 349. 两个数组的交集

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

学习链接:两个数组的交集

状态:做出来了

时间复杂度:O(n)       空间复杂度:O(n)

细节之处(知识点):1

思路:由于元素分布疏散,利用set1记录nums1不重复元素的集合 ,resset记录相交的元素,然后遍历nums2,判断set1是否包含该元素,包含则加入到resset里面。

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
			Set<Integer> set1=new HashSet<>();
			Set<Integer> resSet=new HashSet<>();
			for(int i:nums1){
				set1.add(i);
			}
			for(int i:nums2){
				if(set1.contains(i)){
					resSet.add(i);
				}
			}
			int[] res=resSet.stream().mapToInt(n->n).toArray(); //1 用stream流进行转换int,然后转换成数组
			return res;
    }
}

四、202. 快乐数

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

「快乐数」 定义为:

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

学习链接:快乐数

状态:做出来了

时间复杂度:O(n)       空间复杂度:O(n)

细节之处:1 2 3 

思路: 因为元素分布疏散,所以使用set,用set记录上一次替换的,如果有重复的替换,则一定是无限循环,返回false,若是res==1,则返回true.

class Solution {
	public boolean isHappy(int n) {
		HashSet<Integer> record = new HashSet<>();
		int cur = n;
		int res = 0;
		if (n == 1) return true;   // 1 下面代码在n=1的时候会判false,除非下面俩个if调换顺序,这里直接特判

		while (true) {
			record.add(cur);
			while (cur > 0) {
				res += (cur % 10) * (cur % 10);
				cur = cur / 10;
			}
			cur = res;   //2 更新 cur,res回到之前的状态,避免下次计算累加
			res = 0;
			if (record.contains(cur)) {
				return false;   //如果有重复的res,证明一定是循环,直接返回false
			}
			if (cur == 1) return true;
		}

	}
}

五、1. 两数之和

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

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

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

学习链接:两数之和

状态:做出来了

时间复杂度:O(n)       空间复杂度:O(n)

细节之处:1

思路:因为这道题不仅得存储值还得存储下标,所以需要使用到键值对,使用map,利用map记录值和下标,遍历一个数组,然后在之前的map里面寻找是否能够完成数学等式的元素,如果有,再判断是否是相同的元素,如果不是,则记录下来,返回结果。 

class Solution {
    public int[] twoSum(int[] nums, int target) {
		Map<Integer,Integer> map=new HashMap<>();
		int count=0;
		int[] res=new int[2];
		for(int i:nums){
			map.put(i,count++);
		}
		for(int i=0;i<nums.length;i++){
			if(map.containsKey(target-nums[i])&&map.get(target-nums[i])!=i){ //1 判断map是否包含相加所需的元素,且这个元素不是相同的元素
				res[0]=i;
				res[1]=map.get(target-nums[i]);
				break;
			}
		}
		return res;
    }
}

 六、总结

元素分布密集使用数组,分散使用set,如果是键值对使用map,然后根据题目要求选择对应的实现类,若无特别要求,则使用hashset或者LinkedHashSet(增查效率高),map则使用hashMap或LinkedHashMap(增差效率高)

如果对大家有帮助,大家点个赞,收藏一下吧,文章如果有错误的,欢迎大家在评论区指出我的错误,我一定会及时改正的

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值