什么时候想到用哈希法?
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
242.有效的字母异位词
先看暴力的解法,两层for循环,同时还要记录字符是否重复出现,很明显时间复杂度是 O(n^2)
(leetcode测试用例会超出时间限制)
class Solution {
//暴力法双重for循环
public boolean isAnagram(String s, String t) {
if(s.length() != t.length()){
return false;
}
//遍历一个字符串 第一个字符
for(char c : s.toCharArray()){
if(countChar(s,c) != countChar(t,c)){
return false;
}
}
return true;
}
private int countChar(String s,char c){
int count = 0;
//遍历第一个字符在整个字符串中出现的个数
for(char cc : s.toCharArray()){
if(cc == c){
count ++;
}
}
return count;
}
}
字典解法
定义一个数组叫做record用来上记录字符串s里字符出现的次数。
需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。
那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。
那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。
最后如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。
class Solution {
public boolean isAnagram(String s, String t) {
//定义一个26位英文字典
int[] record = new int[26];
//第一个字符串进行遍历字典+1
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']--;
}
//当字典数组全为0 表示为字母异位词
for(int count : record){
if(count != 0){
return false;
}
}
return true;
}
}
349. 两个数组的交集
看到需要去重唯一,不考虑输出结果顺序,就要想到用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<>();
//遍历数组一 去重添加到set集合中
for(int s : nums1){
set1.add(s);
}
//遍历数组二 判断集合中是否包含数组元素 得到最总结果
for(int i : nums2){
if(set1.contains(i)){
resSet.add(i);
}
}
//将set集合转换为数组
//方法一 申请一个数组用来存放resSet元素最后返回
// int[] arr = new int[resSet.size()];
// int j = 0;
// for(int i : resSet){
// arr[j++] = i;
// }
// return arr;
//方法二 使用stream流
return resSet.stream().mapToInt(x->x).toArray();
}
}
202. 快乐数
之所以使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。
重复会成环 使用Set来判断集合中是否出现过
class Solution {
public boolean isHappy(int n) {
Set<Integer> record = new HashSet<>();
while( n != 1 && !record.contains(n)){
record.add(n);
n = getNextNumber(n);
}
return n==1;
}
private int getNextNumber(int n){
int result = 0;
while(n>0){
int temp = n % 10;
result += temp * temp;
n = n / 10;
}
return result;
}
}
1. 两数之和
暴力解法 双重for循环
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if(nums == null || nums.length == 0){
return res;
}
for(int i = 0;i < nums.length;i++){
for(int j = i + 1 ;j < nums.length;j++){
if(nums[i] + nums[j] == target){
res[0] = i;
res[1] = j;
return res;
}
}
}
return res;
}
}
map
本题其实有四个重点:
- 为什么会想到用哈希表
当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
- 哈希表为什么用map
因为本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
- 本题map是用来存什么的
- map中的key和value用来存什么的
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)
接下来是map中key和value分别表示什么。
这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素
数组中同一个元素在答案里不能重复出现。key为元素 value为下标
map存放的就是我们访问过的元素
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
Map<Integer,Integer> resmap = new HashMap<>();
if(nums == null || nums.length == 0){
return res;
}
for(int i=0 ;i<nums.length ;i++){
int temp = target - nums[i];
//如果集合中存在temp
if(resmap.containsKey(temp)){
res[0] = resmap.get(temp);
res[1] = i;
break;
}
//不存在 将当前遍历元素添加到map集合中去
resmap.put(nums[i],i);
}
return res;
}
}