「这是我参与11月更文挑战的8天,活动详情查看:2021最后一次更文挑战」。
前言
一直都计划学习数据结构与基本算法,但是平时都看一阵停一阵。现在决心坚持下去,我准备从LeetCode的HOT100开始,每天完成1~2道习题,希望通过这种方式养成持续学习的习惯。因为我是做iOS开发的,主要是用Objective-C语言,最近也在学习Swift,所以本系列的题解都将使用swift语言完成,本文更新的是LeetCode中HOT100的第8题015三数之和。
题目
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
提示:
0 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5
分析
本题的目的很简单,从整数数组中找出三个元素之和为0的组合。这让我想起来了我们之前曾做过的一道题,LeetCode之HOT100--001两数之和,这是从整数数组中找出两数之和为指定值的组合,当时我们采用的是用空间换时间的方法进行题解的,大家感兴趣的可以点击链接进行查看。
空间换时间法
看到本题的第一想法就是将之前的 两数之和 的思路用上,那么我们可以从左往右依次固定某一个元素,然后用其右边的元素数组中查找是否有两个元素之和等于当前固定的元素的相反数,如果存在,则将该组合加入结果集。具体思路如下:
- 初始化当前元素curIndex为0
- 从当前元素右侧的元素数组中查找是否有两个元素的和为指定值 -nums[curIndex],(这里可以使用LeetCode之HOT100--001两数之和中的方法进行),如果存在,则将curIndex对应的元素和满足条件的两个元素一起加入结果集
- curIndex右移一位,继续步骤1~步骤3,直到curIndex右侧的元素少于2个结束
该方法思路上很简单,执行起来时间复杂度为O(n2),空间复杂度为O(n)。
先排序,后遍历法
上述的方法是curIndex一定是从前往后,要到最后倒数第二个元素才能结束。本题的题解采用另一种方法,对上述方法进行进一步优化,那就是先排序,后遍历的方法,排序后因为数组是元素是大小有序的,所以我们可以更方便地判断是否需要继续循环判断。具体思路如下:\
1. 先对数组进行排序(一般编程语言都提供了自带的排序算法的API)
2. 初始化当前元素curIndex = 0,
3. 如果nums[curIndex] > 0,因为数组有序,则其右侧的左右元素也均 >0,所以不存在当前元素与右侧的另外两个元素之和等于0,结束。如果nums[curIndex] <= 0,则从当前元素右侧的元素数组中查找是否有两个元素能与当前元素组成和为0的组合,由于数组元素有序,所以可以采用双指针发进行判断 \
3.1 初始化left为当前元素的右边第一个元素下标curIndex + 1, right为最后一个元素下标sortNums.count - 1
3.2 如果nums[curIndex] + nums[left] + nums[right] = 0,则将nums[curIndex]、nums[left]、nums[right]保存进结果集,然后left右移到不等于原先left的值
如果nums[curIndex] + nums[left] + nums[right] > 0,表明总和需要减小,所以将right左移到不等于原先right的值
如果nums[curIndex] + nums[left] + nums[right] < 0,表明总和需要增大,所以将left右移到不等于原先left的值
3.3 如果left < right,则继续3.2 ~ 3.3,否则步骤3结束
4. curIndex右移右移到不等于当前值的下一个元素,如果nums[curIndex] <= 0,则继续步骤3,否则结束
题解
class KLLC015 {
func threeSum(_ nums: [Int]) -> [[Int]] {
if nums.count < 3 {
return [[Int]]()
}
//保存答案的数组
var ans = [[Int]]()
//排序
var sortNums = nums
sortNums.sort()
//初始化 curIndex
var curIndex = 0
while curIndex < (nums.count - 2) && sortNums[curIndex] <= 0 {
//双指针法,从curIndex右侧寻找两个元素和curIndex对应的元素之和为0的组合
var left = curIndex + 1, right = sortNums.count - 1
while left < right {
let sum = sortNums[curIndex] + sortNums[left] + sortNums[right]
if sum == 0 {
//满足条件则保存,然后将left右移
ans.append([sortNums[curIndex], sortNums[left], sortNums[right]])
repeat {
left += 1
} while (left < right) && (sortNums[left] == sortNums[left-1])
} else if sum > 0 {
//sum > 0,需要将right左移
right -= 1
} else {
//sum < 0,需要将left右移
left += 1
}
}
//将curIndex右移到不等于当前值的下一个元素
repeat {
curIndex += 1
}while (curIndex < nums.count) && sortNums[curIndex] == sortNums[curIndex-1]
}
return ans
}
}