目录
哈希day1
1. LeetCode242 有效的字母异位词
题目
思路
暴力解法
- 第一种解法,s开一个外循坏,每遍历一个字符,就进入t开的内循环,查找是否有相同的字符出现,若有,标记此字符,退出内循环;接着往s的下一个字符遍历。遍历结束后,再次对t开循环,挨个字符检查是否都变成了刚才的标记,如果有的没有标记,则s和t一定存在某些字符的数量的不一致。
- 第二种解法, 利用sort函数,如果s和t是同位字符串,那么经过排序后,得到的结果应该是一样的。
记录几个常用的函数:
获取数组长度:array.length;
获取字符串长度:string.length();
将字符串转成字符数组:char[] array = string.toCharArray();
取字符串的第n个字符:char x = string.charAt(n);
数组排序:Arrays.sort(array);
哈希法
哈希中三个方法:数组,set,map,哈希的宗旨是用空间复杂度换取时间复杂度。
涉及到查找元素的,量较少的,用数组,量较多的,用set;要用到key-value的,用map。
此题哈希法思路:
- 新开一个int类型的数组,26个char的长度,分别对应a到z 26个字母,用来存储每个字母在s和t字符串中出现的次数。int类型数组默认初始值都为0。
- 首先遍历s字符串,
(当前字符-'a')
即为当前字符在新开的数组中对应的索引。字符出现一次,就在其对应的索引位置+1,记录出现次数。 - 遍历s后,再遍历t,同样找到每次遍历到的字符在新开的数组中对应的索引,字符出现一次,就在其对应的索引位置-1,如果s和t同一个字符的出现次数相等的话,最后遍历结束,结果应该是新开的数组的每个元素都为0。
- 最后,判断元素是否都为0。
代码
暴力——排序
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1);
Arrays.sort(str2);
return Arrays.equals(str1, str2);
}
}
暴力——挨个定位
class Solution {
public boolean isAnagram(String s, String t) {
char[] s1 = s.toCharArray();
char[] t1 = t.toCharArray();
if (s1.length != t1.length){return false;}
for (int i = 0; i < s1.length; i++){
for (int j = 0; j < t1.length; j++){
if (t1[j] == s1[i]){
t1[j] = 1;
}
}
}
for (int i = 0; i < t1.length; i++){
if (t1[i] != 1){
return false;
}
}
return true;
}
}
哈希法
class Solution {
public boolean isAnagram(String s, String t) {
int[] array = new int[26];
int sum = 0;
for (int i = 0; i < s.length(); i++){
array[s.charAt(i) - 'a']++;
}
for (int i = 0; i < t.length(); i++){
array[t.charAt(i) - 'a']--;
}
for (int i = 0; i < array.length; i++){
if (array[i] != 0){
return false;
}
}
return true;
}
}
2. LeetCode349 两个数组的交集
题目
思路
【知识点】哈希法的数组和set
数组:新定义一个数组,数组中每个元素的索引代表key,元素的值代表value,可以快速地保存元素出现的次数。
优点:相比HashSet,节省空间和时间。空间上,数组的长度是确定的;时间上,HashSet每次的hash操作都会消耗不少时间。
缺点:数组定义的前提是要知道数组的大小。
HashSet:Set<Integer> set = new HashSet<>();
针对这个set,有add()方法,去重地添加元素,即如果元素已经存在于set中,则不重复加入。还有contains()方法,查询set中是否包含某元素。size()方法,获取set里元素的个数。底层代码会帮助你实现哈希映射。
优点:大小不需要可控。
确定:时间长,空间消耗多。
本题set思路
遍历nums1,定义set1,去重存放nums1中的数字。遍历nums2,每遍历一个元素,查询是否在nums1中存在,若存在,则说明是二者交集,存到新开的setFinal里。最后setFinal里的所有元素即为交集。
因为题目要返回整型数组,从set转为数组有两种方法。
1、return resSet.stream().mapToInt(x -> x).toArray();
2、遍历set,挨个从set里取整数出来放到新定义的int[]数组里。
这两种方法,2比1在速度上有明显的优势,怀疑可能是在set里将元素转为整型再将set转为array很耗时。
本题哈希数组思路
因为题目里又加了1 <= nums1.length, nums2.length <= 1000
,0 <= nums1[i], nums2[i] <= 1000
这两个条件,所以可以定义一个1001长度的数组,从index0到index1000可以代表从0到1000的共计1001个数字。索引对应的值代表这个数字是否出现过。
先遍历nums1,对于每个元素,都在数组的对应位置将值设为1。再遍历nums2,对于每个元素,查询数组上对应位置的值是否为1,若为1,则说明这个数是二者交集,存到新定义的HashSet中。最终HashSet里的所有数字就是我们想要的结果了。
同样地,set需要转为整型数组,和上面两种方法一样。
【知识点】解法耗时分析
四种解法中,耗时排名:数组2 < set2 < 数组1 < set1
可得结论:set比数组耗时,从set转为数组的两个方法中,1又比2耗时。且对最终执行时间影响最大的是转化方法中1比2耗的时长。
【知识点】遍历数组与set的快速操作
快速操作:
for(int i : array) // 对于array中的每个整型数字i进行遍历,i代表元素值,不是索引!!!
for(int i : set) // 对于set中的每个整型数字i进行遍历,i代表元素值,不是索引!!!
代码
哈希set:
import java.util.HashSet;
import java.util.Set;
// import java.util.*
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);
}
}
//方法1:将结果集合转为数组
return resSet.stream().mapToInt(x -> x).toArray();
//方法2:另外申请一个数组存放setRes中的元素,最后返回数组
int[] arr = new int[resSet.size()];
int j = 0;
for(int i : resSet){
arr[j++] = i;
}
return arr;
}
}
哈希数组:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
int[] array = new int[1001];
Set<Integer> set = new HashSet<>();
for (int i : nums1){
array[i] = 1;
}
for (int i : nums2){
if (array[i] == 1){
set.add(i);
}
}
//方法1:将结果集合转为数组
return set.stream().mapToInt(x -> x).toArray(); /3ms
//方法2:另外申请一个数组存放setRes中的元素,最后返回数组
int[] newArray = new int[set.size()];
int j = 0;
for (int i : set){
newArray[j++] = i;
}
return newArray; //1ms
}
}
3. LeetCode202 快乐数
题目
思路
首先明确一个点,题目中已经暗示了,只有两种情况,一是无限循环但不等于1,二是无限循环等于1。至于为什么一定会无限循环,答案是因为int类型最大值为为2 147 483 647, 所以平方和最大的数是1 999 999 999,平方和为1 + 81*9 = 724。任何数的平方和都在1到724之间,724次循环之内一定有重复的。
第二点,求每次的平方和的时候最好单独定义一个函数来求。
代码
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
int sum = solveSum(n);;
while (sum != 1 && !set.contains(sum)){
set.add(sum);
sum = solveSum(sum);
}
if (sum == 1){
return true;
}else {return false;}
}
public int solveSum(int n){
int sum = 0;
while (n > 0) {
sum += (n % 10) * (n % 10);
n = n / 10;
}
return sum;
}
}
4. LeetCode01 两数之和
题目
思路
【知识点】HashMap
定义:Map<Integer,Integer> map = new HashMap<>();
此题中,key
为数字值,value
为对应的索引
获取map中是否含有key:map.containsKey()
根据key值得value值:map.get(key)
插入一对新的key-value值:map.put(key, value)
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] add = new int[2];
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++){
if (map.containsKey(target-nums[i])){
add[0] = i;
add[1] = map.get(target-nums[i]);
return add;
}else {
map.put(nums[i], i);
}
}
return null;
}
}
暴力法,不用hashmap:
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] add = new int[2];
for (int i = 0; i < nums.length; i++){
for (int j = i + 1; j < nums.length; j++){
if (nums[i] + nums[j] == target){
add[0] = i;
add[1] = j;
return add;
}
}
}
return add;
}
}