【leetcode】前端er必会算法-链表基础操作篇

293 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

原文位于 github仓库-正在起步阶段的前端知识库,其中记录了一名前端初学者的学习日记🤔&学习之路点点滴滴的记录(练手demo🧑‍💻,必会知识点🧐)

欢迎大家来贡献更多“前端er必会知识点”🧑‍🎓/分享更多有意义的demo❤️!(请给这个年幼的小仓库更丰富的内容吧🥺🥺🥺)

本篇主要内容(都比较基础~):

【1】链表的合并

21. 合并两个有序链表(递归法重要!)

这俩题一样哈——剑指 Offer 25. 合并两个排序的链表

迭代法
 var mergeTwoLists = function(l1, l2) {
     let dummyHead = new ListNode(0, l1);
     let cur = dummyHead;
     while(l1 !== null && l2 !== null){
         if(l1.val < l2.val){
             cur.next = l1;
             l1 = l1.next;
         }
         else{
             cur.next = l2;
             l2 = l2.next;
         }
         cur = cur.next;
     }
     cur.next = l1 === null ? l2 : l1;
     return dummyHead.next;
 };
递归

之前写过一篇 掌握递归调用栈思想 由浅入深研究递归🎉,结合着递归栈的思想看本题的递归解法会好想很多~

 var mergeTwoLists = function(l1, l2){
     if(l1 === null){
         return l2;// 碰到一个链表走到null 就结束“递” 开始归
     }
     else if(l2 === null){
         return l1;
     }
     else if(l1.val < l2.val){
         l1.next = mergeTwoLists(l1.next, l2);
     }
     else{
         l2.next = mergeTwoLists(l1, l2.next);
     }
 }

参考力扣题解区大佬的递归法图解

递归就思考第一层第二层最好

每层不要忘了返回当层结果(执行这一次递归函数 返回的结果)!

以原始用例 [1,2] [1,3,4]

【1】开始“递” 一直到最里面那一层

  • l1小的时候 l1指向再里层一些的那个递归函数mergeTwoLists()别忘了l1要往前挪动一位 mergeTwoLists(l1.next, l2)
  • l2小的时候同理

img

【2】return l2 执行时

img

这里结束了“递”

最里面那层的mergeTwoLists()执行完了

开始“归” —— 逐步执行外层的mergeTwoLists()函数

【3】最外面一层函数执行完

img

就可以返回最终结果了~

img

小结一下——

  • 想一下“哪个是最里层被调用的函数” 在那里返回对应结果

    • 也就是主要想第一层

【2】链表的删除

83. 删除排序链表中的重复元素

 var deleteDuplicates = function(head) {
     // let dummyHead = new ListNode(0, head);
     // let pre = dummyHead;
     let cur = head;
     // if(cur === null){
     //     return null;
     // }
     while(cur !== null && cur.next !== null){
         if(cur.val === cur.next.val){
             cur.next = cur.next.next;
         }
         else{
             cur = cur.next;
         } 
     }
     return head;
 };

【3】链表删除的延伸

82. 删除排序链表中的重复元素 II

但是现在,咱们要做的事情变成了把前驱和后继一起删掉,前面两个值为1的结点要一起狗带才行,起始结点直接变成了第三个:

img

如果继续沿用刚才的思路,我们会发现完全走不通。因为我们的 cur 指针就是从图中第一个结点出发开始遍历的,无法定位到第一个结点的前驱结点,删除便无法完成。

虚拟头结点应用场景:

其实在链表题中,经常会遇到这样的问题:链表的第一个结点,因为没有前驱结点,导致我们面对它无从下手。这时我们就可以用一个 dummy 结点来解决这个问题。

本题思路

如果想要删除两个连续重复的值为 1 的结点,我们只需要把 dummy 结点的 next 指针直接指向 2:

img

如此一来,就大功告成啦~

注意:由于重复的结点可能不止一个两个,我们这里需要用一个 while 循环来反复地进行重复结点的判断和删除操作。

 const deleteDuplicates = function(head) {
     // 极端情况:0个或1个结点,则不会重复,直接返回
     if(!head || !head.next) {
         return head
     }
     // dummy 登场
     let dummy = new ListNode() 
     // dummy 永远指向头结点
     dummy.next = head   
     // cur 从 dummy 开始遍历
     let cur = dummy 
     // 当 cur 的后面有至少两个结点时
     while(cur.next && cur.next.next) {
         // 对 cur 后面的两个结点进行比较
         if(cur.next.val === cur.next.next.val) {
             // 若值重复,则记下这个值
             let val = cur.next.val
             // 反复地排查后面的元素是否存在多次重复该值的情况
             while(cur.next && cur.next.val===val) {
                 // 若有,则删除
                 cur.next = cur.next.next 
             }
         } else {
             // 若不重复,则正常遍历
             cur = cur.next
         }
     }
     // 返回链表的起始结点
     return dummy.next;
 };