# 哈希表算法(3)

8 min read
Table of Contents

三数之和和四数之和被放在哈希表章节中,但是其实不适合用哈希表来做,剪枝操作很麻烦。这里主要用双指针法来做。双指针法的好处是可以省一次 for 循环。 代码随想录 | 三数之和四数之和

15. 三数之和

15. 三数之和

中等

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != 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 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

双指针法

15.三数之和

首先将数组排序,然后有一层for循环,i 从下标0的地方开始,lefti+1 的位置上, rightlen-1 的位置上。

在数组中找到abc使得 a+b+c==0,这里 a = nums[i], b = nums[left], c = nums[right]。

首先判断 nums[i] 是否大于 0, 如果 a 大于0,那么 a+b+c 一定大于0。那么如何移动 left 和 right 呢?

这里需要注意 target > 0 才可以这样写。如果 target < 0,a < 0 也是可以找到三元组的。 例如 [-4, -1, 0], target == 5

如果 a+b+c > 0 说明三数之和大了,因为数组排序过,所以 right 应该向左移动,这样三数之和才能小一点。

如果 a+b+c < 0 说明三数之和小了,应该让 left 向右移动,这样三数之和才能大一点。

a+b+c == 0 时开始收集结果,在收集到一个结果之后去重,right 也是一样。

for left < right && nums[left] == nums[left+1] {
left++
}

在 nums[left] 和 下一个数不同时,至少保证一次指针移动

完整代码

func threeSum(nums []int) [][]int {
sort.Ints(nums)
len := len(nums)
result := [][]int{}
for i := 0; i < len - 2; i++ {
if nums[0] > 0 {
break
}
// 如果 nums[i] 和前一个重复,则进行下一次循环
if i > 0 && nums[i] == nums[i - 1] {
continue
}
left := i + 1
right := len - 1
for left < right { // 没有=,因为 left == right 时是一个数,不是三数之和了
if nums[i] + nums[left] + nums[right] == 0 {
result = append(result, []int{nums[i], nums[left], nums[right]})
// 在收集到一个结果后去重
for left < right && nums[left] == nums[left+1] {
left++
}
for left < right && nums[right] == nums[right-1] {
right--
}
// 在 nums[left] 和 下一个数不同时,至少保证一次指针移动
left++
right--
} else if nums[i] + nums[left] + nums[right] > 0 {
right--
} else {
left++
}
}
}
return result
}

哈希解法(未完成)

func threeSum(nums []int) [][]int {
res := make([][]int, 0)
sort.Ints(nums)
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for i := 0; i < len(nums); i++ {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if nums[i] > 0 {
break
}
// 三元组元素a去重
if i > 0 && nums[i] == nums[i-1] {
continue
}
set := make(map[int]struct{})
for j := i + 1; j < len(nums); j++ {
// 三元组元素b去重
if j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2] {
continue
}
c := -nums[i] - nums[j]
if _, ok := set[c]; ok {
res = append(res, []int{nums[i], nums[j], c})
// 三元组元素c去重
delete(set, c)
} else {
set[nums[j]] = struct{}{}
}
}
}
return res
}

18.四数之和

18. 四数之和

中等

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • 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]]

提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

双指针法

这道题同样使用双指针的思路,就是在 三数之和 的基础上再套一层 for 循环。但是需要注意,四数之和 的 target 不是 0 是可以变的,这时就不能直接根据 n1 > target 来确定没有符合要求的四元组了。因为 target 可能为负数,所以四数之和也可能是越加越小的。

如果 target < 0,a < 0 也是可以找到三元组的。 例如 [-4, -1, 0], target == 5

可以把 a + b 当成一个整体来剪枝,但是剪枝是非必要操作。

if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}

另外在去重的时候,边界也有所更改。例如对 b 去重时,(i := k + 1)需要 i > k+1,因为要取 nums[i - 1], i 必须大于 k 。

完整代码

func fourSum(nums []int, target int) [][]int {
len := len(nums)
res := [][]int{}
if len < 4 {
return nil
}
sort.Ints(nums)
for k := 0; k < len - 3; k++ {
n1 := nums[k]
// if n1 > target { // 不能这样写,因为 target 可能是负数
// break
// }
if k > 0 && n1 == nums[k-1] { // 对 nums[k] 去重
continue
}
for i := k + 1; i < len - 2; i++ {
n2 := nums[i]
if i > k + 1 && n2 == nums[i-1] { // 对 nums[i] 去重
continue
}
left := i + 1
right := len - 1
for left < right {
n3 := nums[left]
n4 := nums[right]
sum := n1 + n2 + n3 + n4
if sum < target {
left++
} else if sum > target {
right--
} else {
res = append(res, []int{n1, n2, n3, n4})
for left < right && n3 == nums[left+1] { // left 去重
left++
}
for left < right && n4 == nums[right-1] { // right 去重
right--
}
// 找到答案时,双指针同时靠近
left++
right--
}
}
}
}
return res
}

哈希表章节总结

xmind

hash
My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts

Comments