本文声明:本文参考【代码随想录】的文章,思路以及代码。基于他的文章(希望大家直接去看原作者的优秀文章【代码随想录】,这篇博客仅为了记录自己的学习过程),形成了自己的理解,希望总结成自己的经验与知识,于是发表了这篇博客,如有不妥的地方欢迎大家指正。
一,哈希表法
1,哈希表:
哈希表是什么:哈希表是构造出来的一种可以快速查找的数据结构,是按值存储。(不详细讲哈希函数,处理冲突的方法,负载因子等等知识)。
2,应用场景:
一般哈希表都是用来快速判断一个元素是否出现集合里。(这在Java中用Set集合实现)
提供一种高效的方法来存储和检索键/值对,以便快速查找值(Java中的Map集合实现)
3,注意事项:
Java中,哈希表体系:不单单是指 map。而是包含了Set集合, List列表和Map键值对, 以及特殊情况下还有数组(个人理解)。具体如何进行选择合适的数据结构,多刷题多总结。Set集合,List集合和Map集合的特点:
Set代表的是无序的,不能重复的集合;
List代表的有序,可以重复的集合;
Map代表的具有映射关系的集合。
二,哈希的算法思想解题
1,题解:力扣242.有效的字母异位词
题目描述: 题目:力扣(LeetCode)官网 - 有效的字母异位词
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
注意:若 s
和 t
中每个字符出现的次数都相同,则称 s
和 t
互为字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram" 输出: true
示例 2:
输入: s = "rat", t = "car" 输出: false
题解思路:
需要统计s和t各个组成字母出现的次数, 这就需要使用Map键值对来统计了。优化1:只需要一个Map对象:其中对s的处理:出现字母对应位置的值+1; 其中对t的处理:出现字母对应位置的值-1;最后判断map中值是都均为0。优化2:map的键是确定了的,最多26个小写字母,因此本题可用数组代替map,(tip:map涉及位置的计算,耗时的;而数组可用直接定位)
多练习,多总结,判断那种情况可用使用数组代替Map进行处理,以提升性能,计算速度
解题代码:
/*
题解:
Java中s.charAt(index)用于提取字符串s中的特定字符操作
charAt()方法可以用于提取字符串中特定位置的字符。要使用该方法,必须向其传递一个参数,该参数为字符串中特定字符的位置。注意,Java中的字符串索引从0开始,因此字符串的第一个字符的索引为0,第二个字符的索引为
s.toCharArray():可以将一个字符串转换成一个字符(char)数组。它的作用是将字符串中的每个字符分离出来,存放在一个新的数组中,并返回这个数组。
Arrays类中包含各种用于操作数组的方法,下面是比较常用的几种方法:
1.Arrays.toString():用于返回指定数组内容的字符串表示形式
2.Arrays.fill():用于替换数组原元素
3.Arrays.sort():用于按照数字顺序排列指定的数组(默认升序)
4. Arrays.equals():用于比较两个数组内容是否相等,返回类型为Boolean,两数组内容相同返回true,不同返回false
5.Arrays.binarySearch():用于查找数组中的元素,返回值为int
6.Arrays.copyOf() 和Arrays.copyOfRange():Arrays.copyOf()
具体的自己去API文档学习,使用规则和细节。
自己写的:
//得使用map键值对 but这里有个小tip,s和t都是由字母组成得,那么就可以用数组来代替map
int[] count = new int[26]; //26个小写字母,初始化为0哦
for(int i = 0; i < s.length(); i++){
count[s.charAt(i) - 'a']++;
}
for(int i = 0; i < t.length(); i++){
int target = t.charAt(i) - 'a';
count[target]--;
}
for(int i = 0; i < 26; i++){
if(count[i] != 0){
System.out.println("false");
return false;
}
}
System.out.println("true");
return true;
*/
class Solution {
public boolean isAnagram(String s, String t) {
//大佬版
//法二:排序,然后判断是否一样
if(s.length() != t.length()){ //求字符串长度length()
return false;
}
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1);
Arrays.sort(str2);
return Arrays.equals(str1,str2);
}
}
2,题解:力扣349. 两个数组的交集
题目描述:题目:力扣(LeetCode)官网 - 两个数组的交集
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 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] 也是可通过的
解题思路:
本题很明显的是:判断一个一个元素是否在集合之中,因此利用哈希来做,这里选用Set集合来做。具体做法:1,设置一个集合Set集合coll 存入nums1的所有元素,Set集合result存储最终结果;2,遍历nums2的元素判断是否在coll之中(求元素的交);3,若在,则加入result中,因为采用的是Set集合result,会去除重复元素,以达到题目要求:输出结果每个元素唯一。
本题1是意识到用Map进行解题;2,是利用Set集合特性,选择HashSet数据类型。
解题代码:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet coll = new HashSet();
HashSet result = new HashSet();
//先把nums1存进去, HasSet会去重
for(int i = 0; i < nums1.length; i++){
coll.add(nums1[i]);
}
for(int j = 0; j < nums2.length;j ++){ //可以用for each来
if(coll.contains(nums2[j])){
result.add(nums2[j]);
}
}
System.out.println(result);
Object[] object = result.toArray(); //hashSet.toArray()只能得到Object[]数组
int[] num = new int[object.length]; //还需要进一步转换为整形数组
for(int i = 0; i < object.length;i++){
num[i] = (int) object[i];
}
return num;
}
}
3,题解:力扣202题. 快乐数
题目描述: 题目:力扣(LeetCode)官网 -快乐数
编写一个算法来判断一个数 n
是不是快乐数。「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
示例 2:
输入:n = 2 输出:false
解题思路:
本题的功能实现:求平方和、判断是否快乐数等都还好。难点在于如何处理无限循环:如何让while结束。思路是:利用哈希Se集合,储存每一个处理过的数字。倘若n后续的平方数 又出现在Set集合中,则他是会无限循环的(直接返回false,结束while)。
解题代码:
class Solution {
public boolean isHappy(int n) {
//思考点:怎么结束无限循环:答当sum又等于已经出现过的n时,就会产生循环
//当然还有可能存在:无限不循环的情况。(Why) 默认不考虑出现不了
//自己写的 SO 应当把已经检验过的n值,存起来
Set<Integer> set = new HashSet<Integer>(); //不要求排序,但是集合内无重复元素
while(!set.contains(n)){
set.add(n);
n = squareSum(n);
if(n == 1){
return true;
}
}
return false;
//二选一,注释掉其中一个
//大老版:快慢指针。 真的是把快慢指针的精髓都运用的得心应手啊
/*
如果 n 是一个快乐数,即没有循环,那么快跑者最终会比慢跑者先到达数字 1。
如果 n 不是一个快乐的数字,那么最终快跑者和慢跑者将在同一个数字上相遇。
*/
int slow = n, fast = n;
do{
slow = squareSum(slow); //走一步
fast = squareSum(fast);
fast = squareSum(fast); //fast走两步
}while(slow != fast); //如果是快乐数,最后fast = 1 会等slow
return slow == 1; //return slow == 1 ? true : false;
}
public int squareSum(int n){
int sum = 0;
int temp;
while(n != 0){
temp = n % 10;
sum = sum + temp * temp;
n = n / 10; //两个整数相除,结果为整数
}
return sum;
}
}
4,题解:力扣1,两数之和
题目描述: 力扣(LeetCode)官网 -两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6 输出:[1,2]
解题思路:
- 很明显直接两个for的暴力解:代码如下:
/*
int[] result = new int[2];
//直接干就完啦,
for(int i = 0; i < nums.length - 1; i++){
//这里也可以就是nums.length, 当就= length时直接不执行第二个for
int temp = target - nums[i];
for(int j = i + 1;j < nums.length;j++){
if(nums[j] == temp){
result[0] = i;
result[1] = j;
return result; //return new int[]{i,j};
}
}
}
return result;
//O(n) = n^2 ; S(n) = 1.
*/
- 代码优化:使用双指针可用减少一轮for。 这里使用map也能在一轮for中完成功能:
什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
本题呢,就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。那么我们就应该想到使用哈希法了。
因为本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
注意Map与 数组和Set集合用法的区别、场景:数组的Set的局限性如下:
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
解题代码:
class Solution {
public int[] twoSum(int[] nums, int target) {
/*
能够快速寻找数组中是否存在目标元素 --- 铁定哈希表
使用哈希表,可以将寻找 target - x 的时间复杂度降低到从O(N) -> O(1)
这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target -x, 然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
*/
Map<Integer, Integer> hashtable = new HashMap<Integer,Integer>();
for(int i = 0; i < nums.length;i++){
if(hashtable.containsKey(target - nums[i])){
return new int[]{hashtable.get(target-nums[i]), i};
}
hashtable.put(nums[i], i);
//map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
}
return new int[0];
}
}
5,题解:力扣四数相加II
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 n
,请你计算有多少个元组 (i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2] 输出:2 解释: 两个元组如下: 1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0 2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例 2:
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0] 输出:1
解题思路:
利用题解4中的 Map思路: 定义map1存入nums1[v] + nums2[v](0 <= v, u < n);定义map2存入nums3[v] + nums4[v](0 <= v, u < n);键值对key-value:这里的key = num1[v] + nums2[u], value = key出现过的次数。最后在遍历map1和map2,相加是否等于0,统计符合要求的元组个数。
自己写的:还是有很多不规范的地方,或者不懂的地方会出BUG
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
//两个两数之和
HashMap hashMap1 = nums_add(nums1, nums2); //nums1 + nums2
HashMap hashMap2 = nums_add(nums3, nums4); //nums3 + nums4
int count = 0;
Set<Integer> setKey1 = hashMap1.keySet();
//hashMap.keySet()返回是的Object类型的set集合, 需要用Integer类型接收,而不是int(个人理解Object类型可以转Integer类型,但不能转为int类型)
//知识点1,定义变量(Set, Map)是要用Integer代替int, :因为泛型不接受基本数据类型
Set<Integer> setKey2 = hashMap2.keySet();
for (Integer temp1 : setKey1){
for( Integer temp2 : setKey2){
if(temp1 + temp2 == 0){
count += (int)hashMap1.get(temp1) * (int)hashMap2.get(temp2);
}
}
}
return count;
}
public HashMap nums_add(int[] nums1, int[] nums2){
HashMap hashMap = new HashMap(); //nums1 + nums2
int n = nums1.length;
for (int i = 0; i < n; i++ ){
for (int j = 0; j < n;j ++){
int temp = nums1[i] + nums2[j];
if(hashMap.containsKey(temp)){
hashMap.put(temp, (int)hashMap.get(temp) + 1);
}else{
hashMap.put(temp, 1);
}
}
}
return hashMap;
}
代码优化:完全可用效仿题解4:只定义一个map:存储nums1[v] + nums2[u](0 <= v,u<n)。之后直接判断 (- nums3[v] - nums4[u]) 在不在map中就完事了。这一减少了一个map和两个for:
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
//本题简单的一个点是: 不用考虑重复的元素这种复杂情况:否则就不适合用哈希表
/*
法二:
大佬版本:对自己代码的改进:
对于nums3 , nums4不需要在开辟空间存hashMap2了
直接在hashMap1中找key = 0 - nums3[i] - nums4[j]就行
*/
Map<Integer, Integer> hashMap = new HashMap();
for(int u : nums1){
for(int v : nums2){ //数组也能用foreach
hashMap.put(u+v, hashMap.getOrDefault(u+v, 0) + 1);
System.out.println(hashMap.get(u+v));
//nums1={1,2}, nums2={-2, -1}
//get(u+v)= 1,1,2,1。结果又是对的:没有默认返回0,然后0+1 = 1Rightt!!!
}
}
// 知识点2:hashMap.getOrDefault()函数:可用于简化代码(替换自己写的代码里的if- else)
// (1)getOrDefault(key, default)如果存在key, 则返回其对应的value, 否则返回给定的默认值
// (2)getOrDefault(key, default) +/-1:key值相同, value值+1或者-1;否则返回给定的默认值 +1或者-1
int answer = 0;
for(int i : nums3){
for(int j : nums4){
if(hashMap.containsKey(-i-j)){
answer += hashMap.get(-i-j);
}
}
}
return answer;
}
}
6,题解:力扣赎金信
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。如果可以,返回 true
;否则返回 false
。magazine
中的每个字符只能在 ransomNote
中使用一次。
示例 1:
输入:ransomNote = "a", magazine = "b" 输出:false
示例 2:
输入:ransomNote = "aa", magazine = "ab" 输出:false
解题思路:
这道题就是解题1的翻版: 题解1是判断两个字符串构成的字符集合相等与否;本题是判断字符串1构成的字符集合是否能满足字符串2构成所需的字符集合(判断包含关系)。本题只需意识到用Map集合来统计每个字符出现次数即可 ---> 可用使用数组代替map!!!
解题代码:
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
//典型的哈希表法,tip本题均为小写字母可用数组代替哈希表
int[] character = new int[26];
//重要知识点1:java 怎么从string里面获取其中的某一个字(字符)
//Java中的String类提供了charAt方法,它可以用于获取字符串中指定索引位置的字符。
// 格式:char res = (String)array.charAt(int index);
/*
方法二:对于for循环可用用一下方法实现
for(char c : maganize.toCharArray()){
character[c- 'a']++;
}
知识点2:String array.toChararray():
使用String类的toCharArray()函数将字符串转换为字符数组(多用多记啊)
*/
for(int i = 0; i < magazine.length(); i++){
int temp = magazine.charAt(i) - 'a';
character[temp]++;
}
for(int i = 0; i < ransomNote.length(); i++){
int temp = ransomNote.charAt(i) - 'a';
character[temp]--;
}
for( int j : character){
if(j < 0){
return false;
}
}
return true;
}
}
小飞猪起飞楼!上岸上岸上岸。