题目链接:454. 四数相加 II - 力扣(LeetCode)
思路:
- 首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。
- 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
- 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
- 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 count 就可以了
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
ab = dict()
for i in range(len(nums1)):
for j in range(len(nums2)):
if nums1[i] + nums2[j] not in ab.keys():
ab[nums1[i] + nums2[j]] = 1
else:
ab[nums1[i] + nums2[j]] += 1
count = 0
for i in range(len(nums3)):
for j in range(len(nums4)):
if 0 - nums3[i] - nums4[j] in ab.keys():
count += ab[0 - nums3[i] - nums4[j]]
return count
# 代码随想录
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
# use a dict to store the elements in nums1 and nums2 and their sum
hashmap = dict()
for n1 in nums1:
for n2 in nums2:
if n1 + n2 in hashmap:
hashmap[n1+n2] += 1
else:
hashmap[n1+n2] = 1
# if the -(a+b) exists in nums3 and nums4, we shall add the count
count = 0
for n3 in nums3:
for n4 in nums4:
key = - n3 - n4
if key in hashmap:
count += hashmap[key]
return count
思路:
首先第一想法是可以用字典将magazine中的每个字母的个数保存下来,再用ransomNote中的对应字母去做减法,如果出现字母不全或个数不够的情况即返回false否则返回true。
但其实本题给出了只有小写字母,那么利用ASCⅡ码将26字母分别索引为0-25放在数组中存储个数会更加节省内存。
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
dict_ = dict()
for m in magazine:
if m in dict_.keys():
dict_[m] += 1
else:
dict_[m] = 1
for n in ransomNote:
if n in dict_.keys():
dict_[n] -= 1
if dict_[n] < 0:
return False
else:
return False
return True
# 代码随想录 版本一 使用数组作为哈希表
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
arr = [0] * 26
for x in magazine: # 记录 magazine里各个字符出现次数
arr[ord(x) - ord('a')] += 1
for x in ransomNote: # 在arr里对应的字符个数做--操作
if arr[ord(x) - ord('a')] == 0: # 如果没有出现过直接返回
return False
else:
arr[ord(x) - ord('a')] -= 1
return True
# 版本二 使用defaultdict
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
from collections import defaultdict
hashmap = defaultdict(int)
for x in magazine:
hashmap[x] += 1
for x in ransomNote:
value = hashmap.get(x)
if value is None or value == 0:
return False
else:
hashmap[x] -= 1
return True
# 版本三
class Solution(object):
def canConstruct(self, ransomNote, magazine):
"""
:type ransomNote: str
:type magazine: str
:rtype: bool
"""
# use a dict to store the number of letter occurance in ransomNote
hashmap = dict()
for s in ransomNote:
if s in hashmap:
hashmap[s] += 1
else:
hashmap[s] = 1
# check if the letter we need can be found in magazine
for l in magazine:
if l in hashmap:
hashmap[l] -= 1
for key in hashmap:
if hashmap[key] > 0:
return False
return True
# 版本四
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
c1 = collections.Counter(ransomNote)
c2 = collections.Counter(magazine)
x = c1 - c2
#x只保留值大于0的符号,当c1里面的符号个数小于c2时,不会被保留
#所以x只保留下了,magazine不能表达的
if(len(x)==0):
return True
else:
return False
思路:
在固有思维之下,第一反应是使用哈希法解决问题,结果完成了一半,题目中的要求三元组不可重复没有完成,时间复杂度同样是O(n²),但是哈希法去重较为麻烦,思路如下(输出答案对但是力扣提交不通过):
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
dict_1 = dict()
dict_2 = dict()
list1 = []
list2 = []
for i in range(len(nums)): # 记录nums[i] 与nums[j]之和,对于和相等的组合只保存一次
for j in range(i+1, len(nums)):
m = nums[i] + nums[j]
if m not in dict_1:
dict_1[m] = i
dict_2[m] = j
for index, n in enumerate(nums): # 挑选出总和为0且i, j, k互不相等的组合
if -n in dict_1 and dict_1[-n] != index:
list1 = []
list1.append(nums[dict_1[-n]])
list1.append(nums[dict_2[-n]])
list1.append(nums[index])
list1.sort() # 排序处理,用于后续的去重
for p in list2:
if list1 == p:
list2.remove(p)
list2.append(list1)
return list2
另外一种解法是双指针法,在此题中双指针法比哈希法更为高效。
首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。时间复杂度:O(n^2)。
# 代码随想录 版本一
class Solution:
def threeSum(self, nums):
ans = []
n = len(nums)
nums.sort()
# 找出a + b + c = 0
# a = nums[i], b = nums[left], c = nums[right]
for i in range(n):
left = i + 1
right = n - 1
# 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if nums[i] > 0:
break
if i >= 1 and nums[i] == nums[i - 1]: # 去重a
continue
while left < right:
total = nums[i] + nums[left] + nums[right]
if total > 0:
right -= 1
elif total < 0:
left += 1
else:
ans.append([nums[i], nums[left], nums[right]])
# 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while left != right and nums[left] == nums[left + 1]: left += 1
while left != right and nums[right] == nums[right - 1]: right -= 1
left += 1
right -= 1
return ans
# 版本二
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
if len(nums) < 3: return []
nums, res = sorted(nums), []
for i in range(len(nums) - 2):
cur, l, r = nums[i], i + 1, len(nums) - 1
if res != [] and res[-1][0] == cur: continue # Drop duplicates for the first time.
while l < r:
if cur + nums[l] + nums[r] == 0:
res.append([cur, nums[l], nums[r]])
# Drop duplicates for the second time in interation of l & r. Only used when target situation occurs, because that is the reason for dropping duplicates.
while l < r - 1 and nums[l] == nums[l + 1]:
l += 1
while r > l + 1 and nums[r] == nums[r - 1]:
r -= 1
if cur + nums[l] + nums[r] > 0:
r -= 1
else:
l += 1
return res
思路:
四数之和,和15.三数之和 (opens new window)是一个思路,都是使用双指针法, 基本解法就是在15.三数之和 (opens new window)的基础上再套一层for循环。
但是有一些细节需要注意,例如: 不要判断nums[k] > target
就返回了,三数之和 可以通过 nums[i] > 0
就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1]
,target
是-10
,不能因为-4 > -10
而跳过。但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)
就可以了。
15.三数之和 (opens new window)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。
那么一样的道理,五数之和、六数之和等等都采用这种解法。
对于15.三数之和 (opens new window)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。
之前我们讲过哈希表的经典题目:454.四数相加II (opens new window),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。
而454.四数相加II (opens new window)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少!
# 双指针法
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
res = []
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]: continue # 对nums[i]去重
for k in range(i+1, n):
if k > i + 1 and nums[k] == nums[k-1]: continue # 对nums[k]去重
p = k + 1
q = n - 1
while p < q:
if nums[i] + nums[k] + nums[p] + nums[q] > target: q -= 1
elif nums[i] + nums[k] + nums[p] + nums[q] < target: p += 1
else:
res.append([nums[i], nums[k], nums[p], nums[q]])
# 对nums[p]和nums[q]去重
while p < q and nums[p] == nums[p + 1]: p += 1
while p < q and nums[q] == nums[q - 1]: q -= 1
p += 1
q -= 1
return res
回顾使用了双指针法的题目:
双指针法将时间复杂度:O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下:
链表相关双指针题目: