LeetCode #001# Two Sum详解(js描述)

索引

    1. 思路1:暴力搜索
    2. 思路2:聪明一点的搜索
    3. 思路3:利用HashMap巧解

问题描述:https://leetcode.com/problems/two-sum/

思路1:暴力搜索

一个很自然的想法就是暴力搜索了。根据排列组合原理,列举Cn取2对数字,逐对进行判断,效率是O(n^2-1/2n),代码如下:

var twoSum = function(nums, target) {
    for (let i = 0; i != nums.length; ++i) {
        for (let j = i + 1; j != nums.length; ++j) {
            if (nums[i] + nums[j] == target) {
                return [i, j];
            }
        }
    }
};

思路2:聪明一点的搜索

最开始感觉就这么简单一个问题,哪还有别的什么方法啊。。但是仔细想想输入规模一变大的话n^2也太恐怖了,应该有更快一点的方法。另外一个比较自然的思路就是:先将数组排序,这个nlgn内可以完成,然后弄两个下标分别指向头和尾,边做判断边根据判断结果缩小搜索范围,这样一般在扫描完整个数组之前(n以内)就可以找到相应元素了,不过需要额外的空间来保存元素和原下标间的映射关系。最终效率是O(nlgn)尾巴有点大,先放代码:

// O(nlgn)
var twoSum2 = function(nums, target) {
    let copy = nums.map((x, i) => {
        return {
            val: x,
            index: i,
        };
    }); // 保存元素的下标值
    copy.sort((a, b) => a.val - b.val); // 按照元素值从小到大排序

    let i = 0, j = copy.length - 1;
    let sum;
    while ((sum = copy[i].val + copy[j].val) != target) {
        sum > target ? j-- : i++;
    }

    i = copy[i].index;
    j = copy[j].index;
    return i < j ? [i, j] : [j, i];
};

正确性我也是纠结了一会儿,毕竟数学很菜。。。所以给出一个乱七八糟。。又不严谨的。。。“证明”↓

我们证明这个循环不变式:按照从有序数组的外围朝向内侧的搜索顺序,如果(sum = copy[i].val + copy[j].val) != target那么要找的元素一定在i+1..j或者i..j-1范围中,至于变化i还是j取决于copy[i].val + copy[j].val相对target是大了还是小了:大了一定减j,小了一定加i。

初始化:单看i=0以及j=copy.length - 1;的话是显然成立的,不过这属于特殊情况,属于没路走了,只能i+1或者j-1。选取一个稍普通些的情形i+1..j-1(i=0且j=copy.length - 1),这个时候i和j都是可进可退的,那么当sum > target的时候为什么一定是j-1-1而不是i+1-1呢(这两种操作都会让sum更接近target)?一个直觉且没毛病的理由是i..j-1在上个(或者上上个)迭代已经判断过了,所以此路明显不通,所以就只能是j-1-1了。然而我们需要考察更普通的情形,连续n次迭代sum都小于target,于是i先加了n,之后m次迭代sum都大于target,j才随后减去m,那么在j逐步-1的过程中i可不可以尝试回退一步(-1,回退n步和1步情形都是一样的,假设回退一步方便讨论)呢?因为这么做确实也让sum变小了,并且容易知道i+n-1..j-m这对数并没有判断过(i累计加了n的时候j仍可能还在原地踏步)。不妨假设[i+n-1, j-m]就是原问题的解,即i前进到i+n又回退到i+n-1并且copy[i+n-1].val + copy[j-m].val == target成立。仔细考察“有序数组”这一前置条件,会发现倘若i和j是原问题的解,也就是copy[i].val + copy[j].val == target成立,那么copy[i].val + copy[任何大于j的下标值].val总是大于target,这是因为copy[任何大于j的下标值].val > copy[j].val,言下之意,考虑最终的情形,必定i和j是有一方会先到达要找的元素之一且在原地等待,不存在什么走过头再回头的情形,于是就推翻假设了。。。此时已经考虑了所有情况,因此循环不变式成立。当sum < target时类似。

保持:假设对于某个i..j(i>0且j<copy.length - 1)循环不变式成立,也就是要找的元素一定在i+1..j或者i..j-1中。那么再进行一次迭代,也就是在i+1..j或者i..j-1中搜索,这又回到了初始化的情形,所以循环不变式仍然成立。

终止:因为题目确保了一定有那么一对元素存在,迭代终止的时候就是sun == target找到答案的时候。

PS. 我扯了一堆网上看了一下发现好像。。。似乎有一个什么数学定理。。。。。。。。。。。。。。。。。

思路3:利用HashMap巧解

知道这个思路后感觉很easy。。然而一开始并不容易想到(仅限我。。哈哈。。。。),事实上在给出target和一个元素后,另外一个元素就已经随之确定了。因此可以构建一个map,用另外一个元素的值作为key(或者自己本身的值),而value部分可以用来存当前元素的下标,这样一来,只需要至多扫描一遍数组(或者一遍以内),就可以得到答案了。效率是O(n),典型的用空间换时间。js代码如下:

// O(n)
var twoSum3 = function(nums, target) {
    let obj = {};
    nums.forEach((x, i) => obj[target - x] = i);
    for (let i = 0; i != nums.length; ++i) {
        let j = obj[nums[i]];
        if (j != undefined && j != i) {
            return i < j ? [i, j] : [j, i];
        }
    }
};

// O(n),渐进性没有改善,不过去掉了一些尾巴
// Runtime: 52 ms, faster than 100.00% of JavaScript online submissions for Two Sum.
var twoSum4 = function(nums, target) {
    let obj = {}, j; // 用obj作为map
    for (let i = 0; i != nums.length; ++i) {
        if ((j = obj[nums[i]]) != undefined) {
            return [j, i];
        }
        obj[target - nums[i]] = i;
    }
};

// 这个和上一个是一样的,纯粹为了测试一下用obj作map快还是new Map()快。结果是慢了一点。
var twoSum5 = function(nums, target) {
    let map = new Map();
    let obj = {}, j;
    for (let i = 0; i != nums.length; ++i) {
        if ((j = map.get(nums[i])) != undefined) {
            return [j, i];
        }
        map.set(target - nums[i], i);
    }
};

原文地址:https://www.cnblogs.com/xkxf/p/10222738.html

时间: 2024-10-16 02:56:44

LeetCode #001# Two Sum详解(js描述)的相关文章

详解js面向对象编程

转自:http://segmentfault.com/a/1190000000713346 基本概念 ECMA关于对象的定义是:”无序属性的集合,其属性可以包含基本值.对象或者函数.“对象的每个属性或方法都有一个名字,而每个名字都映射到一个值. 类 在现实生活中,相似的对象之间往往都有一些共同的组成特征.类,实际上是对象的设计蓝图或者制作配方.我们能基于相同的类创建出许多不同的对象,这些对象又会含有各自的属性和方法. 封装 封装主要用于阐述对象中所包含(或封装的内容),它通常由两部分组成: 相关

详解js的bind、call、apply

详解js的bind.call.apply 说明 虽然bind.call.apply都是js很基础的一块知识,但是我从未认真总结过这三者的区别. 由于公司后端是用的微服务架构,又没有中间层对接,导致前端这边非常难于处理接口数据. 于是我在项目初期封装了一套薄弱的api方法充当中间层的作用,还有为了让后端接口字段命名统一,也单独封装了一套方法用来统一每个微服务字段返回名. 但是,随着业务的增加,代码出现强耦合现象,到最后薄弱的中间层已经撑不住了,连代码的审视都变得很麻烦. 最后不得以重新设计这套ap

【leetcode】Edit Distance 详解

下图为TI C6xx DSP Nyquist总线拓扑图,总线连接了master与slave,提供了高速的数据传输.有很多种速率不同的总线,如图中的红色方框,最高速总线为CPU/2 TeraNet SCR(即VBUSM SCR),带宽为256bit,其他低速总线为CPU/3,CPU/6,带宽参考图中所示.总线之间用Bridge(桥)连接,作用包括转换总线的速率,使之与所流向总线的速率相同等. 在具体应用中,各种速率的总线完全可以满足复杂的数据传输,而数据传输的瓶颈往往在于连接总线之间的Bridge

详解js变量、作用域及内存

详解js变量.作用域及内存 来源:伯乐在线 作者:trigkit4 原文出处: trigkit4 基本类型值有:undefined,NUll,Boolean,Number和String,这些类型分别在内存中占有固定的大小空间,他们的值保存在栈空间,我们通过按值来访问的. JavaScript 1 2 (1)值类型:数值.布尔值.null.undefined. (2)引用类型:对象.数组.函数. 如果赋值的是引用类型的值,则必须在堆内存中为这个值分配空间.由于这种值的大小不固定(对象有很多属性和方

详解js和jquery里的this关键字

详解js和jquery里的this关键字 js中的this 我们要记住:this永远指向函数运行时所在的对象!而不是函数被创建时所在的对象.this对象是在运行时基于函数的执行环境绑定的,在全局环境中,this等于window 先来看个例子: <script> var fullname = "Trigkit4"; var person = { fullname : 'Jack', prop:{ fullname : 'Blizzard', getFullname : fun

详解js中typeof、instanceof与constructor

详解js中typeof.instanceof与constructor typeof返回一个表达式的数据类型的字符串,返回结果为js基本的数据类型,包括number,boolean,string,object,undefined,function.语法为typeof(data) 或 typeof data instanceof则为判断一个对象是否为某一数据类型,或一个变量是否为一个对象的实例;返回boolean类型 语法为 o instanceof A 以下为综合实例: 1<script type

详解JS中Number()、parseInt()和parseFloat()的区别

转载:详解JS中Number().parseInt()和parseFloat()的区别 三者的作用: Number(): 可以用于任何数据类型转换成数值: parseInt().parseFloat(): 专门用于把字符串转换成数值: 一.Number( ): (1)如果是Boolean值,true和false将分别转换为1和0. (2)如果是数字值,只是简单的传入和返回. (3)如果是null值,返回0. (4)如果是undefined,返回NaN. (5)如果是字符串,遵循下列规则: 如果字

[LeetCode] 001. Two Sum (Medium) (C++/Java/Python)

索引:[LeetCode] Leetcode 题解索引 (C++/Java/Python/Sql) Github: https://github.com/illuz/leetcode 001.Two_Sum (Medium) 链接: 题目:https://oj.leetcode.com/problems/two-sum/ 代码(github):https://github.com/illuz/leetcode 题意: 一个数组中两个位置上的数的和恰为 target,求这两个位置. 分析: 暴力找

详解js中的闭包

前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以<javascript高级程序设计>里面的理论为基础.用拆分的方式,深入讲解一下对于闭包的理解,如果有不对请指正. 写在闭包之前 闭包的内部细节,依赖于函数被调用过程所发生的一系列事件为基础,所以有必要先弄清楚以下几个概念: 1. 执行环境和活动对象 ** - 执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的