哈希表有关算法基础题
文章目录
哈希表概述
本文主要内容
LeetCode242.有效的字母异位词
思路一:遍历字符串使用hashmap的键值key保存字母,value保存key字母出现的次数,第二个字符串,出现key值遍减一, 最后hashmap中value全为0
public boolean isAnagram(String s, String t) {
if(s.length()!=t.length()){ //长度不一致返回false
return false;
}
HashMap<Character,Integer> map = new HashMap<>(); //创建hash表 key放字符 value放个数
for(int i=0;i<s.length();i++){ //遍历字符串
if(map.containsKey(s.charAt(i))){ //如果包含value+1;
Integer value = map.get(s.charAt(i));
value++;
map.put(s.charAt(i),value);
} else{ //不包含,添加value为1
map.put(s.charAt(i),1);
}
if(map.containsKey((t.charAt(i)))){ //t字符串中,如果包含value-1
Integer value = map.get(t.charAt(i));
value--;
map.put(t.charAt(i),value);
}else{ //不包含,添加value为-1
map.put(t.charAt(i),-1);
}
}
Set<Character> characters = map.keySet(); //遍历map 每一个value都是0返回正确
for (char c: characters) {
if(map.get(c)!=0){
return false;
}
}
return true;
}
思路二:类似与思路一,创建字符串数组(26)个代替hashmap该方法更适合本题,因为判断的主题为char类型,若判断类型为自定义类,可以使用hashmap
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) { //长度不一致返回false
return false;
}
int cnt[] = new int[26]; //26个英文字母
for(int i=0;i< s.length();i++){ //遍历两个字符串
cnt[s.charAt(i)-97]++; //s中字符对应的数加一
cnt[t.charAt(i)-97]--; //t中字符对应的数减一
}
for(int i=0;i< cnt.length;i++){ //遍历数组全为零正确
if(cnt[i]!=0){
return false;
}
}
return true;
}
LeetCode349. 两个数组的交集
思路:
1.将其中一个数组元素添加到hashset中(去重)本题重点是交集的元素,而不是个数
2.遍历第二个数组,判断集合中是否包含数组中的元素
3.要注意的是,不确定有多少个重合的,所以不能创建数组添加,需要通过集合转数组
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
return new int[0]; //不满足条件
}
Set<Integer> set = new HashSet<>(); //创建集合1
for(int i=0;i< nums1.length;i++){ //遍历数组一将不重复的元素添加到set
set.add(nums1[i]);
}
Set<Integer> set1 = new HashSet<>(); //创建集合2
for(int i=0;i< nums2.length;i++){ //遍历数组二将set中包含的元素添加到set1
if(set.contains(nums2[i])){
set1.add(nums2[i]);
}
}
int []array = new int[set1.size()]; //创建数组
Object[] objects = set1.toArray(); //将set1中整型元素添加到数组中
for(int i=0;i<set1.size();i++){
array[i] = (int)objects[i];
}
return array;
}
LeetCode202. 快乐数
思路一:
每次得到一个新的数,取余数得到每一位的数,再次平方->判断。。取余。貌似要用到递归。
重审题目:提示会进入无线循环且n为int型 可以得到最大的数为1999 9999 9 其加工后为730
数字有限,所以必定存在环。所以可以用set集合判断
public boolean isHappy(int n) {
HashSet<Integer> set = new HashSet<>(); //set集合
while(n!=1&&!set.contains(n)){ //n不等于1 n没有出现
set.add(n); //添加到集合
n = getNextNumber(n); //加工数据
}
return n==1; //循环不存在时,n是否等于1
}
private static int getNextNumber(int n){
int res = 0;
while(n>0){ //从个位取数加工
int pow = n%10;
res += pow*pow;
n/=10;
}
return res;
}
思路二:快慢指针
接思想一,加工一次数据成为一次操作,此时可以设置慢指针每次加工一次,快指针每次加工两次,当fast==slow时
1.若n为不快乐数,fast与slow在一个环形链表中走,此时fast等于slow一定不为1
2.若是快乐数,fast与slow在一条含有环,且1自成环的链路上走,此时fast等于slow必定为1
如图
public boolean isHappy(int n) {
int fast = n; //快指针
int slow = n; //慢指针
do {
fast = getNextNumber(getNextNumber(fast)); //加工两次
slow = getNextNumber(slow); //加工一次
} while (fast != slow);
return slow == 1; //fast==slow时返回slow==1
}
private int getNextNumber(int n) { //加工一次
int res = 0;
while (n > 0) {
int pow = n % 10;
res += pow * pow;
n /= 10;
}
return res;
}
}
补充:
快乐数
不是快乐数的数称为不快乐数(unhappy number),所有不快乐数的数位平方和计算,最後都会进入 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 的循环中
LeetCode1. 两数之和
思路一:最先想到暴力解法第一层循环遍历所有元素,第二层循环找当前元素和等于target 这里我们介绍第二种思路
思路二:本质是在数组/集合中寻找元素,题目描述为在集合中寻找两个数a,b使得a+b=target可以理解为在集合中寻找target-a若存在直接返回
public int[] twoSum(int[] nums, int target) {
int arr[] = new int[2]; //结果数组
Map<Integer,Integer> map = new HashMap<>(); //map集合 key用来存放差值,value用来存放index
for(int i=0;i<nums.length;i++){ //遍历数组
if(map.containsKey(nums[i])){ //如果map中key与num相同,说明存在和为target的两个数
arr[0] = map.get(nums[i]);
arr[1] = i;
return arr;
}else { //如果不存在 将target-nums[i]d的值添加进map
map.put(target-nums[i],i);
}
}
return null;
}
LeetCode454. 四数相加II
思路:试图模仿两数之和,但四个数出自四个不同的数组,而且target没有明确给出,尝试将数组分组合并,出现条件两个数组的和是另两个数组的和的相反数
本题采用hashmap的思想,将将nums1和nums2相加并添加到map的key中,并同时记录出现次数,将3,4数组的元素两两相加,取反在map中查找,匹配并加和对应value值
本题重点 : 1:如何将数组分组 2:一组的数组如何与另一组比较 3.如何找出所有答案
public static int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer, Integer> map = new HashMap<>(); //创建hashmap
for (int a : nums1) { //将nums1和nums2相加并添加到map的key中
for (int b : nums2) {
if (map.containsKey(a + b)) {
map.put(a + b, map.get(a+b)+1); //用value记录出现了多少次相同的和
} else {
map.put(a + b, 1);
}
}
}
int cnt = 0; //最终计数
for (int c : nums3) { //遍历nums3和nums4
for (int d : nums4) {
if (map.containsKey(-(c + d))) { //将和取负数在集合中查找
cnt += map.get(-(c + d)); //查找成功,将cnt加上对应-(c+d)的value值
}
}
}
return cnt; //返回
}
LeetCode383. 赎金信
思路一:关键在处理ransomNote中重复的元素,建立hashmap用key值存储magazine中字符,value存储key元素重复的次数,再遍历ransomeNote,匹配则对应value减一 若不匹配或value==0 失败
public boolean canConstruct(String ransomNote, String magazine) {
Map<Character, Integer> map = new HashMap<>(); //创建hashmap
char s1[] = magazine.toCharArray(); //转为字符数组
for (int i = 0; i < s1.length; i++) {
if (map.containsKey(s1[i])) { //将字符数组添加进map并记录出现次数
map.put(s1[i], map.get(s1[i]) + 1);
} else {
map.put(s1[i], 1);
}
}
char s2[] = ransomNote.toCharArray(); //将ransomNote转为字符数组
for (int i = 0; i < s2.length; i++) {
if (!map.containsKey(s2[i])) { //不包含或者包含value==0返回false
return false;
} else if (map.get(s2[i]) == 0) {
return false;
} else {
map.put(s2[i], map.get(s2[i]) - 1); //匹配条件,value减一
}
}
return true;
}
思路二:重审题目发现和第242题类似
本题重点:1:magazine中的元素不可以重复使用 2:都是小写 3:本质还是查找是否存在
因此我们换一种哈希做法
public static boolean canConstruct(String ransomNote, String magazine) {
if (ransomNote.length() > magazine.length()) { //rans大于magazine返回fasle
return false;
}
int arr[] = new int[26]; //26个小写字母
for (char c : magazine.toCharArray()) { //遍历maga记录每个字母出现的次数
arr[c - 'a']++;
}
for (char c : ransomNote.toCharArray()) { //遍历rans将每个字母对应位置数值减一
arr[c - 'a']--;
if (arr[c - 'a'] < 0) { //不包含返回false
return false;
}
}
return true;
}
LeetCode15. 三数之和
思路:本题难在满足a+b+c=0条件的a,b,c在一个数组中且必须是三个不同的索引,考虑map集合时对索引的去重十分的复杂,这里在两数之和的启发下使用三指针方法解决固定一个指针:剩余两个指针遍历数组,加和为第一个指针的相反数
本题难点:1:数组排序的思想 2:三指针 3:i,j,k的去重问题
public List<List<Integer>> threeSum(int[] nums) {
int last = nums.length - 1;
List<List<Integer>> res = new ArrayList<>(last>255?256:last+1);
Arrays.sort(nums); //排序
int j, k;
if (nums[0] > 0 || nums[last] < 0||nums.length<3) {//和为0 第一个数大于0 最后一个数小于0 或者个数小于3
return res;
}
if (nums[0] == 0 && nums[last] == 0) { //数组元素全为零
res.add(Arrays.asList(0, 0, 0));
return res;
}
int target;
for (int i = 0; i <= last - 2; i++) { // 固定i ijk不可以重复 所以i<=last-2
j = i + 1; //设置双指针
k = last;
if (i > 0 && nums[i] == nums[i - 1]) { //给i去重
continue;
}
if(nums[i]>0){break;} //跳出特例
while (j < k) { //双指针遍历
target = nums[i] + nums[j] + nums[k];
if (target < 0) {
j++;
} else if (target > 0) {
k--;
} else {
res.add(Arrays.asList(nums[i], nums[j++], nums[k--]));//满足条件添加
while (j<k&&nums[j] == nums[j-1]) { //j向前判重
j++;
}
while (k>j&&nums[k] == nums[k + 1]) { //k向后判重
k--;
} //因为i固定 j,k任意重复,结果必定重复
}
}
}
return res;
}
LeetCode18. 四数之和
与三数之和不同的时,本题加和不为0,剪枝难度加大,使用四个指针的方式:将前两个指针固定,后两个类似于双指针,代码如下
public List<List<Integer>> fourSum(int[] nums, int target) {
int n = nums.length;
List<List<Integer>> res = new ArrayList<>();
if (n < 4) {return res;} //不足四个返回res
Arrays.sort(nums); //排序
int i, j, k, l; //四个指针 每次固定前两个
long sum1_2, sum1_4; //前两个和前四个数之和
for (i = 0; i < n - 3; i++) { //固定第一个数,ijkl不能重复所以i<n-3
if(target>0&&nums[i]>target) return res;//剪枝 因为排完序,第一个数大于,四个数和必大于
if (i > 0 && nums[i] == nums[i - 1]) { //将前一个相同的i过滤掉
continue; //注意由于是去重不可以是i==i+1
}
j = i + 1; //固定第二个数
for (; j < n - 2; j++) { //n-2同理
sum1_2 = nums[i] + nums[j]; //前个数相加
//剪枝
// if(target>0&&nums[j]>0&&sum1_2>target){
// break;
// }
if (j > i + 1 && nums[j] == nums[j - 1]) {//对j向前去重
continue;
}
k = j + 1; //双指针从j+1到表尾开始遍历
l = n - 1;
while (k < l) { //相遇为跳出条件
sum1_4 = sum1_2 + nums[k] + nums[l];
if (sum1_4 < target) {
k++;
} else if (sum1_4 > target) {
l--;
} else {//满足条件加入k++ l--
res.add(Arrays.asList(nums[i], nums[j], nums[k++], nums[l--]));
while (k < l && nums[k] == nums[k - 1]) k++;//对k l去重
while (k < l && nums[l] == nums[l + 1]) l--;//因为ij固定 kl任意固定答案固定
}
}
}
}
return res;
}
总结
哈希表的应用多发生在对集合元素的查找,在无序集合中可以完成双指针无法完成的工作如两数之和,但有时双指针可以替换哈希表解决问题,如三数之和。在哈希与双指针之间的选择是重点