朋友去面试头条的前端,二面,第一个题目,就是下面这张纸,面试官也会给一张纸要求手写答案。如果你看到就立刻知道了怎么解答,那可以直接忽略本文了,或者有更好的写法,直接评论区留言吧,谢谢~
我的学习风格总是想着循序渐进,下面从几个小题开始。
1 简单题目热身(简单场景递归一下)
题目:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们组成的数组。 你不能重复利用这个数组中同样的元素。
其实就是一个数组,找出两个元素,他们的和是指定的值,但是不能一直就一个元素,也就是说加数组只有4的话,不能返回结果是[4,4]这样
var nums = [8, 9, 2, 15, 7, 1]
var target = 9
var twoSum = function(nums, target) {
var result = []
for(var i=0; i<nums.length;i++){
for(var j=i+1; j<nums.length;j++){
if(nums[i]+nums[j] === target){
result.push([nums[i],nums[j]])
}
}
}
return result
}
console.log( twoSum(nums, target) )
复制代码
运行结果如下
代码很简单,也不用什么解释吧。不过要引出来的是下面一种解法,递归。
var nums = [8, 9, 2, 15, 7, 1]
var target = 9
var twoSum = function(nums, target) {
var result = []
var _sum = function(nums, target){
if(nums.length === 0){
return false
}
for(var i = 1; i < nums.length; i++){
if((nums[0] + nums[i]) === target){
result.push([nums[0] , nums[i]])
}
}
nums.splice(0,1) // 或者使用 nums.shift(),删除数组第一个元素
return _sum(nums, target)
}
_sum(nums, target)
return result
}
console.log( twoSum(nums, target) )
复制代码
运行结果如下,当然,和上述结果是一样的
小结 代码稍微多了一点,其实思想是一样的,不过想更好的解释递归,那么需要再把题目简化一下
题目:还是上面的题目,加上一个限定条件:你可以假设每种输入只会对应一个答案。也就是说对应这样的题干数组[11, 7, 10, 2] 只有 [7,2]这样一个结果。对于只有一个结果这样的情况,递归的写法代码会简练的多。
var twoSum = function(nums, target) {
// 1
var result = []
if(nums.length === 0){
// 2
return false
}
for(var i = 1; i < nums.length; i++){
if((nums[0] + nums[i]) === target){
result.push([nums[0], nums[i]])
// 3
return result
}
}
// 4
nums.shift()
// 5
return twoSum(nums, target)
}
console.log( twoSum(nums, target) )
复制代码
小结 :注释1 中,会把result置为[], 那岂不是每次递归调用这个方法时候,开始都是重置?答,因为前提条件已经改了,我们返回的结果只有一个 注释4 ,修改递归执行函数的参数,毕竟要每次不一样嘛要不然怎么进行下去。
提问,真的清楚每个 return 的作用吗?比如 注释5 那里的return不写的话,有什么区别?这个一定要清楚。
2 题目进阶(for循环?递归?)
根据上面例子,很容易想到3重循环,确实也是可以解决的,只是那个判定条件,有些low 并且假如是寻找长度为4子数组呢?那要for循环4次?显然不可取
3 回归主题(重点理解递归时候的return)
提问 ,如果用递归,这个问题怎么解决?
var candidates = [2, 3, 8, 4, 10, 15]
var target = 9
var combinationSum2 = function(candidates, target) {
const buffer = [];
const result = [];
const backTrace = (index, target) => {
if(target == 0) {
return result.push(buffer.slice());
}
if(target < 0) {
return;
}
if(index === candidates.length) return;
buffer.push(candidates[index]);
backTrace(index + 1, target - candidates[index]);
buffer.pop();
backTrace(index + 1, target);
}
backTrace(0, target);
return result.filter(item => item.length === 3)
};
console.log(combinationSum2(candidates, target))
复制代码
运行结果如下
解析代码
- 第一次执行 backTrace
- 执行 backTrace(index, target) 方法index为0 target为9
- backTrace方法中,有三个终止条件,在第一次执行时候,这三个return没有一个会执行
- 此时 buffer 为 [2]
- 第二次执行 backTrace
- 执行 backTrace(index, target) 方法index为1 target为7
- 三个return没有执行
- 此时 buffer 为 [2,3]
- 第三次执行 backTrace
- 执行 backTrace(index, target) 方法index为2 target为4
- 三个return没有执行
- 此时 buffer 为 [2,3,8]
- 第四次执行 backTrace
- 执行 backTrace(index, target) 方法index为3 target为 -4
- 因为 target < 0 所以执行第二个return
- 在这次执行过程中 后面代码没有执行,buffer没变
- 返回到第三次执行 backTrace 的过程中
- 执行 buffer.pop()后 buffer为 [2,3]
- 执行backTrace(index + 1, target) 看上面第三次执行步骤,index为2,target为4
- 第五次执行 backTrace
- 执行 backTrace(index, target) 方法index为3 target为 4
- 三个return没执行
- 此时buffer为[2,3,4]
- 第六次执行 backTrace
- 执行 backTrace(index, target) 方法index为4 target为 0
- 执行第一个return , result为 [[2,3,4]]
- 返回到第五次执行 backTrace 的过程中
- 执行 buffer.pop()后 buffer为 [2,3]
- 执行backTrace(index + 1, target) 看上面第五次执行步骤,index为3,target为4
- 第七次执行 backTrace
- 执行 backTrace(index, target) 方法index为4 target为 4
- 三个return没执行
- 此时buffer为[2,3,10]
- 第八次执行 backTrace
- 执行 backTrace(index, target) 方法index为5 target为 -6
- ...
- ...
看到这里,相信已经明白了这个代码所做的事情,其实这就是回溯算法,不断试错,错误的话,回到上个状态,换参数继续试错下去。
其实,最最开始的题目,答案已经有了。因为我们上面的代码,最后返回的result中,含有多个和为目标值的数组,那么我们只需要再遍历一下这个result的每一项,筛选出长度符合要求的即可。