引言:前端时间有一学弟问我LeetCode上这两题如何解,以及思路是怎样来的
🤷♂️:学长,这有两道关于数字之和的题目,你能给我讲解一下吗?
🐱💻:嗯,这两道题目很类似,如果你掌握了思路,那么你肯定也会N数之和的算法
🤷♂️:可是我总找不到他们的相同出发点🤢,到底是怎么样的思路呢
🐱💻:这样吧,我们从最简单的开始,我给你出一道题,两数之和,你试试看能不能做出来
两数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在两个元素 a,b,使得 a + b = 0 ?请你找出所有和为 0 且不重复的二元组。
注意:答案中不可以包含重复的二元组
/*输入:*/ nums = [-1,0,1,2,-1,-4]
/*输出:*/ [[-1,1]]
示例 2:
/*输入:*/ nums = [-6,3,0,2,1,0,-3]
/*输出:*/ [[-3,3],[0,0]]
示例 3:
/*输入:*/ nums = [0]
/*输出:*/ []
🐱💻:第一步你知道应该做什么吗?
🤷♂️:…嗯,我觉得是先把数组排序,这样如果有相同的元素就会集中在一起,我们能清除的知道有多少个
🐱💻:没错,假设经过排序后,我们得到了如下数组,你看出什么来了吗?
[ -3 -1 0 1 2 3 ]
🤷♂️:啊,数组有序之后,我一下就能看出哪两个数组之和为0,分别是-3和3,-1和1
🐱💻:眼睛看当然容易,程序如何写呢?我先用箭头把他们标出来,显然你的任务是先找到-3和3,再找到-1和1
[ -3 -1 0 1 2 3 ]
▲ ▲ ▲ ▲
│ │ │ │
🤷♂️:我想想…,这里是两个数两个数找,好像有点像双指针!一头一尾,判断他们的和是否为0,然后轻松找到-3和3,可以我如何过度到-1和1呢?
[ -3 -1 0 1 2 3 ]
▲ ▲
│ │
🐱💻:双指针的精髓就是你如何选择移动指针,是选择移动左指针还是右指针,我再给你一个例子,你看看你选择移动左指针还是右指针:
[ -5 -4 -2 0 1 4 ]
▲ ▲
│ │
🤷♂️:肯定是移动左指针,然后就找到-4和4了,嘻嘻~
🐱💻:没错,我来告诉你你为什么会选择移动左指针,假设左、右指针指向的数为a和b,因为此时 a + b = -1,小于0,因此我们需要让a或者b其中一个数变大,才有可能让a + b ==0,因为这是一个有序数组,恰好a右边的数字一定大于a,所以选择让左指针向右移动
🤷♂️:(恍然大悟)原来是这样啊,如果a + b == 0的话,那就意味着左右指针都得向中心移动,因为只移动一个指针下一次相加比较肯定不会等于0(数组无重复元素)
🐱💻:你试着写一下程序吧
🤷♂️:
function TwoSum(arr) {
arr.sort((a, b) => a - b)
let res = []
let i = 0
let j = arr.length - 1
while (i < j) {
let result = arr[i] + arr[j]
if (result == 0) {
res.push([arr[i], arr[j]])
i++
j--
} else if (result < 0) {
i++
} else {
j--
}
}
return res
}
🐱💻:没错,看来学弟的领悟力挺高的👏,那假如数组里面有重复的元素呢,假如按照上面代码的方法,得到的数组里面会有重复的结果,类似[[-2,2],[-2,2],[-1,1]]
,这就会不符合我们题目的要求
🤷♂️:那该怎么办呢?去除重复元素不就好啦?
🐱💻:这当然可以,但是去除相重复内容的数组并不方便,费事费力,我们要从源头再思考一下,能不能当重复的元素第一次遇到,我们才push
进入数组呢?
🤷♂️:我第一个想到的是做一个标记,但好像这个并不方便
🐱💻:很简单,只要加上两条语句,如果之前碰到了,就直接移动指针,看起来就好像没有这些重复元素一样
if (result == 0) {
res.push([arr[i], arr[j]])
while (nums[i] == nums[i + 1]) i++;
while (nums[j] == nums[j - 1]) j--;
i++
j--
}
🤷♂️:哇!原来对数组排序有这么妙的作用,以后不管三七二十一,遇到什么题目我都排序,嘻嘻~😜
🐱💻:我再给你加一点难度,要求a + b == target才push
进入数组,其中target是我给定的值,而不是a + b ==0
🤷♂️:嘻嘻~,学长终于问到我会的了,像这样就可以:
function TwoSum(arr, target) {
arr.sort((a, b) => a - b)
let res = []
let i = 0
let j = arr.length - 1
while (i < j) {
let result = arr[i] + arr[j]
if (result == target) { //0改为target
res.push([arr[i], arr[j]])
while (nums[i] == nums[i + 1]) i++;
while (nums[j] == nums[j - 1]) j--;
i++
j--
} else if (result < target) { //0改为target
i++
} else {
j--
}
}
return res
}
🐱💻:嗯,不错嘛,接下来你再看看这道三数之和的题目
🤷♂️:如果按照刚才的思路,这道题目是求三个数字的和,是不是得弄个三指针呢?左右分别放一个指针我知道,可是这第三个指针放哪里呢?
🐱💻:这样和你说吧,你知道二维和三维的关系吗?
🤷♂️:二维是一个面,三维是一个体,体是由多个面组成的
🐱💻:所以想要得到一个体,你得多次求"面"。而在这个题目中,意味着你得多次进行两数之和运算,假设题目输入了如下数组:
[ -4 -1 -1 0 1 2 ]
🤷♂️:我还是没想到怎么解题
🐱💻:这样吧,我给你四个小输入,求两数之和,让a+b等于后面的数字
1.
[ -1 -1 0 1 2 ] a+b == -4
2.
[ -1 0 1 2 ] a+b == -1
3.
[ 0 1 2 ] a+b == -1
4.
[ 1 2 ] a+b == 0
🤷♂️:嘿嘿,这个我刚才学会了,学长等我一下,我马上求出来:
1.
[ -1 -1 0 1 2 ] a+b == 4 //[]
2.
[ -1 0 1 2 ] a+b == 1 //[[-1,2],[0,1]]
3.
[ 0 1 2 ] a+b == 1 //[]
4.
[ 1 2 ] a+b == 0 //[]
🐱💻:然后我再告诉你刚才那个题目的答案,你发现什么了吗?
[ -4 -1 -1 0 1 2 ] //[[-1,-1,2][-1,0,1]]
🤷♂️:哇!我发现如果把target取反再加入右边的数组好像就是上面的答案了
1.
[ -1 -1 0 1 2 ] a+b == 4 //[]
2. ┌─────────────────────┐
[ -1 0 1 2 ] a+b ==│1 //[[-1,2],[0,1]] │ => [[-1,-1,2][-1,0,1]]
3. └─────────────────────┘
[ 0 1 2 ] a+b == 1 //[]
4.
[ 1 2 ] a+b == 0 //[]
🐱💻:你已经建立起了他们的联系,试着用循环解决一下这个问题
🤷♂️:(思考中…),我还是不会写😢
🐱💻:没关系,我先给你看我写的解析的流程图
-4 -1 -1 0 1 2
▲ ▲ ▲
│ │ │
k i j
-4 -1 -1 0 1 2
▲ ▲ ▲
│ │ │
k i j
-4 -1 -1 0 1 2
▲ ▲ ▲
│ │ │
k i j
-4 -1 -1 0 1 2
▲ ▲ ▲
│ │ │
k i j
-4 -1 -1 0 1 2
▲ ▲ ▲
│ │ │ => [-1,-1,2]
k i j
-4 -1 -1 0 1 2
▲ ▲ ▲
│ │ │ => [-1,0,1]
k i j
-4 -1 -1 0 1 2
▲
│ //Meet the same value, and jump
k
-4 -1 -1 0 1 2
▲ ▲ ▲
│ │ │
k i j
🤷♂️:哇!原来是这么一个流程,我好像有一点头绪了,(忙碌中…)
🤷♂️:是这样吗:
function TwoSum(arr) {
arr.sort((a, b) => a - b)
let res = []
for (let k = 0; k < arr.length; k++) {
if (k > 0 && arr[k] == arr[k - 1]) continue //消除k的重复
let i = k + 1
let j = arr.length - 1
while (i < j) {
let result = arr[k] + arr[i] + arr[j]
if (result == 0) {
res.push([arr[k], arr[i], arr[j]])
while (nums[i] == nums[i + 1]) i++; //消除i的重复
while (nums[j] == nums[j - 1]) j--; //消除j的重复
i++;
j--;
} else if (result < 0) {
i++;
} else {
j--;
}
}
}
return res
}
🐱💻:不错👍,如果你仔细地思考了我给你讲的这些,那么剩下的那道四数之和我相信你肯定也能够完成
🤷♂️:嗯嗯!我这就回去自己研究
(第二天)
🤷♂️:学长,我写出来了!你康康,四个指针[i,j,k,l]
var fourSum = function(nums, target) {
if (nums.length < 4) return []
nums.sort((a, b) => a - b)
let res = []
for (let i = 0; i < nums.length - 1; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue //消除i的重复
for (let j = i + 1; j < nums.length - 1; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue //消除j的重复
let k = j + 1
let l = nums.length - 1
while (k < l) {
let result = nums[i] + nums[j] + nums[k] + nums[l]
if (result === target) {
res.push([nums[i], nums[j], nums[k], nums[l]])
while (nums[k] == nums[k + 1]) k++; //消除k的重复
while (nums[l] == nums[l + 1]) l--; //消除l的重复
k++;
l--;
} else if (result > target) {
l--;
} else {
k++
}
}
}
}
return res
};
🐱💻:完美啊
🤷♂️:😎
如果这篇文章帮助到了你,欢迎点赞收藏加关注,后续将会推出更优质的内容~