1、关于leetcode


这是第一篇关于leetcode的题解,就先扯点关于leetcode的话。

其实很早前就在博客园看到过leetcode一些题解,总以为跟一般OJ大同小异,直到最近点开了一篇博文Leetcode 编程训练,无意间点进leetcode的主页看了下,乖乖,居然能用JavaScript提交代码(还能用python、ruby等)!瞬间来了兴趣,一口气把几十道水题都切完了,代码放在了github,有兴趣的可以参考或者帮忙review一下。对于个人认为有意思的题目,楼主也会时不时地写些题解和大家分享下。

2、解题过程


今天要说的是Rotate Array这题,并不是说这题有多么地难(leetcode把它难度定位为EASY),而是让我理解了JavaScript中以前听说过但是一直没引起重视的一个很重要的性质。

先回到这道题本身,题目很简单,给一个数组,向右移动k位,求新的数组,关键来了,题目要求你Do not return anything, modify nums in-place instead.。右移k位,相当于把数组最右边的k位放到数组开头,还要考虑k大于数组长度的情况,似乎也很容易想到,写下如下代码:

  1. var rotate = function(nums, k) {
  2. k %= nums.length;
  3. var tmp = [];
  4. if (k)
  5. tmp = nums.slice(-k);
  6. nums.splice(-k, k);
  7. nums = tmp.concat(nums);
  8. };

tmp保存了右边要移动要前面的数组(slice),而nums自己则截掉后面要移动的数组(splice),然后把两段数组一拼,不是说直接修改nums数组么,那把结果直接赋给nums就ok了!但是无情地返回了wrong answer,leetcode不提供sample,但是出错了会给一组出错了的数据,数据如下:

  1. Input: [1,2], 1
  2. Output: [1]
  3. Expected: [2,1]

尝试着把数组带入,打印结果:

  1. var rotate = function(nums, k) {
  2. k %= nums.length;
  3. var tmp = [];
  4. if (k)
  5. tmp = nums.slice(-k);
  6. nums.splice(-k, k);
  7. nums = tmp.concat(nums);
  8. console.log(nums); // [2, 1]
  9. };
  10. rotate([1, 2], 1);

靠,输出的真的是你expected的东西啊!正当我百思不得其解的时候,我突然意识到我犯了一个很严重的错误。leetcode服务器匹配你的结果正确与否,不可能进入你写的函数里去判断!而实际上,它应该是这样判断的:

  1. var rotate = function(nums, k) {
  2. k %= nums.length;
  3. var tmp = [];
  4. if (k)
  5. tmp = nums.slice(-k);
  6. nums.splice(-k, k);
  7. nums = tmp.concat(nums);
  8. };
  9. var a = [1, 2];
  10. rotate(a, 1);
  11. console.log(a); // [1]

确实与expected的不符!为什么数组a会变成1?怎样才能变成expected的答案?我们接下去看。

3、JavaScript函数的参数传递方式


关于变量值的复制我们都已经很清楚了,基本类型(undefined、null、boolean、number、string)和引用类型(object)是不一样的。如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值赋值到为新变量分配的位置上,此后这两个变量可以参与任何操作而不会互相影响。而当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中,不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量:

  1. var a = [0, 1, 2, 3];
  2. var b = a;
  3. b.push(4, 5, 6);
  4. console.log(a); // [0, 1, 2, 3, 4, 5, 6]

而ECMAScript中所有函数的参数都是按值传递的,也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样!弄清楚了这一点,我们再回到上面的代码。

当没有执行nums = tmp.concat(nums);这行代码时,数组a和函数rotate的参数nums都引用着同一个地址,于是它们的值一起改变;当执行这行代码后,nums指向了一个新的地址,无论它指向哪里,此时的它已经和数组a没有任何关系,也就是说a的值在这一刻之后不会再变化了。

于是我们很清楚地知道,要想在nums上体现a的变化,函数内的nums参数不能去引用一个新的对象,只能在自身上操作,思考下写下如下代码,终于AC:

  1. var rotate = function(nums, k) {
  2. k %= nums.length;
  3. var tmp = [];
  4. if (k)
  5. tmp = nums.slice(-k);
  6. nums.splice(-k, k);
  7. Array.prototype.unshift.apply(nums, tmp);
  8. };

利用JavaScript的这个性质,能出现一些很神奇的效果,这些我也在做题过程中逐渐体会到了一点,具体题目到时再跟大家分享吧!

理解JavaScript中的参数传递 - leetcode189. Rotate Array的更多相关文章

  1. 深入理解JavaScript中创建对象模式的演变(原型)

    深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...

  2. 理解javascript中的回调函数(callback)【转】

    在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...

  3. 【拾遗】理解Javascript中的Arguments

    前言 最近在看JavaScript相关的知识点,看到了老外的一本Javascript For Web Developers,遇到了一个知识盲点,觉得老外写的很明白很透彻,记录下来加深印象,下面是我摘出 ...

  4. 深入理解JavaScript中的作用域和上下文

    介绍 JavaScript中有一个被称为作用域(Scope)的特性.虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,我会尽我所能用最简单的方式来解释作用域.理解作用域将使你的代码脱颖而出,减 ...

  5. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

  6. 深入理解JavaScript中的属性和特性

    深入理解JavaScript中的属性和特性 JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaSc ...

  7. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

  8. 【干货理解】理解javascript中实现MVC的原理

    理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程 ...

  9. 理解javascript中的策略模式

    理解javascript中的策略模式 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 使用策略模式的优点如下: 优点:1. 策略模式利用组合,委托等技术和思想,有效 ...

随机推荐

  1. CSS:布局的三个关键属性:float、position、display

    最近在出差,就我一个在这里.客户要做几个页面,涉及到了页面的布局问题,没办法自己得做了.然后就遇到了一些问题.页面不论怎么都不能按照设想的布局. 以前也没有做过网页布局方面的工作.上网上找类似的例子, ...

  2. spring hibernate摘记

    一.spring 1.ContextLoaderListener    它作用就是启动Web容器时,自动装配ApplicationContext的配置信息.因为它实现了ServletContextLi ...

  3. 使用eclipse查看源码的方法

    打开eclipse,建立项目:Test,将struts2相关jar包导入到其中.在Package Explorer标签栏下操作. 如下图: 在此,以查阅struts2中,struts2-core-2. ...

  4. 一个有趣的SQL Server 层级汇总数据问题

        看SQL Server大V宋大侠的博客文章,发现了一个有趣的sql server层级汇总数据问题.          具体的问题如下:     parent_id emp_id emp_nam ...

  5. read

    从标准输入读入一行内容并以空格为分隔符赋值给变量,如果输入的内容过多,则把剩下的所有内容都赋值给最后一个变量 $read A B C 123 456 789 101 $echo "$A&qu ...

  6. Sqlserver2008R2 数据库镜像配置步骤

    Sqlserver2008镜像功能可以保障数据库的高可用性.数据库镜像维护着数据库的两个副本,这两个副本必须分别放置在不同的SQL Server数据库实例中.可以用两台服务器也可以用一台服务器的不同实 ...

  7. css3 选择器(二)

    接css3选择器(一) 八.结构性伪类选择器[:nth-child(n)] :nth-child(n)选择器用来匹配某个父元素的一个或多个特定的子元素,和jquery中一样. 其中"n&qu ...

  8. [转]ORACLE 动态执行SQL语句

    本文转自:http://zhaisx.iteye.com/blog/856472 Oracle 动态SQLOracle 动态SQL有两种写法:用 DBMS_SQL 或 execute immediat ...

  9. SQL Server With 递归 日期 循环

    要实现的效果:查询从Date From 到 To 之间的 所有日期: 示例代码如下: DECLARE @DATE_FROM DATETIME = N'2016-05-16';--N'2015-05-1 ...

  10. Remote Displayer for Android V1.2

    VERSION LOG for Android Remote Displayer Features:The app allows you to see your Android device remo ...