本人一直在努力地积累Leetcode上用Python实现的题,并且会尽力讲清每道题的原理,绝不像其他某些博客简略地带过。
如果觉得讲的清楚,欢迎关注。
给定一个包含 n 个整数的数组 nums
和一个目标值 target
,判断 nums
中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target
相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
思路:很明显。四数之和与三数之和很像,但当我用四指针夹逼来做时发现超时了,但却有大神写的能通过。这说明就算是相同的算法,代码的冗杂程度也会很大程度上影响性能。所以说夹逼遍历,最重要的就是过滤!过滤!过滤!怎样把一定不符合条件的值跳过才是最能提升性能的。
但是先来看看我提交的代码:
1.字典查找法
总思路:把N4拆成2个N2。第一个for循环,先求后2个值可能的取值的所有情况,并且把它们储存在一个字典里,以和作为键。
第二个for,我们遍历前2个值所可能取的各种值,算出和并且检查target - onesum是否在我们的字典里,如果在,就说明我们找到了一个解。其实这种求几数之和的问题,都可以通过这种思路。比如说三数之和,现在我就想到根本不必用指针,算出所有后2个值所加的可能的和,然后用字典存,最后用target-第一个值去检查是否存在这样的键。
class Solution:
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
nums.sort()
ans = set()
sumans = {}
if len(nums) < 4:
return []
for i in range(2, len(nums) - 1):
for j in range(i+1, len(nums)):
onesum = nums[i] + nums[j]
if onesum not in sumans:
sumans[onesum] = [(i, j)]
else:
sumans[onesum].append((i, j))
for i in range(len(nums) - 3):
for j in range(i+1, len(nums) - 2):
onesum = nums[i] + nums[j]
if target - onesum in sumans:
for k in sumans[target - onesum]:
if k[0] > j:
ans.add((nums[i], nums[j], nums[k[0]], nums[k[1]]))
return [i for i in ans]
虽然说我们写出夹逼的方法,但确实有人写出了而且比我快,我们主要分析一下他们是怎么设置过滤条件的。
2.四指针夹逼法
class Solution:
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
if not nums:
return []
_4_sum_list = []
nums.sort()
if nums[-1]*4 < target:
return []
for i in range(len(nums)-3):
if nums[i]*4 > target:
break
if i==0 or nums[i]!= nums[i-1]:
ele = nums[i]
target_3_sum = target - ele
if nums[-1]*3 < target_3_sum:
continue
for j in range(i+1,len(nums)-2):
ele2 = nums[j]
if ele2*3 > target_3_sum:
break
if j==i+1 or ele2!= nums[j-1]:
target_2_sum = target_3_sum - ele2
point_left = j+1
point_right = len(nums)-1
while point_left < point_right:
if nums[point_left] + nums[point_right] > target_2_sum:
point_right -= 1
elif nums[point_left] + nums[point_right] < target_2_sum:
point_left += 1
else:
aaa = [ele, ele2,nums[point_left], nums[point_right]]
_4_sum_list.append(aaa)
point_left += 1
point_right -= 1
while point_left < point_right and nums[point_left] == nums[point_left-1]:
point_left += 1
while point_left < point_right and nums[point_right] == nums[point_right+1]:
point_right -= 1
return _4_sum_list
首先思路很清晰,i,j,point_left,point_right四个指针。
注意 因为经过了排序,i到point_right递增。
1.如果i也就是第一个指针的四倍大于等于target, break
2.如果最大一项的三倍小于还需要填充的和,则进入下个更大的i。
3.同理它这里的if保证了不重复计算相同的i.
4。剩下来的思路与三数之和大致相同。具体可参考我的另一篇博客。https://mp.csdn.net/postedit/80647482
反思总结:1.用字典查找法。先遍历求出后几个值的可能取值,并用字典去存储,最后去搜索target - nums[i]-nums[j]。
2.夹逼法。定4个指针,后2个指针用来夹逼。最重要的是要会过滤条件,否则它就是暴力法。