LeetCode Day 13
阅读原文时间:2023年07月10日阅读:1

LeetCode0026

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

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

思路:

因为是有序数组,设置一个max,当前走到哪个值了,后续的值比它小或者等于它,那都是重复值,抛弃该位即可。

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function (nums) {
    let max = Number.NEGATIVE_INFINITY;
    let cursor = 0;
    while (cursor < nums.length) {
        if (nums[cursor] <= max) {
            //重复了,抛弃它
            nums.splice(cursor, 1);
        }
        else {
            //新值
            max = nums[cursor];
            cursor++;
        }
    }
    return nums.length;
};

执行用时 :88 ms,在所有 JavaScript 提交中击败了65.71%的用户

内存消耗 :38 MB,在所有 JavaScript 提交中击败了8.44%的用户

似乎空间消耗的还是比较大,大概是因为splice每次都创建了一个返回数组,虽然我这里没有任何变量引用它,想减少空间的使用量,那我们只好考虑出现重复值的时候,在找到一个不重复的数值之前,把后续的数组往前移动若干位置覆盖它即可。

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function (nums) {
    if (!nums || nums.length === 0) return 0;
    let validCount = nums.length;
    let duplicate = 0;
    for (let i = 0; i < validCount; i++) {
        while (i + 1 + duplicate < validCount && nums[i] === nums[i + 1 + duplicate]) {
            duplicate++;
        }

        if (duplicate > 0) {
            //将后续的数据往前移动
            for (let j = i + 1 + duplicate; j < validCount; j++) {
                nums[j - duplicate] = nums[j];
            }
        }

        validCount -= duplicate;
        duplicate = 0;
    }
    return validCount;
};

执行用时 :188 ms,在所有 JavaScript 提交中击败了14.89%的用户

内存消耗 :37.2 MB,在所有 JavaScript 提交中击败了38.56%的用户

只引入了两个常数变量,空间复杂度没什么变化,数组的移动上导致几乎多了一倍有余的时间,而空间仅仅节省了0.8MB,完全得不偿失。

我们试试官方题解:

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function (nums) {
    if (!nums || nums.length === 0) return 0;
    if (nums.length == 0) return 0;
    let i = 0;
    for (let j = 1, lens = nums.length; j < lens; j++) {
        if (nums[j] != nums[i]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
};

执行用时 :64 ms, 在所有 JavaScript 提交中击败了99.92%的用户

内存消耗 :37.2 MB,在所有 JavaScript 提交中击败了38.25%的用户

这个做法的关键点在于,j一直往后找,找到一个不跟当前重复的值,就直接插入到当前位置之后,其实它只是找下一个比当前大的值,插入到前面的值之后而已,而不是后续的整体部分集体往前移动。

LeetCode0027

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。

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

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序

你不需要考虑数组中超出新长度后面的元素。

参考0026这道题的官方解法,我们很容易可以得出以下的代码:

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function (nums, val) {
    let cur = -1;
    for (let next = 0, lens = nums.length; next < lens; next++) {
        if (nums[next] !== val) {
            cur++;
            nums[cur] = nums[next];
        }
    }
    return cur + 1;
};

执行用时 :72 ms, 在所有 JavaScript 提交中击败了33.58%的用户

内存消耗 :34.2 MB, 在所有 JavaScript 提交中击败了5.04%的用户

空间的话只是两个常数指针变量差不了多少,但是时间确实可以优化一下。

考虑到nums=[5,1,2,3,4],然后val=5的情况,上面的写法就需要将后续的1,2,3,4都依次往前面赋值1次,一共赋值4次。

因为题目不要求数组的顺序,如果我们发现某个值不满足需要,我们可以直接把它跟数组最后一个进行交换,然后将有效的数组长度减少1,这样可以减少数字的移动次数和赋值次数。

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function (nums, val) {
    let current = 0;
    let validLens = nums.length;
    while (current < validLens) {
        if (nums[current] === val) {
            nums[current] = nums[validLens - 1];
            validLens--;
        } else {
            current++;
        }
    }
    return current;
};

执行用时 :64 ms,在所有 JavaScript 提交中击败了76.40%的用户

内存消耗 :34.2 MB,在所有 JavaScript 提交中击败了5.04%的用户