026 - 删除排序数组中的重复项(remove-duplicates-from-sorted-array)

Create by jsliang on 2019-06-06 11:11:26
Recently revised in 2019-06-06 14:30:57

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 解题
3.1 解法 - 双指针
3.2 解法 - 违规操作

二 前言

返回目录

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2], 
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 
你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

三 解题

返回目录

解题千千万,官方独一家,上面是官方使用 Java 进行的题解。

小伙伴可以先自己在本地尝试解题,再看看官方解题,最后再回来看看 jsliang 讲解下使用 JavaScript 的解题思路。

3.1 解法 - 双指针

返回目录

  • 解题代码
var removeDuplicates = function(nums) {
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] === nums[i + 1]) {
      nums.splice(i, 1);
      i--;
    }
  }
};
  • 执行测试

  • nums[1, 1, 2]

  • return
2
  • LeetCode Submit
✔ Accepted
  ✔ 161/161 cases passed (124 ms)
  ✔ Your runtime beats 72.77 % of javascript submissions
  ✔ Your memory usage beats 15.24 % of javascript submissions (37.6 MB)
  • 知识点

  • splice()splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。splice() 详细介绍

  • 解题思路

首先,这道题涉及到所谓的 双指针 了,什么是 双指针 呢?

双指针,顾名思义,就是利用两个指针去遍历数组,一般来说,遍历数组采用的是单指针(index) 去遍历,两个指针一般是在有序数组中使用,一个放首,一个放尾,同时向中间遍历,直到两个指针相交,完成遍历,时间复杂度也是O(n)。

啥意思咧?就好比我们的代码:

for (let i = 0; i < nums.length; i++) {
  if (nums[i] === nums[i + 1]) {
    nums.splice(i, 1);
    i--;
  }
}

在这里,我们是不是使用了数组的两个位置?[i] 以及 [i + 1]

通过这两个指针的不断移动,直到把整个数组遍历一次,从而得到最终解。

然后,我们需要知道的是,由于代码中,我们使用了 for() + splice(),从而造成的耗时和空间会比其他代码大,因为 splice() 是 JavaScript 封装好的方法。

是不是觉得无法理解?那么我们稍微看一眼 splice() 的源码吧:

仅仅看看就行了,这里不做过多讲解,刚兴趣的小伙伴可以前往 V8 引擎源码

function ArraySplice(start, delete_count) {
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.splice");

  var num_arguments = arguments.length;
  var array = TO_OBJECT(this);
  var len = TO_LENGTH(array.length);
  var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
  var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len, start_i);
  var deleted_elements = ArraySpeciesCreate(array, del_count);
  deleted_elements.length = del_count;
  var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;

  if (del_count != num_elements_to_add && %object_is_sealed(array)) {
    throw %make_type_error(kArrayFunctionsOnSealed);
  } else if (del_count > 0 && %object_is_frozen(array)) {
    throw %make_type_error(kArrayFunctionsOnFrozen);
  }

  var changed_elements = del_count;
  if (num_elements_to_add != del_count) {
    // If the slice needs to do a actually move elements after the insertion
    // point, then include those in the estimate of changed elements.
    changed_elements += len - start_i - del_count;
  }
  if (UseSparseVariant(array, len, IS_ARRAY(array), changed_elements)) {
    %NormalizeElements(array);
    if (IS_ARRAY(deleted_elements)) %NormalizeElements(deleted_elements);
    SparseSlice(array, start_i, del_count, len, deleted_elements);
    SparseMove(array, start_i, del_count, len, num_elements_to_add);
  } else {
    SimpleSlice(array, start_i, del_count, len, deleted_elements);
    SimpleMove(array, start_i, del_count, len, num_elements_to_add);
  }

  // Insert the arguments into the resulting array in
  // place of the deleted elements.
  var i = start_i;
  var arguments_index = 2;
  var arguments_length = arguments.length;
  while (arguments_index < arguments_length) {
    array[i++] = arguments[arguments_index++];
  }
  array.length = len - del_count + num_elements_to_add;

  // Return the deleted elements.
  return deleted_elements;
}

如上,splice() 先执行删除操作,删除指定个数的元素,然后再插入 elements(元素或数组),他的每次删除都涉及大量元素的重新排列,而在插入元素时引入队列来管理。所以 splice() 的效率不高

最后,我们要做的就是返回数组的长度,就这样就 OK 了。

3.2 解法 - 违规操作

返回目录

  • 解题代码
var removeDuplicates = function(nums) {
  var a = [...new Set(nums)];
  for (var i = 0;i < a.length;i++) nums[i] = a[i];
  return a.length;
};
  • 执行测试

  • nums[1, 1, 2]

  • return
2
  • LeetCode Submit
✔ Accepted
  ✔ 161/161 cases passed (112 ms)
  ✔ Your runtime beats 93.2 % of javascript submissions
  ✔ Your memory usage beats 45.6 % of javascript submissions (37.1 MB)
  • 知识点

  • SetSet 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。Set 详细介绍

  • 解题思路

首先jsliang 表示这种方法可能不符合题意,但是它又是可行的!所以不管怎么说,莽就对了~

然后Set 对象会对值进行唯一操作,如果输入 [1, 1, 2],那么这个 Set 会变成 Set(2) {1, 2}let a = new Set([1, 1, 2]);

接着,我们利用 ES6 的扩展运算符:[...new Set(nums)],可以直接将 Set 类型转换成 Array 类型。

最后,循环遍历 a,将 a 的内容赋值到 nums 即可(注意,nums 后面还是会有一些乱七八糟的数据,不过它不影响我们的解题)。


不折腾的前端,和咸鱼有什么区别!

图

jsliang 会每天更新一道 LeetCode 题解,从而帮助小伙伴们夯实原生 JS 基础,了解与学习算法与数据结构。

扫描上方二维码,关注 jsliang 的公众号,让我们一起折腾!

知识共享许可协议
jsliang 的文档库梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于https://github.com/LiangJunrong/document-library上的作品创作。
本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。

Copyright © jsliang.top 2019 all right reserved,powered by Gitbook该文件修订时间: 2019-06-14 21:45:48

results matching ""

    No results matching ""