代码随想录算法训练营第七天 | 454. 四数相加II、383. 赎金信、15. 三数之和、18.四数之和
今日学习的文章链接和视频链接
参考代码随想录
自己看到题目的第一想法
今天的题目前几天才写过,希望都可以不看笔记写出来
自己实现过程中遇到哪些困难
- 454. 四数相加II秒了,尝试使用
dict.setdefault()
函数,没有写出来,还是踏踏实实写if
判断吧 - 383. 赎金信也秒了,字母异位词的高级版,感觉对于字符串中用数组做哈希表更熟练了
- 15. 三数之和和18.四数之和思路基本一致,用了双指针降低时间复杂度,都是先遍历,循环内双指针,然后判断;难点在于判断去重和剪枝操作,都是看了笔记才想出来,还需要多练习
今日收获,记录一下自己的学习时长
- 应打卡7月4日,7月5日补打卡,学习时长2hr
- 栈和队列*1,232.用栈实现队列
- 因为笨蛋操作,一不小心第四天的打卡没了,懒得再写一遍了
0454. 四数相加II 4Sum II
1. 题目描述
给你四个整数数组nums1
、nums2
、nums3
和nums4
,数组长度都是n
,请你计算有多少个元组(i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例1:
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例2:
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1
2. 解题思路
- 可以使用暴力枚举法,时间复杂度为
O(n^4)
,超出时间限制 - 因为题目给出四个独立数组
nums1
、nums2
、nums3
和nums4
,只用考虑nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
,不用考虑在同一个数组中有重复的四个元素相加等于0
的情况,所以适合使用 哈希法 - 时间复杂度:
O(n^2)
。 - 空间复杂度:
O(n^2)
。
具体算法:
- 定义
map
做哈希表,先遍历nums1
和nums2
数组;哈希表中key
为nums1
和nums2
两数之和,即key = nums1[i] + nums2[j]
,而value
为两数之和出现的次数 - 定义变量
count
,统计nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
出现的次数 - 再遍历
nums3
和nums4
数组,如果在map
中找到相匹配的元素,即此时key = 0 - nums3[k] - nums4[l]
,就用计数器count
统计map
中key
所对应的次数value
,即count = map[key]
3. 具体实现
3.1. Python - 使用字典
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
# 字典做哈希表
map = dict()
# 先遍历 nums1 + nums2
for n1 in nums1:
for n2 in nums2:
# 统计 nums1 + nums2 出现的次数
if n1 + n2 in map:
map[n1 + n2] += 1
else:
map[n1 + n2] = 1
count = 0
# 再遍历 nums3 + nums4
for n3 in nums3:
for n4 in nums4:
# 寻找匹配的 nums1 + nums2
# nums1 + nums2 + nums3 + nums4 = 0
diff = 0 - n3 - n4
if diff in map:
count += map[diff]
return count
3.2 Java
hashmap.getDefault(Object key, V defaultValue)
: 如果map
中key
,返回相对应的value
;如果没有,就返回初始值defaultValue
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int count = 0;
// map哈希表
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
// nums1,nums2和的出现次数
for (int n1: nums1) {
for (int n2: nums2) {
int sum = n1 + n2;
// key - n1 + n2; value - 出现次数
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
}
// nums3, nums4寻找匹配元素
for (int n3: nums3) {
for (int n4: nums4) {
// n1 + n2 + n3 + n4 = 0
int diff = 0 - n3 - n4;
count += map.getOrDefault(diff, 0);
}
}
return count;
}
}
0383. 赎金信 Ransom Note
1. 题目描述
给你两个字符串:ransomNote
和magazine
,判断ransomNote
能不能由magazine
里面的字符构成。
如果可以,返回true
;否则返回false
。
magazine
中的每个字符只能在ransomNote
中使用一次。
示例1:
输入:ransomNote = "a", magazine = "b"
输出:false
示例2:
输入:ransomNote = "aa", magazine = "ab"
输出:false
示例3:
输入:ransomNote = "aa", magazine = "aab"
输出:true
提示:
字符串`magazing`和`ransomNote`只包含英文小写字母
2. 解题思路
- 本题跟0242_有效的字母异构词很像,区别在判断字符串
magazine
中的字符能否组成字符串ransomNote
,注意题目要求字符串magazine
中的每个字符不可以重复利用 - 可以使用暴力枚举法,时间复杂度为
O(n^2)
。 - 使用哈希法定义哈希表记录字符串
magazine
中每个字符出现的次数 - 哈希表的选择:选择 数组 做哈希表,因为本题中哈希表的大小固定,有26个英文字母,所以定义一个长度为26的数组做哈希表,比使用
map
更加省时间 - 时间复杂度:
O(n)
。 - 空间复杂度:
O(1)
。
具体算法:
- 定义一个大小为 26 的数组
record
,因为从小写字母a
到z
一共有26个字符;key
为英文小写字母,value
为magazine
每个字符出现次数 - 把字符映射到哈希表的索引下标上,因为字符
a
到字符z
的ASCII是26个连续的数值,所以字符a
映射为下标0,相应的字符z
映射为下标25。 - 先遍历字符串
magazine
;遍历时,将magazine[i] - 'a'
所在的元素做+1
操作,不需要记住a
的ASCII,只要求出一个相对值就可以 - 再遍历字符串
ransomNote
;同样,在遍历时将ransomNote[i] - 'a'
所在的元素做-1
操作 - 最后判断在哈希表中,
ransomNote
每个字符所对应的出现次数;如果次数小于0
,说明magazine
中的字符不够组成ransomNote
,返回false
,否则返回true
。
3. 算法实现
3.1 Python - 数组
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
# 数组做哈希表
record = [0] * 26
for m in magazine:
record[ord(m) - ord('a')] += 1
for r in ransomNote:
record[ord(r) - ord('a')] -= 1
if record[ord(r) - ord('a')] < 0: # 说明magazine字符不够组成ransomNote
return False
return True
3.2 Python - 字典
Python
字典get(Object key, V defaultValue)
:: 如果字典中key
,返回相对应的value
;如果没有,就返回初始值defaultValue
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
record = dict()
for m in magazine:
record[m] = record.get(m, 0) + 1
for r in ransomNote:
record[r] = record.get(r, 0) - 1
if record[r] < 0:
return False
return True
3.3 Java - 数组
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
// magazine长度小于ransomNote
if (magazine.length() < ransomNote.length()) {
return false;
}
// 数组做哈希表
int[] record = new int[26];
for (int i = 0; i < magazine.length(); i++) {
record[magazine.charAt(i) - 'a'] ++;
}
for (int j = 0; j < ransomNote.length(); j++) {
record[ransomNote.charAt(j) - 'a'] --;
if (record[ransomNote.charAt(j) - 'a'] < 0) {
return false;
}
}
return true;
}
}
0015. 三数之和 3Sum
1. 题目描述
给你一个整数数组nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
2. 解题思路
- 本题可以使用 哈希法,类似0454_四数相加,用两层循环遍历数组
nums
确定nums[i]
和nums[j]
的值,存储在哈希表里,然后判断0 - nums[i] - nums[j]
是否出现过。 - 哈希法局限:题目要求不能出现重复的三元组,所以需要去重,时间复杂度为
O(n^2)
,非常费时间 - 本体也可以使用双指针法
2.1. 双指针法
- 首先将数组
nums
排序,然后用第一层for
循环遍历,i
从下标为0
的地方开始 - 定义
left
指针为i + 1
的位置上,定义right
指针为数组结尾的位置上,即len(nums) -1
- 如果
i + left + right > 0
,说明此时的三数之和 大于 条件,因为数组nums
已经排序过,所以把right
向左移动一位;如果i + left + right < 0
,说明此时的三数之和 小于 条件,所以把left
向右移动一位;如此循环直到left
和right
相遇为止 - 去重:题目要求不能出现重复的三元组,但是三元组内的元素可以重复
- 对于
i
的去重来说,为了避免跳过元素导致错过匹配的三元组,应该把i
和i - 1
相比较,如果i
和i - 1
相同,跳过i
(而不是比较i
和i + 1
而跳过i + 1
) - 对于
left
和right
的去重:类似于i
的去重,在双指针更新后,right
和right - 1
做判断,如果重复跳过right
;left
和left + 1
做判断,如果重复跳过left
- 对于
- 注意:使用双指针法一定要先对数组 排序,会改变数组的下标,所以如果题目要求的是返回符合条件元素的下标(例如0001_双数之和),就不能使用双指针法,而应该使用哈希法
3. 算法实现
- 时间复杂度:
O(n^2)
。 - 空间复杂度:
O(1)
。
3.1 Python - 双指针法
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = [] # 存储返回的结果
nums.sort() # 排序
for i in range(len(nums)):
# 如果i>0, 不需要继续检查
if nums[i] > 0:
return res
# i的去重
if i > 0 and nums[i] == nums[i - 1]:
continue
# 双指针
left = i + 1
right = len(nums) - 1
# 出界条件 left = right, 双指针相遇
while left < right:
sum = nums[i] + nums[left] + nums[right]
if sum > 0:
right -= 1
elif sum < 0:
left += 1
else: # i + left + right == 0
res.append([nums[i], nums[left], nums[right]])
# 去重逻辑出现在找到第一个三元组后
# right去重
while left < right and nums[right] == nums[right - 1]:
right -= 1
# left去重
while left < right and nums[left] == nums[left + 1]:
left += 1
# 双指针更新,继续遍历
left += 1
right -= 1
return res
3.2 Java - 双指针法
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<> (); // return result
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 排序后,如果i>0, 不需要继续检查
if (nums[i] > 0) {
return res;
}
// i去重
if (i > 0 && nums[i] == nums[i-1]) {
continue;
}
// 双指针
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right --;
} else if (sum < 0) {
left ++;
} else {
// sum == 0
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑出现在找到第一个三元组后
// right 去重
while (left < right && nums[right] == nums[right - 1]) {
right --;
}
// left 去重
while (left < right && nums[left] == nums[left + 1]) {
left ++;
}
left ++;
right --;
}
}
}
return res;
}
}
0018. 四数之和 4Sum
1. 题目描述
给你一个由n
个整数组成的数组nums
,和一个目标值target
。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
2. 解题思路
- 基本思路和0013_三数之和一致,使用双指针法
- 三数之和中先
for
循环遍历nums[i]
作为确定值,然后循环内定义双指针循环,找到nums[i] + nums[left] + nums[right] == 0
;本题中多加一个循环,食用双重for
循环确定nums[i] + nums[j]
,然后循环内定义双指针找到nums[i] + nums[j] + nums[left] + nums[right] == target
- 三数之和原本三重循环,时间复杂度为
O(n^3)
,使用双指针后降低为O(n^2)
;本题中原本四重循环,时间复杂度为O(n^4)
,使用双指针后降低为O(n^3)
- 去重与剪枝:
- 一级剪枝:在数组
nums
排序之后,不能判断nums[i] > target
就返回,因为target
可能是任意值;但是可以使用剪枝处理,判断nums[i] > target && (nums[i]> 0 || target >= 0)
- 二级剪枝:
nums[i] + nums[j] > target && nums[i] + nums[j] >= 0
可以优化为nums[i] + nums[j] > target && nums[j] >= 0
,因为如果nums[j]
大于0
的话,后面的数都一定大于nums[j]
,所以不符合条件
- 一级剪枝:在数组
3. 算法实现
- 时间复杂度:
O(n^3)
- 空间复杂度:
O(1)
3.1 Python - 双指针
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
n = len(nums)
nums.sort()
for i in range(n):
# 一级剪枝
if nums[i] > target and (nums[i] > 0 or target > 0):
break
# 一级去重
if i > 0 and nums[i] == nums[i-1]:
continue
for j in range(i+1, n):
# 二级剪枝
if nums[i] + nums[j] > target and target > 0:
break
# 二级去重
if j > i + 1 and nums[j] == nums[j-1]:
continue
left, right = j+1, n-1
while left < right:
sum = nums[i] + nums[j] + nums[left] + nums[right]
if sum > target:
right -= 1
elif sum < target:
left += 1
else:
# sum == target
res.append([nums[i], nums[j], nums[left], nums[right]])
# right去重
while left < right and nums[right] == nums[right-1]:
right -= 1
# right去重
while left < right and nums[left] == nums[left+1]:
left += 1
left += 1
right -= 1
return res
3.2 Java - 双指针
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (nums[i] > target && (nums[i] > 0 || target > 0)) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < n; j++) {
if (nums[i] + nums[j] > target && nums[j] > 0) {
break;
}
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int left = j + 1;
int right = n - 1;
while (left < right) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right --;
} else if (sum < target) {
left ++;
} else {
res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left < right && nums[right] == nums[right - 1]) {
right --;
}
while (left < right && nums[left] == nums[left + 1]) {
left ++;
}
right --;
left ++;
}
}
}
}
return res;
}
}