前言
学习一下算法题吧
一、爬楼梯
题目描述
思路发现
爬楼梯这道题,通过读题,发现它和初中高中的数学题里的规律推导题类似,要发现它的计算格式
当n=1时, f(n) = 1
当n=2时, f(n) = 2
当n=3 时, f(n) = 3
当n = 4 时, f(n) = 5
……依此类推得到
1、 递归解法
递归的关键是递归的出口条件,分基线条件和终止条件,如果没设置好出口,将出现是循环的情况,就像俄罗斯套娃一样。
像上面公式的f(1) =1,f(2)=2就是基线条件。
如果,再给函数一个设定,最多递归20次,超过20次就终止,这就是终止条件
由上面推导格式,使用递归方式解题应该是比较简明的方法。
let climbStairs = function(n) {
if(n == 1) return 1;
if(n== 2 ) return 2;
return climbStairs(n-1)+climbStairs(n-2);
};
这样的解法,看上去简单清晰,但是它进行了大量的重复计算,容易造成时间复杂度的增加,不信你看它就超时了。
为什么说会有重复计算你,看下图,由颜色的就是重复计算的地方
2、 递归优化解法
考虑时间复杂度,我们可以对上面的做法进行优化,将每次计算的值用Map对象存起来。这样如果下一轮计算的时候,map.get(n) 有值就可以直接返回,而不用进行重复的计算。
/**
* @param {number} n
* @return {number}
*/
let map = new Map()
let climbStairs = function(n) {
if(n == 1) return 1;
if(n== 2 ) return 2;
if(map.get(n) != null) return map.get(n)
else{
let result = climbStairs(n-1)+climbStairs(n-2)
map.set(n,result)
return result;
}
};
这样时间复杂度就是O(n)了
3、 循环解法
一切可以用递归方法解决的题,都可以用迭代循环的方式来解决,循环的方式就多声明三个变量来进行替换和累加。
/**
* @param {number} n
* @return {number}
*/
let climbStairs = function(n) {
if(n==1) return 1;
if(n==2) return 2;
let result = 0, first = 1, second =2;
for(let i = 3;i <=n ;++i){
result = first + second;
first = second;
second = result;
}
return result;
};
4、 数组方式的循环解法
利用数组存值的特性,也是用循环叠加的方式,不过这样空间复杂度会是O(n),上面几种空间复杂度是O(1).
let climbStairs = function (n) {
const memo = [];
memo[1] = 1;
memo[2] = 2;
for (let i = 3; i <= n; i++) {
memo[i] = memo[i - 1] + memo[i - 2];
}
return memo[n];
};
以上是爬楼梯的四种解题方法,可能会有其他解题的方法,到这里我们大概学习了递归的关键是公式推导和设置递归出口,可以设置缓存减少重复计算,可以利用数组的一些方法特点来存值。
二、斐波那契数列
题目描述
解题思路
这题和爬楼梯是类似的,甚至它还给出了解题函数方程,不过有个最大值条件,答案需要取模
1、 递归解法
在笔试题中,可能会这样去写,不过时间复杂度高,而且在这道题中有限制答案要取模
**
* @param {number} n
* @return {number}
*/
let fib = function(n) {
if(n==0) return 0
if(n==1) return 1
return fib(n-1) + fib(n-2)
};
2、 循环解法
这样才是比较正确的做法。
/**
* @param {number} n
* @return {number}
*/
let fib = function (n) {
const MOD = 1000000007;
if (n == 0) return 0
if (n == 1) return 1
let result = 0, first = 0, second = 1;
for (let i = 2; i <= n; ++i) {
result = (first + second) % MOD;
first = second;
second = result
}
return result;
};
三、两数之和
题目描述
1、暴力求解
直接暴力的做法就是循环嵌套找出结果,像这样子,时间复杂度为O(n^2)
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let result = [];
for(let i = 0; i< nums.length; i++) {
for(let j = i + 1; j < nums.length; j++){
if(nums[i] + nums[j] == target){
result[0] = i;
result[1] = j;
return result;
}
}
}
return result;
};
定义一个数组来保存原数组的两个值的下标两次循环嵌套,在通过if(nums[i] + nums[j] == target)找出对应的两个值与目标值target相等,然后返回数组下标,
2、使用Map对象优化
JavaScript中的Map对象就是和Java中HashMap对象是一样的,通用是使用缓存策略来降低重复计算,达到降低时间复杂度的作用。像下面这样时间复杂度就是O(n)
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let map = new Map()
let result = [];
for(let i = 0; i< nums.length; i++) {
let another = target - nums[i]
let anotherIndex = map.get(another)
if(anotherIndex != null) {
result[0] = anotherIndex
result[1] = i
break
}else{
map.set(nums[i], i);
}
}
return result;
};
我们定义了map 对象进行计算缓存,将nums数组对应的值作为map 的key, 将数组的下标数为map对象的的value,
例如: nums = [2,8,6,9], target= 11;
那么第一轮i=0: another = 9, anotherIndex = map.get(9) = null, 进入else ,此时 map= {2=>0}
那么第二轮i=1: another = 3, anotherIndex = map.get(3) = null, 进入else ,此时 map= {2=>0,8=>1}
那么第三轮i=2: another = 5, anotherIndex = map.get(5) = null, 进入else ,此时 map= {2=>0,8=>1,6=>2}
那么第四轮i=3: another = 2, anotherIndex = map.get(2) = 0, 进入if ,此时 map= {2=>0,8=>1,6=>2}
结果返回 [0,3]
总结
以上是三道简单级的leetcode Hot100, 主要用到了递归、计算缓存策略用到map对象,通过缓存计算结果,降低时间复杂度
往期优质文章推荐 Vue3+Vite+TS基于Element plus 二次封装业务组件(含Vue3知识点)