【算法初探】前端学算法之两数之和

144 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 19 天,点击查看活动详情

前一篇文章中,我们通过js实现了二分查找。今天我们继续来看一下leetCode上的真题,顺便一起来回顾一下上一篇中的相关内容。

总结来说就是,给定一个递增的数组,和一个指定的值,通过数组中两个数相加,最终的和是这个给定的值,让我们通过js来实现这么一发方法,下面我们一起看一下常规的解题的方案。

常规思路

通过循环嵌套,找到数组中的一个数,然后继续去遍历下一个数,以此来进行求和,并判断最终的值与指定的值是否相等,这样的实现思路的时间复杂度是O(n^2),基本是不可以的,具体我们可以一起来看一下代码,如下:

/**
 * 寻找和为 N 的两个数 (嵌套循环)
 * @param arr number[]
 * @param n number
 * @return number[]
 */
const findTwoNumber1 = (arr: number[], n: number): number[] => {
    // 需要返回两个数在数组中的值
    const rest: number[] = [];
    
    // 获取数组的长度
    const len = arr.length;
    // 如果数组为空,则直接返回
    if (len === 0) return rest;
    
    // 通过循环来遍历数组,时间复杂度 O(n^2)
    for (let i = 0; i < len - 1; i++) {
        // 获取当前值
        const n1 = arr[i];
        // 标记是否得到了结果
        let flag = false;
        
        // 二次循环来查找第二个值
        for (let j = i + 1; j < len; j++) {
            const n2 = arr[j];
            // 如果两数之和等于指定的值
            if (n1 + n2 === n) {
                rest.push(n1);
                rest.push(n2);
                // 标记为以找到
                flag = true;
                break;
            }
        }
        
        // 如果已经查找到了两个数,则直接跳出循序
        if (flag) break;
    }
    
    
    // 如果没有找到两数之和与指定值相等的值,则直接返回空数组
    return rest;
};

// 功能测试
const arr = [1, 2, 4, 7, 11, 15];
const target = 15;
console.log(findTwoNumber1(arr, target)); // [4, 11]

具体的执行效果可以狠戳这里

虽然我们通过嵌套循环实现了这个题目,但是因为用到了嵌套循环,因此这个算法的时间复杂度是O(n^2),基本算是不可用的,因此我们还需要用另外一种思路来实现这个题目。

二分思路

在前面的题干描述中,我们知道了这个数组是递增的,因为我们可以利用这个特性,在数组中线随便找两个数组,如果它们的和大于给定的值,则需要向前面继续查找;如果它们的和小于给定的值,则需要向后进行查找。这个思路是不是跟我们在前一篇文章中说的二分查找很类似。下面我们一起来看一下用二分思路进行优化有的代码,如下:

/**
 * 寻找和为 N 的两个数 (双指针)
 * @param arr number[]
 * @param n number
 * @return number[]
 */
const findTwoNumber2 = (arr: number[], n: number): number[] => {
    // 需要返回两个数在数组中的值
    const rest: number[] = [];
    
    // 获取数组的长度
    const len = arr.length;
    // 如果数组为空,则直接返回
    if (len === 0) return rest;
    
    // 定义双指针
    let i = 0;  // 头指针
    let j = len - 1; // 尾指针
    
    while (i < j) {
        const n1 = arr[i];
        const n2 = arr[j];
        const sum = n1 + n2;
        // 如果sum大于n,则尾指针要向前移动
        if (sum > n) {
            j--;
        } else if (sum < n) {
            // 如果sum小于n,则头指针要向后移动
            i++;
        } else {
            // sum 等于 n,则表示找到了
            rest.push(n1);
            rest.push(n2);
            break;
        }
    }
    
    return rest;
}

// 功能测试
const arr = [1, 2, 4, 7, 11, 15];
const target = 15;
console.log(findTwoNumber2(arr, target)); // [4, 11]

具体的执行效果可以狠戳这里

通过双指针的思路思想实现的代码,它的时间复杂度是O(n),下面我们一起来做一下性能分析。

性能分析

我们还是通过性能测试来对这两个方法进行分析,代码如下:

const arr = [1, 2, 4, 7, 11, 15];
const target = 15;

// 方法一
console.time('findTwoNumber1');
for (let i = 0; i < 100 * 10000; i++) {
    findTwoNumber1(arr, target);
}
console.timeEnd('findTwoNumber1');

// 方法二
console.time('findTwoNumber2');
for (let i = 0; i < 100 * 10000; i++) {
    findTwoNumber2(arr, target);
}
console.timeEnd('findTwoNumber2');

当我们进行测试是数组的长度约长时,循环嵌套的实现思路跟二分实现的思路,它们之间的差别就是O(n)

最后

当一个算法的时间复杂度达到了O(n^2),那这个算法基本就是不可以的算法。当我们遇到有序的数据结构时,首先想到的一定是二分。如果我们要优化嵌套循环的题目,可以考虑"双指针"。

最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家

往期回顾

【算法初探】前端学算法之用JS实现二分查找

【算法初探】前端学算法之旋转数组(1)

【算法初探】前端学算法之旋转数组(2)

【算法初探】前端学算法之有效的括号

【算法初探】前端学算法之用栈实现队列

【算法初探】前端学算法之反转单向链表

【算法初探】前端学算法之用链表实现队列