#Java #哈希表 #集合
Feeling and experiences:
哈希表理论基础:
了解哈希碰撞
代码随想录中的解释也很详细:网站链接
归纳了一下在Java中,List,Set,Map集合的常用方法:
当涉及到Java集合时,不同类型的集合接口和实现类可能提供不同的方法,但有一些通用的方法和模式。以下是一些常见的集合方法:
1. 共同方法:
- `add(element)`: 将元素添加到集合中。
- `remove(element)`: 从集合中移除指定元素。
- `contains(element)`: 判断集合是否包含指定元素。
- `size()`: 返回集合中的元素数量。
- `isEmpty()`: 检查集合是否为空。
- `clear()`: 清空集合中的所有元素。
2. List接口方法:
- `get(index)`: 获取指定位置的元素。
- `set(index, element)`: 将指定位置的元素替换为新元素。
- `indexOf(element)`: 返回指定元素的第一个出现位置的索引。
- `subList(fromIndex, toIndex)`: 返回指定范围的子列表。
3. Set接口方法:
- 通常继承自Collection接口的方法,但不包含重复元素。
- `add()`操作会忽略重复元素。
4. Map接口方法:
- `put(key, value)`: 将键值对存储到Map中。
- `get(key)`: 根据键获取值。
- `containsKey(key)`: 检查Map是否包含指定键。
- `containsValue(value)`: 检查Map是否包含指定值。
- `keySet()`: 返回Map中所有键的集合。
- `values()`: 返回Map中所有值的集合。
有效的字母异位词 :力扣题目链接
这道题就能体现哈希表的妙用,因为26个字母是固定的(题目只考虑小写),那么我们可以创建一个容量为26的数组,索引0代表a所在的位置,索引1代表b所在的位置......
那要怎么才能让数组的下标固定表示一个字母,也就是每个字母有一个单独与固定的位置。
可以想到用字符相减的方法,比如:a-a=0,b-a=1,c-a=2.......
这就刚好可以利用数组的下标来取索引值了。
那就先拿一个字符串出来,依次遍历,出现的字母就在其对应的数组位置中+1操作,表示出现次数。
以下是画的草图示例:
在比较的时候,思路就很清晰了,当第二个字符串,也同样遍历。
但是要要反过来,出现的字母在对应的数组位置中-1操作,如果访问到了0,说明出现次数就不相同了(意思就是,该的字符串当前的字母出现的次数比第一个字符串所含的字母多了,不够减了)。
直接上代码解释:
class Solution {
public boolean isAnagram(String s, String t) {
//给了两个字符串,要求出现次数相同
//因为字母只有26个,不多
//考虑到用哈希表的方法
//首先建立一个存放26个字母的数组,用来记录字母出现的次数
int []arr = new int[26];
//依次先获取s字符串的字母
for(int i =0;i<s.length();i++){
arr[s.charAt(i)-97]++; //获取当前字母,-97(小写a 的ASCLL码值)
//这样就相当于在数组的下标中,记录了该字母
}
//这样操作之后,只要是s中的字母,出现后就会被记录,再次到它就会增加次数
if(s.length()==t.length()){
for(int i = 0;i<t.length();i++){
//如果该字母在数组中没有被记录(即 ==0),则说明已经不满足了
if(arr[t.charAt(i)-97]== 0){
return false;
}
else{
//不为0,则说明被记录,但是还要考虑出现的次数
arr[t.charAt(i)-97]--;
//把记录的次数-1
}
}
return true;
}
return false;
}
}
写完之后,测试集未完全通过,才发现未考虑到如果字符串不相同的情况,此代码,如果没有判断字符串相等,则会出错。(记录一下错误)。
在代码随想录是这么写的:
/**
* 242. 有效的字母异位词 字典解法
* 时间复杂度O(m+n) 空间复杂度O(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']++; // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
}
for (int i = 0; i < t.length(); i++) {
record[t.charAt(i) - 'a']--;
}
for (int count: record) {
if (count != 0) { // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
return true; // record数组所有元素都为零0,说明字符串s和t是字母异位词
}
}
不一样的点在于先处理完两个字符串,再用增强for循环检查数组中是否每个元素都为0;
这样确实更加美观与简便!
两个数组的交集 力扣题目链接
一看到交集,区间,脑海里第一想到的就是滑动窗口。
可惜这道题很简单,只要求找出共同拥有的数。
第一次想到了Set集合,因为Set的特点在于,不能有重复元素
第一次写的时候,又没有考虑完全:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
//先创建一个Set集合
Set<Integer> set = new HashSet<>();
List<Integer> res = new ArrayList<>(); //用来存储结果
//我先把一个数组添加到Set集合中
for(int i : nums1){
set.add(i);
}
//加入第二个数组,如果加不进去,说明该数字已经在集合中了,则记录该数字
for(int i : nums2){
if(!set.add(i) && !res.contains(i)){
//如果加不进来,则记录该数字,放到结果集合中
res.add(i);
}
}
return res.stream().mapToInt(Integer::intValue).toArray();
}
}
该代码出现的问题是,如果nums1中的元素比元素比nums2的元素更多,那么就不能得到正确答案。
所以,得运用两个Set集合
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
return new int[0];
}
Set<Integer> set1 = new HashSet<>();
Set<Integer> resSet = new HashSet<>();
//遍历数组1
for (int i : nums1) {
set1.add(i);
}
//遍历数组2的过程中判断哈希表中是否存在该元素
for (int i : nums2) {
if (set1.contains(i)) {
resSet.add(i);
}
}
return resSet.stream().mapToInt(Integer::intValue).toArray();
}
}
整体逻辑就是,根据Set集合不能添加重复元素的特性来操作。
记一下集合转为Int类型数组的方法:stream().mapToInt(Integer::intValue).toArray();
快乐数 :力扣题目链接
一看到这个题,思路很容易就出来,因为存在对各个数位的数进行运算,那么就对每一位(从个位到最高位)进行处理运算。
要计算平方和,就把各个数位上的每个数字取出来,平方后再相加,直到变为1为止
但是要注意的是,这样运行下去,可能会无限循环,一直不等于1,如果我们把循环结束条件设置为1的话,可能就陷入死循环了。
对这个特殊问题进行处理,最开始没有想到该如何是好。
看了代码随想录的文字讲解,再集合今天所练习的内容,原来也是运用到了Set集合。
把每次的得到的sum放到集合中,当集合中再次出现了sum,则说明已经走不出去了。
class Solution {
public boolean isHappy(int n) {
//创建一个Set集合,存放sum值
Set<Integer> set = new HashSet<>();
while(n!=1 && !set.contains(n)){
set.add(n);
n = getSum(n);
}
return n==1;
}
public int getSum(int n ){
int sum = 0;
while(n>0){
sum += (n%10) * (n%10);
n/=10;
}
return sum;
}
}
如果考虑到了循环条件并且想到了如何处理存在无限循环的可能的问题。其余的就非常简单了。
看到力扣上有题解不用集合的方法,而是用到了快慢指针来找出口。
和之前做的找环出口很像,这方法真的很妙,节约很多的内存开销!
两数之和 :力扣题目链接
梦开始的地方!
刚开始刷力扣的时候,第一题上来for循环:
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
for (int i = 0; i < numsSize; ++i) {
for (int j = i + 1; j < numsSize; ++j) {
if (nums[i] + nums[j] == target) {
int* ret = malloc(sizeof(int) * 2);
ret[0] = i, ret[1] = j;
*returnSize = 2;
return ret;
}
}
}
*returnSize = 0;
return NULL;
}
到现在,两个月的时间里,接触了Java,接触了算法,就有了更多的思路!
使用HashMap~~
在力扣上一个非常好的图解:
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if(nums == null || nums.length == 0){
return res;
}
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int temp = target - nums[i]; // 遍历当前元素,并在map中寻找是否有匹配的key
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
break;
}
map.put(nums[i], i); // 如果没找到匹配对,就把访问过的元素和下标加入到map中
}
return res;
}
}
虽是力扣上的第一题,也是HashMap的基本用法,但还是加强训练,能熟练运用!
今日的训练题目(关于哈希表),没有涉及到多少巧解,就是训练对那几类集合的运用,卡哥的总结很精炼:
万籁深山,一星在水.......
Fighting!