题目与题解
参考资料:哈希表理论基础
242.有效的字母异位词
题目链接:242.有效的字母异位词
代码随想录题解:242.有效的字母异位词
解题思路:
题目要求统计字符串中每个字符出现的次数,如果两个字符串的每个字符出现频次都相同,则符合要求。对于这种需要统计的对象范围很小的情况,非常适合用数组去统计。
由于小写字母一共就26个,可以设置一个大小为26的数组,‘a’对应第0个元素,'b'对应第1个元素... ‘z’对应第25个元素,并初始化每个元素为0。遍历第一个字符串,记录每个字符的出现次数;接着遍历第二个字符串,每个字符出现一次,就在数组中对当前字符的频次减去1;最后判断数组是否为全0,如果是,则这两个字符串是有效的字母异位词。
class Solution {
public boolean isAnagram(String s, String t) {
// 数组实现
int[] count = new int[26];
Arrays.fill(count, 0); // 初始化数组为全0,不初始化的话默认也是全0
for (int i = 0; i < s.length(); i++) {
count[s.charAt(i) - 'a'] += 1;
}
for (int i = 0; i < t.length(); i++) {
count[t.charAt(i) - 'a'] -= 1;
}
for (int i = 0; i < 26; i++) {
if (count[i] != 0) return false;
}
return true;
}
题目还给出了拓展要求,假设字符串含unicode字符。经过查询,unicode字符的范围在0x0000到0x10FFFF,远比26个小写字母多很多。此时数组法固然也可以解决相应的问题,但输入的字符串长度远小于unicode字符串的范畴,此方法会导致大量的空间浪费。此时用键值对,即map类型的方法更适合存储数据。这样,分别用两个map对象存储两个字符串中字符出现的频次,最后对比两个map的结果是否相同即可。
class Solution {
public boolean isAnagram(String s, String t) {
// HashMap实现
HashMap<Character, Integer> maps = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
if (maps.containsKey(s.charAt(i))) {
maps.put(s.charAt(i), maps.get(s.charAt(i)) + 1);
} else {
maps.put(s.charAt(i), 1);
}
}
HashMap<Character, Integer> mapt = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
if (mapt.containsKey(t.charAt(i))) {
mapt.put(t.charAt(i), mapt.get(t.charAt(i)) + 1);
} else {
mapt.put(t.charAt(i), 1);
}
}
return maps.equals(mapt);
}
}
看完代码随想录之后的想法
数组法存储空间浪费少,哈希法查找对比效率高,都挺好。
遇到的困难
题目本身不难,难在我不记得java里面要从字符串里面取字符应该用charAt方法,平常真的不太用,这次记住就行。
349. 两个数组的交集
题目链接:349. 两个数组的交集
代码随想录题解:349. 两个数组的交集
解题思路:
交集这个概念一出,对应了set这一数据结构,利用set特性去除数组中的重复元素,然后再将两个set进行比对会比较高效。
分别基于输入的数组,创建两个set类型变量,遍历数组,将每个元素都存入set变量,会得到每个数组对应的集合结果;接着,再分别遍历两个集合,将每个元素存入新的set变量,得到交集结果;最后将set转换为数组类型,返回结果。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 用set
HashSet<Integer> nums1Set = new HashSet<>();
for (int num : nums1) {
nums1Set.add(num);
}
HashSet<Integer> resultSet = new HashSet<>();
for (int num : nums2) {
if (nums1Set.contains(num)) {
resultSet.add(num);
}
}
int[] result = new int[resultSet.size()];
int index = 0;
for (int num : resultSet) {
result[index++] = num;
}
return result;
}
}
看完代码随想录之后的想法
看到输出结果唯一,就要想到去重,对应的就是set容器。
由于本题限制了数组大小为1000以内,数量不大,可以用数组代替哈希表进行计算。如果没有限制范围,数组法就不适用了。
随想录题解如下:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
int[] hash1 = new int[1002];
int[] hash2 = new int[1002];
for(int i : nums1)
hash1[i]++;
for(int i : nums2)
hash2[i]++;
List<Integer> resList = new ArrayList<>();
for(int i = 0; i < 1002; i++)
if(hash1[i] > 0 && hash2[i] > 0)
resList.add(i);
int index = 0;
int res[] = new int[resList.size()];
for(int i : resList)
res[index++] = i;
return res;
}
}
遇到的困难
因为题目已经说了用集合,众所周知英文字母set就有集合的意思,其实也就对应了java里面的hashset数据类型,很容易想到。但是写的时候,同样不知道怎么用set类型,与map不同,插入元素是set.add,而map是map.put。
202. 快乐数
题目链接:202. 快乐数
代码随想录题解:202. 快乐数
解题思路:
一个数字通过快乐数的计算过程,最终只能有两个结果:为1,或是无限循环,而无限循环就意味着之前算出来的平方和不止一次出现。显然可以用set类型记录结果,保证记录的平方和没有重复,直到第一个重复的结果出现,返回false;如果中途平方和为1,返回true。
class Solution {
public boolean isHappy(int n) {
Set<Integer> squareSums = new HashSet<>();
int loopResult = n;
while (loopResult != 1 ) {
int squareSum = 0;
int num = loopResult % 10;
while (loopResult != 0 ) {
squareSum += num*num;
loopResult /= 10;
num = loopResult % 10;
}
if (squareSums.contains(squareSum)) return false;
loopResult = squareSum;
squareSums.add(loopResult);
}
return true;
}
}
看完代码随想录之后的想法
思路是差不多的,随想录另写了一个小方法getNextNumber用于记录每次循环后得到的平方和结果,看起来更清晰一点。
遇到的困难
想的时候很容易,写的时候脑子不太清楚,尤其是在计算每一次的平方和时,写错了好几个地方,debug了三次才通过。第一次得到了死循环,因为内层循环的条件错写成num != 0;并且还忘记在内层循环更新num的值了;还有return false这一句,一开始放在了外层循环的最后一句,那由于前一步已经把这次的平方和结果塞进去了,马上就return false了,逻辑顺序出错。这种涉及到细节的过程,一定要带入一个数字试试,仔细一点,否则很容易出错。
1. 两数之和
题目链接:1. 两数之和
代码随想录题解:1. 两数之和
解题思路:
虽然知道题目应该要用哈希表做,但一时半会儿没想起来,所以先用暴力法给一个结果。两层循环遍历数组,第一层取数组中的每一个元素,第二层查找数组中为target - nums[i]的元素,如果找到了就返回下标。要小心出现target - nums[i] = nums[i]的情况,第二层循环的j不能等于i。
class Solution {
public int[] twoSum(int[] nums, int target) {
// 暴力法
for (int i = 0; i < nums.length; i++) {
int result = target - nums[i];
for (int j = 0; j < nums.length; j++) {
if (result == nums[j] && j != i) {
return new int[]{i, j};
}
}
}
return new int[]{};
}
}
看完代码随想录之后的想法
本来想试着用map类型解决,但是第一次想只要统计数组中每个元素的出现频次,就可以很容易的查找是否存在一个合适的数,但突然发现结果要求返回其下标,那该方法作废。
最后还是看了随想录的做法,思路是用map类型存键值对,key是数字,value是对应的下标,然后查找target-nums[i]对应的结果是否在map的key中。
我第一次看到的时候觉得很奇怪,如果数组中存在一样的数,那么遍历后该数的新下标不就把老下标覆盖了吗?假设target正好等于两个一样的数之和,那不是根本无法取得前一个数的下标吗?
后来看了算法,才发现其实是一边查询一边把不符合要求的数字插入map中的。如果提前找到了结果,就不用把后面的数再一一插进去计算查找了。这意味着,如果数组中确实存在两个相同的数之和等于target,那么第一个数已经存入map的情况下,当遍历到第二个数时,代码会先判断这个数是否符合要求,符合就直接返回结果,不需要再把它插入了,很巧妙。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
} else {
map.put(nums[i], i);
}
}
return new int[]{};
}
}
遇到的困难
hash法一开始想岔了没想出来。以后要注意,但凡涉及到无重复的,唯一的,要快速查找的,用set和map会更灵活。
今日收获
复习了hash表的用法。哈希法的基础知识其实挺复杂的,我光看了一遍有点记不住。但是目前可以先把它们的用法和基本原理掌握一下,先保证下次会用。
题目是分了两天做的,晚上开始的有点晚,很困,效率不高,加起来连上写博客大概也花了三个小时。这强度逐渐吃不消了呀,加油加油。