四数相加
题目链接:leetcode
题目描述:给你四个整数数组 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+3,另一种是2+2. 由于1+3的遍历时间复杂度达到
O
(
n
3
)
O(n^3)
O(n3).因此我们考虑2+2. 将AB数组划分为一部分,CD为另一个。这里需要满足加和条件的频数,而不是下标,那就可以用字典来表示。其中key为两数之和的可能值,value则为两数之和出现的频数。
若dic1 表示AB形成的字典,遍历dic1,我们在dic2中找其相反数。若存在,count加上两数分别在两个字典的频数之积,最后返回count即可。
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
n = len(nums1)
dic1 = {}
for i in range(n):
for j in range(n):
if nums1[i] + nums2[j] in dic1:
dic1[nums1[i]+nums2[j]] += 1
else:
dic1[nums1[i]+nums2[j]] = 1
dic2 = {}
for i in range(n):
for j in range(n):
if nums3[i] + nums4[j] in dic2:
dic2[nums3[i]+nums4[j]] += 1
else:
dic2[nums3[i]+nums4[j]] = 1
count = 0
for i in dic1:
if -i in dic2:
count += dic1[i] * dic2[-i]
return count
赎金信
题目链接leetcode 383.
题目描述:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
类似于有效的字母异位词,无序且关心频数。可以考虑哈希表储存每个字母的频数。这里对magazine的字母进行统计,然后我们没有必要对ransomNote 也进行统计,只需遍历ransomNote,在magazine中对应的字母频数减一,若出现负数,则返回false。不对ransomNote进行统计是因为可能不需遍历完ransomNote即可知道结果。
哈希表的实现可以是数组,也可以是字典。数组的底层实现可能会快些。这里用字典来实现,需要注意的是当value为0时,需要将其key删除。
Python
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
dicMag = {}
for i in magazine:
if i in dicMag:
dicMag[i] += 1
else:
dicMag[i] = 1
for i in ransomNote:
if i not in dicMag:
return False
else:
dicMag[i] -= 1
if dicMag[i] == 0:
del dicMag[i]
return True
三数之和
题目链接 leetcode 15
题目描述:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
首先考虑暴力解法。 因为题目要求不能有重复的三元组,而这个三元组的自由度为2,也就是说元组的其中两位相等即判为重复。如果我们对元组排了序,就只需要比较前两位。基于这个思想,我们来简单了解暴力解法。
首先对序列进行排序,然后三个嵌套循环
i
,
j
,
k
,
i
<
j
<
k
≤
n
i,j,k, i< j <k \le n
i,j,k,i<j<k≤n.
去重的操作:对于
i
,
j
,
k
i,j,k
i,j,k来说,如果与前一位一致,则跳过。特别的,如果找到了一个满足条件的三元组,那么
k
k
k就不需要继续遍历了,直接
j
j
j 遍历下一个。显然暴力解法时间复杂度为
O
(
n
3
)
O(n^3)
O(n3).
接下来考虑双指针解法。一样需要对序列先排序。
i
i
i 是一样的遍历和去重的操作。然后是定义左右指针
l
=
i
+
1
,
r
=
n
−
1
l = i+1,r= n-1
l=i+1,r=n−1。 双指针的思想是,比较此时三数和与目标值,如果大于目标值,则通过左移右指针来减少;如果小于目标值,则通过右移左指针来增大;若三数和恰为目标值,那么储存这三位数,并且同时更新左右指针(注意去重操作)。
当然,我们也要思考这方法是否有遗漏(尚未参透严谨的证明)。与暴力解法进行比较,第一层循环一样,我们比较
j
,
k
j,k
j,k 和
l
,
r
l,r
l,r。最初
l
,
r
l,r
l,r 在数组两端,此时如是需要增大,即右移, 则只能
l
l
l 右移,同时说明在这个
i
i
i 下,
l
l
l 在最左端,无论
r
r
r 在哪个位置都不满足条件,相当于遍历了所有
r
r
r,在给定此时
i
,
l
i,l
i,l的情况下。 若是需要左移也是同理。 此时
l
,
r
l,r
l,r 位置
[
i
,
−
,
l
,
…
,
r
]
[i,-,l,\dots,r]
[i,−,l,…,r]
这时若需左移,显然只能
r
r
r 左移(
l
l
l左移已被遍历过)。右移也只能
l
l
l.然后
l
,
r
l,r
l,r相对位置
[
i
,
−
,
−
,
l
…
,
r
]
o
r
[
i
,
−
,
l
,
…
,
r
,
−
]
[i,-,-,l\dots,r] \quad or\quad [i,-,l,\dots,r,-]
[i,−,−,l…,r]or[i,−,l,…,r,−]
然后同理,一样只能
l
l
l右移,
r
r
r左移
Python 代码实现
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)-2):
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i-1]:
continue
l,r = i+1, len(nums)-1
while l < r:
sum = nums[i] + nums[l] + nums[r]
if sum > 0:
r -= 1
elif sum < 0:
l += 1
else:
result.append((nums[i],nums[l],nums[r]))
while l < r and nums[l] == nums[l+1]: l += 1
while l < r and nums[r] == nums[r-1]: r -= 1
l,r = l+1,r-1
return result
四数之和
题目链接:leetcode18
题目描述:给你一个由 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
你可以按 任意顺序 返回答案
题解与三数之和类似,只是在外面多套了一个循环。同时目标值给位任意给定 target 值而不是0. 这就使得预剪枝判定发生变化。nums[i] >0 改为 nums[i] > target and (nums[i] >= 0 or target >=0).
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
result = []
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 j > i +1 and nums[j] == nums[j-1]:
continue
l,r = j+1,n-1
while l < r:
sum = nums[i] + nums[j] + nums[l] + nums[r]
if sum > target:
r -= 1
elif sum < target:
l += 1
else:
result.append((nums[i],nums[j],nums[l],nums[r]))
while l < r and nums[l] == nums[l+1]: l += 1
while l < r and nums[r] == nums[r-1]: r -= 1
l,r = l+1,r-1
return result