关于函数传参 & JS如何刷力扣

420 阅读5分钟

关于函数传参

事情要从第一次刷力扣说起, 我怎么也没想到萌新的我第一天刷力扣, 并且刷的第一题(数组去重), 就捅出这么一个大窝, 本文将说明函数传参的原理, 以及顺带说明了用JS怎么刷力扣, 如果本文顺利看下来,

先上被坑的那个力扣题 :

image.png

我由于没有好好审题, 因此用了一个新的数组空间去做了这题, 也阴差阳错地解决了一个难点, 我的解题代码如下 :

      /**
       * @param {number[]} nums
       * @return {number}
       */
      var removeDuplicates = function (nums) {
        let newNums = [];
        for (let i = 0; i < nums.length; i++) {
          if (newNums.indexOf(nums[i]) === -1) {
            newNums.push(nums[i]);
          }
        }
        nums = newNums;
        return nums.length;
      };

关于思路的详细解析, 可以见另一篇文章, 大概就是开辟了一个新数组, 然后遍历原数组, 遍历的每个值都进行一次判断(判断是否新数组中有该值, 有则放进新数组, 无则跳过, 进行下一轮遍历)

力扣上面的两个注释解释下(题目有说) :

  • @param {number[]} nums 意思是nums的值为去重后的新数组
  • @return {number} 意思是返回数组的长度

本题思路是对的, 但是运行的时候确实报错了

image.png

然后我在自己的vscode运行了下, 结果如下

image.png

可见, 自己运行是对的, 但是跑力扣的时候却报错了, OK, 填坑之旅开始

想法一 : 因为力扣知道你申请了另一片数组空间?

首先, 力扣无法检测你是否申请了另一片空间存放数组的, 它只能检测空间复杂度是否超过规定值, 因此不是空间复杂度的原因.

想法一排除

想法二 : 力扣需要修改的nums是全局变量的nums

先提前剧透一下, 这个想法, 对了

在经过一番摸鱼滚打后, 大概知道了力扣过测试用例的方法

      let nums = [1,1,2];
      let len = removeDuplicates(nums);
      console.log(len);
      console.log(nums);

结合力扣给的示例 :

image.png

得出结论, 这里面关键的信息是, 力扣希望你调用函数时, 函数作用域内的nums, 在经过数组去重后, 修改全局作用域下的nums, 因此, 这就能理解问题所在了, 在经过一轮排查, 我发现了一个盲点

	nums = newNums;

说明这里的nums, 并没有修改全局作用域下的nums, 也就是它给的测试用例, 下面举个例子说明下函数传参问题

函数传参

查阅了资料后, 知道当函数传递的类型为值传递, 关于值传递, 先放一段大佬的解释 :

什么是值传递 ?

当函数参数是引用类型时,将参数复制了一个副本到局部变量,只不过复制的这个副本是指向堆内存中的地址而已,我们在函数内部对对象的属性进行操作,实际上和外部变量指向堆内存中的值相同,但是这并不代表着引用传递,函数参数传递的并不是变量的引用,而是变量拷贝的副本,当变量是原始类型时,这个副本就是值本身,当变量是引用类型时,这个副本是指向堆内存的地址。

什么是引用类型(复杂数据类型) ?

简单理解, 像数组, 对象等类型为引用类型, 那么像数字型, 字符串型为原始类型(简单数据类型)

先看个简单的例子 :

      let nums = [1, 2, 3];
      nums = [1, 2, 3, 4];
      console.log(nums);

输出 : image.png

分析 : image.png

0x666666和0x777777为数组申请空间的首地址

因此当nums = [1, 2, 3, 4];时, 仅仅改变了nums指针的指向而已

正题 :

      let nums = [1, 2, 3];

      change(nums);
      function change(nums) {
        nums = [1, 2];
        console.log(nums + "函数作用域");
      }

      console.log(nums + "全局作用域");

输出 : image.png

分析 :

image.png

  1. let nums = [1, 2, 3];对应步骤①, nums在堆中申请了一块数组空间存放[1, 2, 3] 并且nums指向了首地址
  2. change(nums); 对应步骤②, 当函数调用时, 此时函数中接受的实参为一个值, 当值传到函数里面时, nums为一个副本, 它变成了局部变量numsimage.png, 并且指向了0x666666, 和全局变量一样
  3. nums = [1, 2]; 对应步骤③, 此处最为关键, 此时函数申请了另外一片空间, 存放了[1, 2], 并且局部变量的nums指向了0x777777, 也就是新空间的首地址, 因此, 全局作用域下的nums, 和局部作用域下的nums, 已经变得不一样了

那有没有办法修改全局变量的nums呢, 方法肯定有的, 这里只提供一种最简单的作为解释

例子 :

      let nums = [1, 2, 3];

      change(nums);
      function change(nums) {
        nums[2] = 1;
        console.log(nums + "函数作用域");
      }

      console.log(nums + "全局作用域");

输出 : image.png

分析 : 结合上图的步骤②, 此时nums还指向0x66666

nums[2] = 1;, 这里修改的还是原数组的值, 因为nums[2]指向了nums的第三个值存放的地址

改进

到这里, 基本上结论已经浮出水面了, 结合上述原因, 对代码重新作出了改进

      var removeDuplicates = function (nums) {
        let newNums = [];
        for (let i = 0; i < nums.length; i++) {
          if (newNums.indexOf(nums[i]) === -1) {
            newNums.push(nums[i]);
          }
        }
        // 遍历新数组, 一个个存放到原数组中
        for (let i = 0; i < newNums.length; i++) {
          nums[i] = newNums[i];
        }
        // 删除多余的部分
        nums.splice(newNums.length, nums.length);
        return nums.length;
      };

力扣 :

image.png 终于......

结论 : 通过解决了函数传参的问题, 解决了本文的疑惑, 至于浅拷贝和深拷贝, 由于涉及不多, 并没有花时间讲述, 虽然最终题解时间复杂度和空间复杂度, 都很菜, 这里先不给出最优解, 本文旨在讲述函数传参的问题和前端用JS做力扣要怎么做, 最优解有不少大佬讲得比我好, 可以直接去看力扣题解

PS : 萌新发文章, 轻喷, 有做的不好的欢迎指出, cya