前端刷题日常Day3--数组合并和删除元素

93 阅读7分钟

背景

最开始打算在牛客网刷《剑指offer》结果发现刷了几道链表的题后,还得买书才能继续刷,我这个一毛不拔怎么能忍,况且我已经有这本书了,干嘛还要买,本来想在网上再找找刷这本书的地方,但木有找到,所以转战leetcode。

看了leetcode的题库,就从第一个《面试经典150题》来吧,里面有些题其实刷过,所以可能有的题会很快写出来,但是不会的话估计就很慢了,然后设置每天两道,因为不想给自己那么大压力,而且还想学点别的。

好了要开始我的刷题之旅了,有木有一起刷题的小伙伴,可以一起来哦~

image.png

刚开始是数组的题目,相对简单,而且之前都做过,所以做的还是比较快,把昨天和今天刷的题一并总结下,共4题。

image.png

一、数组合并和链表合并

题目一 88. 合并两个有序数组

哈哈,我一看这个题,简单呀,我要一遍ac,一顿操作猛如虎写完信心满满提交,结果竟然不对!惊掉下巴,为啥不对。我是新建的一个数组来保存结果的,然后还放到了chrome里运行,没错啊,什么情况!然后我又去看了题目,看到:

请你 合并 nums2 **到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

原来是这样,这题有坑啊,看似简单,实际上暗藏玄机,如果用原数组的话,那写法就有的说了,我最开始写的还是新开一个数组,把nums1拷贝过去,😂,换汤不换药,只是把结果保存到nums1,然后就通过了。

var merge = function(nums1, m, nums2, n) {
    let i = 0;
    let j = 0;
    let nums1backup = [...nums1];
    let count = 0;
    while (i < m && j < n) {
        if (nums1backup[i] < nums2[j]) {
            nums1[count++] = nums1backup[i++];
        } else {
            nums1[count++] = nums2[j++];
        }
    }
    while (i < m) {
        nums1[count++] = nums1backup[i++];
    }
    while (j < n) {
        nums1[count++] = nums2[j++];
    }
};

因为昨天睡前刷的,太瞌睡了,就直接看了答案,看大家是如何原地合并的。

看到了解题的关键:从后向前遍历,妙啊。不错不错,顿时又一顿自卑==,写一遍如下:

// 拷贝到nums1
var merge = function(nums1, m, nums2, n) {
    let sum = m + n;
    let i = m - 1;
    let j = n - 1;
    while (i >= 0 && j >= 0) {
        if (nums1[i] > nums2[j]) {
            nums1[--sum] = nums1[i--];
        } else {
            nums1[--sum] = nums2[j--];
        }
    }
    while (j >= 0) {
        nums1[--sum] = nums2[j--];
    }
}

这道题的启示:

  1. 看似简单的题却又不简单,我们需要仔细审题
  2. 掌握从后向前遍历的思路,这个思路值得学习。
  3. 看人家脑袋多灵光。啧啧

做了数组的合并,那是不是还有链表的合并,正好前两天也做了,那直接放到一起吧。插入一题,链表的合并。

题目二 21. 合并两个有序链表

解法一 构造新链表

这个和合并数组一样,依然是2种解法,一种是构造一个新列表,挨个构造新节点合并过去。

var mergeTwoLists = function(list1, list2) {
    let node1 = list1;
    let node2 = list2;
    let newHead = new ListNode();
    let node = newHead;
    while (node1 && node2) {
        const newNode = new ListNode();
        if (node1.val < node2.val) {
            newNode.val = node1.val;
            node1 = node1.next;
        } else {
            newNode.val = node2.val;
            node2 = node2.next;
        }
        node.next = newNode;
        node = newNode;
    }
    if (node1) {
        node.next = node1;
    }
    if (node2) {
        node.next = node2;
    }
    return newHead.next;
};

解法二 合并到list1中

合并到链表1中,相当于不断的往list1插入节点,考虑到头节点,所以需要一个哑节点来处理,所以构造一个哑节点指向list1,使用pre和cur两个指针,用来方便插入,cur指向list1当前遍历到的节点。所以这时有两种情况:

  1. 如果list1的节点小于list2的节点,则pre和cur同时向后移动,无需操作
  2. 否则,将list2当前的节点插入到pre后面,然后将pre移动到新插入的节点,而list2的节点后一位。

想清楚了,写出来,一遍AC,😄,开森。

var mergeTwoLists = function(list1, list2) {
    let dummyHead = new ListNode();
    dummyHead.next = list1;
    let pre = dummyHead;
    let cur = list1;
    let node2 = list2;

    while (cur && node2) {
        // list1节点小于list2的节点,则无需操作,pre和cur向后走一步
        if (cur.val < node2.val) {
            pre = pre.next;
            cur = cur.next;
        } else {
            let tmp = node2.next;
            pre.next = node2;
            node2.next = cur;
            pre = node2;
            node2 = tmp;
        }
    }
    if (node2) {
        pre.next = node2;
    }
    return dummyHead.next;
}

这个题给我的启示是:

  1. 对于链表的删除和插入题目,一般都需要构造一个哑节点,方便处理。
  2. 链表的题目思路不难,主要在于熟练指针的操作,写的多了基础的题就可以写出来了。

二、数组删除有套路

后面的三道题都是跟数组删除元素相关了,而且可以说有模版,都是双指针的应用。我写的三道题几乎一样,只是判断的地方不一样,感受一下吧。

题目三 27. 移除元素

这题写了好几遍了,现在看到后可以很快写出来。双指针,一遍ac~~,其实这道题最开始做的时候其实是想不到这种写法的,但看了这种写法后,赞叹“不错不错真不错”。

var removeElement = function(nums, val) {
    let j = 0;
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] !== val) {
            nums[j++] = nums[i];
        }
    }
    return j;
};

题目四 26. 删除有序数组中的重复项

这题是不是跟上个题目很像,以致我琢磨了会,这两题啥区别😆,噢,这题是删除重复项,数组有序,那重复项就是挨着的,所以写法跟上题几乎一样。跟上个题的区别是判断条件变了。

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

这例可能有人说,最后一个数组会越界啊,但是却通过了,我在想是不是因为js的越界不像c一样那么严格会报错,它不会报错,取出的是undefined,所以肯定不等于,所以对了。算是投机取巧了。以至于下一道题也是这样。大家继续看。

image.png

题目五 80. 删除有序数组中的重复项 II

这个题在上一道题的基础上又加大难度了,区别就是使得出现次数超过两次的元素**只出现两次**,其他统统一样。

那这个题关键是想清楚,什么时候慢指针j不用往后移动,那什么时候呢,就是当前元素等于后一个元素,并且等于后一个的后一个元素,即nums[i] === nums[i + 1] && nums[i + 1] === nums[i + 2], 这个条件又可以简化为什么呢?nums[i] === nums[i + 2],因为是有序的,所以中间的元素必定和左右两个相等。这个搞清楚了,再取一下反,凡是不符合条件的,慢指针就正常往后,否则不移动。所以代码如下:

var removeDuplicates = function(nums) {
    let j = 0;
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] !== nums[i + 2]) {
            nums[j++] = nums[i];
        }
    }
    return j;
};

所以看到了吗,跟上面两道题的唯一区别是判断条件!哈哈,竟然总结了一个模版代码。

总结

至此,我终于掌握了删除元素的方法了。总结一下模版代码如下:

var removeDuplicates = function(nums) {
    let j = 0;
    for (let i = 0; i < nums.length; i++) {
        if (快指针可以拷贝到慢指针的条件) {
            nums[j++] = nums[i];
        }
    }
    return j;
};

最后

今天的文章也AC了,可是还有一个链表的题目没总结,接着写,最后一句鸡汤结尾,送给大家:

人生的一大目标应该是掌控自己的时间。理想的工作是利用杠杆效应的工作。在这种工作模式下,你可以掌控自己的时间,并能对自己的产出成果负责。如果你通过提供绝佳的解决方案给企业带来了神奇的转机,那么你必将得到金钱的回报。

这是这两天在看的书《纳瓦尔宝典》的句子,这本小书还是挺不错的,推荐大家阅读~~

image.png