代码随想录算法训练营第6天 | 242.有效的字母异位词、349.两个数组的交集、202.快乐数、1.两数之和
今日学习的文章链接和视频链接
参考代码随想录
自己看到题目的第一想法
今天的题目之前都刷过,结合前几天经验来看,可能会有记不得,希望可以不看笔记写出来
自己实现过程中遇到哪些困难
- 242.有效的字母异位词秒了
- 349.两个数组的交集也秒了,学到了数组,set,和map做哈希表的特点和优缺点,特别学习了Python中
set
的数据结构,Java中hashtable
,hashset
,和hashmap
的对比 - 202.快乐数看了笔记才写出来,特别学到用Python实现辅助方法
getSum()
中,遍历的条件为n = n // 10
,而不是n /= 10
,因为前者规定了向下取整,最终n
会等于0
- 1.两数之和也看了笔记,学到了Java中
hashmap
的几个常用方法,包括构造函数,map.containsKey()
,map.get(Key)
,和map.put(Key, Value)
今日收获,记录一下自己的学习时长
- 应打卡7月3日,7月4日补打卡,学习时长4hr
- 字符串*1,28. 找出字符串中第一个匹配项的下标,学习KMP算法
- 进入栈和队列,希望可以加快进度,尽早进入树
0242. 有效的字母异位词 Valid Anagram
1. 题目描述
给定两个字符串s
和t
,编写一个函数来判断t
是否是s
的字母异位词。
注意:若s
和t
中每个字符出现的次数都相同,则称s
和t
互为字母异位词。
示例1:
输入: s = "anagram", t = "nagaram"
输出: true
示例2:
输入: s = "rat", t = "car"
输出: false
2. 解题思路
- 可以用暴力循环解法,时间复杂度为
O(n^2)
- 使用哈希表进行优化。数组就是一个哈希表,定义一个数组记录字符串
s
里字符出现的次数。
具体算法:
- 定义一个大小为 26 的数组
record
,因为从小写字母a
到z
一共有26个字符 - 把字符映射到哈希表的索引下标上,因为字符
a
到字符z
的ASCII是26个连续的数值,所以字符a
映射为下标0,相应的字符z
映射为下标25。 - 先遍历字符串
s
;遍历时,将s[i] - 'a'
所在的元素做+1
操作,不需要记住a
的ASCII,只要求出一个相对值就可以 - 再遍历字符串
t
;同样,在遍历时将t[i] - 'a'
所在的元素做-1
操作 - 最后遍历哈希表
record
,如果有元素不为0
,说明字符串s
和字符串t
所包含的字符不一样,他们不是字母异位词,返回False
;如果所有record
的元素都为0
,返回True
。 - 时间复杂度:
O(m + n)
。其中字符串s
长度为m
,字符串呢t
长度为n
。 - 空间复杂度:
O(1)
。因为只定义了一个常量大小的辅助数组。
3. 算法实现
3.1. Python
Python
中字符串函数ord()
把字符转化为相对应的int
值
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
# 定义哈希表
record = [0] * 26
# 遍历s,记录对应字符出现次数+1
# key是i-'a'
for i in s:
record[ord(i) - ord('a')] += 1
# 遍历t,相对应的字符次数-1
for j in t:
record[ord(j) - ord('a')] -= 1
# 遍历record,检查是否都为0
for i in range(len(record)):
if record[i] != 0:
return False
return True
3.2. Java
class Solution {
public boolean isAnagram(String s, String t) {
// hash table
int[] record = new int[26];
for (int i = 0; i < s.length(); i++) {
record[s.charAt(i) - 'a'] ++;
}
for (int j = 0; j < t.length(); j++) {
record[t.charAt(j) - 'a'] --;
}
for (int i = 0; i < 26; i++) {
if (record[i] != 0) {
return false;
}
}
return true;
}
}
0349. 两个数组的交集 Intersection of Two Arrays
1. 题目描述
给定两个数组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] 也是可通过的
2. 解题思路
- 本题可以使用暴力枚举法,时间复杂度为
O(n^2)
- 使用哈希法进行优化
- 注意题目要求输出结果是唯一的,说明需要对输出结果进行去重,同时可以不考虑输出结果的顺序
2.1 哈希表的选择
- 使用数组做哈希表的前提条件是明确知道哈希表的大小,本题中原始题目没有限制数组的范围,所以不可以用数组做哈希表;后续题目改进,限制了
0 <= nums1[i], nums2[i] <= 1000
,才可以用数组做哈希表 - 本题中学习使用
set
做哈希表,set
是一个无序的没有重复的集合,符合题目要求的输出结果唯一。 set
和数组相比,占用空间更大,速度更慢。
2.2 具体算法
- 定义一个集合
record
用来存储两个数组交集的元素 - 遍历数组
nums1
,定义第一个集合set1
作为哈希表存储出现过的次数 - 再遍历数组
nums2
,如果出现在set1
中,就把出现过的元素存储在record
中 - 最后把集合
record
转换为list
- 时间复杂度:
O(m + n)
。其中字符串num1
长度为m
,字符串呢nums2
长度为n
。 - 空间复杂度:
O(1)
。因为只定义了一个辅助集合。
3. 算法实现
3.1 Python - Intersection()
- 直接使用库函数
set.intersection()
,返回的set
转为列表
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1).intersection(set(nums2)))
3.2 Python - set做哈希表
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
record = set()
set1 = set()
# 遍历nums1
for num in nums1:
set1.add(num)
# 遍历nums2
for num in nums2:
if num in set1:
record.add(num)
# set -> list
return list(record)
3.3 Python - 数组做哈希表
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
record = set()
# 数组做哈希表
hash = [0] * 1001
# 出现在nums1的元素在hash中做记录
for num in nums1:
hash[num] = 1
# nums2中出现的元素,如果nums1也出现过,record记录
for num in nums2:
if hash[num] == 1:
record.add(num)
# set -> list
return list(record)
3.3 Java - hashset 做哈希表
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> record = new HashSet<>();
// hashset做哈希表
HashSet<Integer> set1 = new HashSet<>();
// 遍历nums1
for (int num1: nums1) {
set1.add(num1);
}
// 遍历nums2
for (int num2: nums2) {
if (set1.contains(num2)) {
record.add(num2);
}
}
// set -> list
int len = record.size();
int[] res = new int[len];
int i = 0;
for (int num: record) {
res[i] = num;
i++;
}
return res;
}
}
0202. 快乐数 Happy Number
1. 题目描述
编写一个算法来判断一个数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
2. 解题思路
- 当要快速判断一个元素是否在集合里时,考虑使用 哈希表。
- 题目说明,在求和的过程中可能会无限循环,即求得的和
sum
可能会重复出现,所以使用 哈希表 判断;如果sum
重复出现,就return false
,否则一直找到sum == 1
为止。 - 求和的过程需要用到对数值各个位上的单数操作。
- 时间复杂度:
O(log(n))
。 - 空间复杂度:
O(log(n))
。
具体算法:
- 定义辅助方法
get_sum
计算每个位数平方的和 - 定义一个集合
record
用来存储每次计算出来的和sum
,并且判断如果本次计算的和sum
已经出现在record
中,说明出现了无限循环,返回false
;如果sum == 1
,返回true
。
3. 算法实现
3.1 Python - getSum()辅助方法
- 注意在辅助方法
getSum()
中,遍历的条件为n = n // 10
,而不是n /= 10
,因为前者规定了向下取整,最终n
会等于0
class Solution:
def isHappy(self, n: int) -> bool:
# set做哈希表
hash = set()
while True:
n = self.getSum(n)
if n == 1:
return True
elif n in hash: # 判断是否有无限循环
return False
hash.add(n)
def getSum(self, n: int) -> int:
sum = 0
while n != 0:
dig = n % 10
sum += dig ** 2
n = n // 10
return sum
3.2 Java - getSum()辅助方法
class Solution {
public boolean isHappy(int n) {
Set<Integer> record = new HashSet<>();
while (n != 1) {
if (record.contains(n)) {
return false;
} else {
record.add(n);
n = this.getSum(n);
}
}
return true;
}
private int getSum(int n) {
int sum = 0;
while (n > 0) {
int digit = n % 10;
sum += digit * digit;
n = n / 10;
}
return sum;
}
}
3.3 Python - 使用集合 + 精简
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while n != 1:
n = sum(int(i) ** 2 for i in str(n))
if n in record:
return False
else:
record.add(n)
return True
3.4 Python - 使用数组
class Solution:
def isHappy(self, n: int) -> bool:
record = []
while n != 1:
n = sum(int(i) ** 2 for i in str(n))
if n in record:
return False
else:
record.append(n)
return True
0001. 两数之和 Two Sum
1. 题目描述
给定一个整数数组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]
示例3:
输入:nums = [3,3], target = 6
输出:[0,1]
2. 解题思路
2.1 什么时候使用哈希法
- 当查询一个元素是否出现过,或者一个元素是否出现在集合里面时,使用哈希法
- 本题中,遍历数组
num
时,除了知道每个元素的值,还要知道所对应的数组下标,适合用key-value
结构来存放,key
用来存放数组元素,value
存放数组元素的下标
2.2 使用数组array
和集合set
做哈希表的局限
- 使用数组
array
做哈希表时,数组大小受限;而且如果元素很少,而哈希值太大,会造成 空间浪费 - 使用集合
set
时,里面只能有一个key
。本题需要返回x
和y
的下标,所以不能用set
2.3 选择map
做哈希表
map
是一种 key-value 的数据结构- 使用
map
用来存储访问过的元素,用来在遍历数组时,记录之前遍历的哪些元素和相对应的下标,才能找到与当前元素匹配的元素(即两个元素相加等于target
) map
的存储结构为:{key: 数据元素, value: 元素所对应的下标}
- 在遍历数组时,
map
中查询所需要的和当前元素相匹配的元素,如果有,返回两个匹配元素的下标对;如果没有,把当前元素加入map
中。 -
- 时间复杂度:
O(n)
。
- 时间复杂度:
- 空间复杂度:
O(n)
。
2.4 具体算法
- 定义哈希表
record
用来存储数组nums
中的元素和所对应的下标;哈希表中key
为元素的值,value
为元素的下标 - 遍历数组
nums
,计算该元素nums[i]
与target
之间的差值,记为diff
- 查询哈希表,如果表中有
key
等于差值diff
的元素,那该元素符合条件,读取该元素的下标,即key == diff
元素所对应的value
;如果表中没有查询到,则表中插入该元素nums[i]
的数值和下标 - 返回两个符合条件元素的下标
3. 算法实现
3.1 Python - 暴力法
- 时间复杂度为
O(n^2)
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
3.2 Python - 使用字典
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 字典做哈希表
# key:num,value:下标
record = dict()
res = list()
for i in range(len(nums)):
diff = target - nums[i]
# 判断diff是否出现在哈希表中
if diff in record:
res.append(record[diff])
res.append(i)
else:
record[nums[i]] = i
return res
3.3 Java - 使用Map
- Java中
HashMap
常用方法:- 构造函数:
Map<Key, Velue> map = new HashMap<>();
- 是否包含
key
:map.containsKey(Key)
- 根据
key
读取对应的value
:map.get(Key)
- 插入新的
key-value
值:map.put(Key, Value)
- 构造函数:
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2]; // 返回res
// 用map做哈希表
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int diff = target - nums[i];
// 寻找对应的元素
if (map.containsKey(diff)) {
res[0] = i;
res[1] = map.get(diff);
break;
} else {
map.put(nums[i], i); // key - nums[i], value - i
}
}
return res;
}
}