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
  • -10^5 <= nums[i] <= 10^5

解题分析及思路:

方法:排序 + 双指针

思路:

本题可使用排序 + 双指针的方法来解决。具体步骤如下:

  1. 对数组进行排序,方便后续去重和双指针操作。
  2. 遍历数组,固定一个元素 nums[i]
  3. 对于每个固定的元素,使用双指针 leftright 分别指向 i + 1 和数组的最后一个元素。
  4. 计算 sum = nums[i] + nums[left] + nums[right]
  • 如果 sum < 0,说明需要增大和,将 left 指针右移。
  • 如果 sum > 0,说明需要减小和,将 right 指针左移。
  • 如果 sum == 0,说明找到了一组满足条件的三元组,将其加入结果集,并将 leftright 指针分别右移和左移,同时进行去重操作。
  1. 在遍历过程中,需要对 nums[i] 进行去重操作,避免结果中出现重复的三元组。

去重逻辑 去重分两部分:

  • 固定元素 nums[i] 去重:代码 if i > 0 && nums[i] == nums[i-1] { continue },避免以相同 nums[i] 计算重复三元组。
  • 双指针元素去重:找到满足条件三元组后
    • left 右移时,若 left < right && left > i+1 && nums[left] == nums[left-1] 则继续右移;
    • right 左移时,若 left < right && right < len(nums)-1 && nums[right] == nums[right+1] 则继续左移,避免重复计算。
func threeSum(nums []int) [][]int {
	sort.Ints(nums)
	var res [][]int
	for i := 0; i < len(nums)-2; i++ {
		if i > 0 && nums[i] == nums[i-1] {
			continue
		}
		var left, right = i + 1, len(nums) - 1
		for left < right {
			for left < right && left > i+1 && nums[left] == nums[left-1] {
				left++
			}
			for left < right && right < len(nums)-1 && nums[right] == nums[right+1] {
				right--
			}
			if left >= right {
				continue
			}
			sum := nums[i] + nums[left] + nums[right]
			if sum < 0 {
				left++
			} else if sum > 0 {
				right--
			} else {
				res = append(res, []int{nums[i], nums[left], nums[right]})
				left++
				right--
			}
		}
	}
	return res
}

复杂度:

  • 时间复杂度:O(n^2),其中 n 是数组的长度。排序的时间复杂度为 O(n log n),双指针遍历的时间复杂度为 O(n^2),因此总的时间复杂度为 O(n^2)
  • 空间复杂度:O(log n),主要是排序所需的额外空间。

执行结果:

  • 执行耗时:44 ms,击败了10.67% 的Go用户
  • 内存消耗:9.8 MB,击败了69.63% 的Go用户

通过次数 2.3M 提交次数 5.9M 通过率 39.4%

Related Posts

1026. 节点与其祖先之间的最大差值

## 题目描述:给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。(如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)示例 1: ![](/img/leetcode/1026节点与其祖先之间的最大差值/tmp-tre

read more

1038. 从二叉搜索树到更大和树

## 题目描述:给定一个二叉搜索树 root (BST),请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。提醒一下, 二叉搜索树 满足下列约束条件:- 节点的左子树仅包含键 小于 节点键的节点。 - 节点的右子树仅包含键 大于 节点键的节点。 - 左右子树也必须是二叉搜索树。*示例 1:***![](/img/leetcode/

read more

105. 从前序与中序遍历序列构造二叉树

## 题目描述:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1: ``` 输入: preorder = [3,9,20,15,7], inorder = [

read more

106. 从中序与后序遍历序列构造二叉树

## 题目描述:给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。示例 1: ``` 输入:inorder = [9,3,15,20,7], postorder

read more

109. 有序链表转换二叉搜索树

## 题目描述:给定一个单链表的头节点 head ,其中的元素 按升序排序 ,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。示例 1: ``` 输入: head = [-10,-3,0,5,9] 输出: [0,-3,9

read more

114. 二叉树展开为链表

## 题目描述:给你二叉树的根结点 root ,请你将它展开为一个单链表:- 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。示例 1: ``` 输入:root

read more