哈希表算法(3)

三数之和和四数之和被放在哈希表章节中,但是其实不适合用哈希表来做,剪枝操作很麻烦。这里主要用双指针法来做。双指针法的好处是可以省一次 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

归档 友链
arrow_up
theme