一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
1、事例
数组[1, 2, 3, 4, 5, 7, 9, 14]找出和为13的两个数 => [4, 9]
2、分析问题
- 这是一个数组
- 数组中之存在一组可以满足和为13的两个数
- 这是一个递增的数组
- 输出结果是一个数组
如果只是为了满足找出两数之和,最简单最先冒出我们脑子的想法可能就是双重遍历,找出满足条件的两个数,然后用数组的形式输出,但是这中方式是否会存在复杂度的问题呢?大概猜想起时间复杂度可能是O(n^2),而这种复杂度一般是“不可用”的。那么我们先来写一下用这种方式来进行一下,最后看一下如何优化或者能否找到其他的解题方法。
3、解题一
- 循环嵌套代码
//嵌套循环
function findTwoNumbers1(arr: number[], n:number): number[]{
const res: number[] = [];
//获取数组长度
const leng = arr.length;
//如果数组长度为0,那么直接返回空数组
if(leng === 0) return res
//开始嵌套循环
for(let i = 0; i < leng; i++){
const startVal = arr[i];
let hasFind = false;
for(let j = i + 1; j < leng; j++){//这里j从i+1开始,因为之前的硬计算过了,这样写也可以减少一些循环
const endVal = arr[j];
const val = startVal + endVal;
if(val == n){
res.push(startVal, endVal)
hasFind = true
break //及时跳出循环
}
}
if(hasFind) break; //及时跳出循环
}
return res
}
//功能测试
const arrArr = [1, 2, 3, 4, 5, 7, 9, 14];
console.log(findTwoNumbers1(arrArr, 13)) //[4,9] 符合预期
- 复杂度分析 前端重时间轻空间,我们直接看时间复杂度,这里有两个for循环进行嵌套,虽然里边的循环是从i+1开始,不是完全的O(n^2),但是数量级依然是O(n^2),然而在数据量较大的情况下,或者更直接的说O(n^2)时间复杂度的算法是不可取的,是不可使用的,所以这种方法虽然能够实现,但是此方法非常不可取。
4、解题二
- 分析问题 在上边的解题中,我们考虑到了数组,考虑到了两数之和,但是有序这个条件,我们似乎并没有使用到。记得上一篇文章中提到过:凡有序,必二分,那么这里是不是可以考虑一下使用二分查找呢?如果直接使用二分查找,那么每次循环都会将数组减少一半,而这里是求两个数的和,那么如此情况下,我们必然不能直接使用二分查找。
因为是有序递增(暂时定义为递增)数组,那么是不是可以考虑用数组中第一个数min和最后一个数max相加得到val,查看一下结果呢?如果val大于n,说明min+max需要减小一些,那么就把max的值向左移动一位,如果val小于n,说明min+max需要增加一些,那么就把min向右移动一位。这样做是不是也可以呢?
- 按照上边的想法,我们来写一下代码
function findTwoNumbers2(arr: number[], n: number): number[]{
const res: number[] = [];
const length = arr.length;
let i = 0;
let j = length - 1;
//当i<j的时候就一直循环相加做对比
while(i < j){
const min = arr[i];
const max = arr[j];
const val = min + max;
if(val > n){
//j需要向左移动
j--
}else if(val < n){
//i需要向右移动
i++
}else{
res.push(min)
res.push(max)
break;
}
}
return res;
}
//功能测试
console.log(findTwoNumbers2(arrArr, 13)) //[4,9] 符合预期
这种写法,其实利用了二分的思想,是有一种双指针的应用。 这里为什么没有判断length==0呢?因为在while循环中,如果length是0后者1的话,不会进入while循环,会直接返回空数组res:[];这样看来是不是解题一种的length==0的判断也能去掉呢?
- 复杂度分析 时间复杂度是O(n),因为这里只有一个循环。不存在时间复杂度为O(n)的js方法,所以综合考虑其时间复杂度是O(n); 空间复杂度也是O(n)。
5、性能对比
const arrTest3 = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
const target3 = 39;
console.time("findTwoNumbers1")
for(let i=0; i< 1000 * 10000; i++){
findTwoNumbers1(arrTest3,target3)
}
console.timeEnd("findTwoNumbers1")//findTwoNumbers1: 1783.185791015625 ms
console.time("findTwoNumbers2")
for(let i=0; i< 1000 * 10000; i++){
findTwoNumbers2(arrTest3,target3)
}
console.timeEnd("findTwoNumbers2")//findTwoNumbers2: 59.72412109375 ms
通过执行时间的对比,我们可以发现二者根本不是一个数量级的。所以我们可以看到解题二的方法更好,也能证明O(n^2)的时间复杂度不可取;
6、总结
- 凡有序,必二分。这里更确切的指代二分的思想,不一定是二分查找
- 遇到嵌套循环,可以考虑是否可以改成双指针的方法。