链表相关算法题 | 前端er

  • 分类刷算法题
  • 之前陆陆续续刷了一些,但是感觉都没有真正掌握起来吧,做的题杂乱无章,所以这次决定好好整理一下好好过一遍
  • 今天要刷的是链表

一、 链表的合并

  • 剑指 Offer 25. 合并两个排序的链表 - 力扣(LeetCode)
  • 主要运用的就是’穿针引线’的思路,比较两个,那个比较小就给那个串起来
  • 难度:简单
  • 思路:
    • 初始化dummy伪结点,cur指向dummy结点
    • 进入while循环:当l1为空或者l2为空时跳出循环
      • 当l1.val<l2.val时,cur后继结点指向l1,l1向前走一步
      • 当l1.val<l2.val时,cur后继结点指向l2,l2向前走一步
      • cur向前走一步
    • 合并剩余的部分
var mergeTwoLists = function(l1, l2) {
    let dummy = new ListNode;
    let cur = dummy;
    //两个都节点都存在时,就一直穿梭引线
    while(l1 && l2) {
    //当l1的值比较小的时候,cur的next指向l1,反之指向l2
        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 dummy.next
};

二、链表的删除

  • 83. 删除排序链表中的重复元素 - 力扣(LeetCode)
  • 这个很常见也很基础
  • 难度: 简单
  • 思路:
    • cur指向head
    • 进入while循环:当cur指向结点为空或cur的后继结点为空时跳出循环
      • 如果cur指向结点值等于其后继结点的值,则让他的后继结点指向其后继结点的后继结点
      • 否则cur向前走一步
var deleteDuplicates = function(head) {
    let cur = head;
    while(cur != null && cur.next!= null) {
        if(cur.val == cur.next.val) {
            cur.next = cur.next.next
        }else{
            cur = cur.next
        }
    }
    return head
};
  • 82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
  • 难度:中等
  • 这道题就是上一道题的衍生了,这道题需要把重复的全部删除,那么就可能出现头结点也要删除遇到这种情况我们就要加他加上dummy伪头结点,才可以next指向真实头结点对他进行删除
  • 思路:
    • 初始化dummy结点:让他的后继节点指向head,同时cur指向dummy
    • 进入while循环:cur的后继节点或cur的后继节点的后继节点为空时跳出循环
      • 当cur的后继节点值等于其后继节点的后继节点值时,遍历其后继节点,对该值节点进行删除
      • 否则的话cur向前走一步
var deleteDuplicates = function(head) {
    //dummy伪头结点
    let dummy = new ListNode();
    dummy.next = head;
    let  cur = dummy;
    while(cur.next && cur.next.next) {
        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
};
  • 剑指 Offer 18. 删除链表的节点 - 力扣(LeetCode)
  • 难度:简单
  • 如上一道题所述,看到删除节点有可能删除第一个节点我们可能就要设置伪头结点啦;
  • 同时这道题说的是删除一个节点,也就是说如果头结点是需要删除的节点话,我们可以采用另一种方法,遇到删除节点为头结点时直接返回head.next就行
  • 以上两种思路跟上述题都差不多就不赘述啦
//设置伪头结点
var deleteNode = function(head, val) {
    let dummy = new ListNode();
    dummy.next = head;
    let cur = dummy;
    while(cur && cur.next) {
        if(cur.next.val == val) {
            cur.next = cur.next.next;
            return dummy.next
        }else{
            cur = cur.next;
        }
    }
};
//遇到头结点需要删除,直接返回head.next
var deleteNode = function(head, val) {
    if(head.val == val) return head.next;
    let cur = head;
    while(cur && cur.next) {
        if(cur.next.val == val) {
            cur.next = cur.next.next;
            return head;
        }else{
            cur = cur.next;
        }
    }
};
  • 剑指 Offer II 021. 删除链表的倒数第 n 个结点 - 力扣(LeetCode)
  • 难度: 中等
  • 这道题我们采取新的思路-快慢指针
  • 遇到涉及相对复杂的链表操作,比如反转、指定位置的删除等等经常需要涉及到反复遍历,解决这类题,我们用到的是双指针中的“快慢指针”
  • 快慢指针指的是两个一前一后的指针,两个指针往同一个方向走,只是一个快一个慢
  • 涉及链表操作、尤其是涉及结点删除的题目,我都建议大家写代码的时候直接把 dummy 给用起来,建立好的编程习惯,这个在后面也不会再赘述啦
  • 还有一道很类似的题,做完这道题也可以试一试喔:剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode)
  • 思路:
    • 设置伪头结点
    • 初始化快指针fast, 慢指针low 都指向dummy
    • 先让快指针走K步(目的:当快指针走到最后的结点时,慢结点刚好指向倒数第K个结点的前继结点)
    • 让快慢指针同频向前走直到fast走到最后的结点
    • 删除慢指针的后继节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tyzcqBC6-1662475101414)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73192dec21274eefb8283e248d23fc82~tplv-k3u1fbpfcp-watermark.image?)]

var removeNthFromEnd = function(head, n) {
    let dummy = new ListNode();
    dummy.next = head;
    let fast  = dummy;
    let slow = dummy;
    while(n!=0) {
        fast = fast.next;
        n--
    }
    while(fast.next) {
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return dummy.next
};

做完上述的题还可以试试以下的题哦

203. 移除链表元素 - 力扣(LeetCode): 这个题跟上述剑指offer18的题很像

三、链表反转

  • 剑指 Offer II 024. 反转链表 - 力扣(LeetCode)
  • 难度: 简单
  • 上述题目我们用到了双指针法的快慢指针,那我们这道题用一下多指针法
    • pre,cur,next依次指向连续结点
    • pre为cur的前继结点,用于反转链表
    • cur为当前结点,通过cur遍历
    • next指向cur的后继节点,防止反转后丢失后继链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZmI3ySr-1662475101415)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65bcc6d793ee4b32a24c128bfc0d0e73~tplv-k3u1fbpfcp-watermark.image?)]

var reverseList = function(head) {
    let pre = null;
    let cur = head;
    while(cur) {
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre
};
  • 92. 反转链表 II - 力扣(LeetCode)
  • 难度:中等
  • 这道题跟上一道题很像,不一样的是上一道题是全部反转,这道题属于局部反转
  • 采用先反转再拼接,所以记得要把left的前一个节点和right的后一个节点记录下来
  • 思路:
    • 初始化伪头结点dummy
    • 初始化leftNode,rightNode,通过for循环让其分别指向left的前继结点和right节点
    • 切断
    • 局部反转(直接采用上一道题的代码)
    • 拼接
  • 这道题直接做可能会容易乱,画个图就好啦~
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRl62BF6-1662475101416)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/472124e362ce418299c01ba59da63a22~tplv-k3u1fbpfcp-watermark.image?)]
var reverseBetween = function(head, left, right) {
    let dummy = new ListNode();
    dummy.next = head;
    let leftNode = dummy;
    let rightNode = dummy;
    //走到left的前继结点
    for(let i =0 ;i<left-1; i++) {
        leftNode = leftNode.next;
    }
    //走到right结点
    for(let i=0; i<right; i++) {
        rightNode = rightNode.next;
    }
    //记录下list1和list2
    let list1 = leftNode.next;
    let list2 = rightNode.next;
    //切断
    leftNode.next = null;
    rightNode.next  = null;
    //反转中间链表
    let list3 = reverseList(list1);
    //拼接
    leftNode.next = list3;
    list1.next = list2;
   //返回
   return dummy.next
};

var reverseList = function(head) {
    let pre = null;
    let cur = head;
    while(cur) {
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre
};

四、环形链表

  • 141. 环形链表 - 力扣(LeetCode)
  • 难度: 简单
  • 遇到环形的还可以多加一个判断:如果只有两个结点的话,是无法构成环形的
  • 这道题的方法太多啦而且都是很常见的方法
  • 可以利用哈希表,哈希表存储已经经过的结点,如果经过的节点一样的就说明环形了
  • 也可以使用上面说过的双指针里面的快慢指针:如果链表是环形的,那么快指针一定会追上慢指针
  • 也可以使用最简单的标记方法:我经过就把它的flag变为true,只有我经过的节点flag为true,那么一定成环了
var hasCycle = function(head) {
    //使用快慢指针
    //如果只有两个节点也构不成环形链表
    if(head == null || head.next == null || head.next.next == null) return false
    let n1 = head;
    let nn = head.next;
    while(n1 != nn){
        //下一个节点能够为空说明不环形
        if(nn.next == null || nn.next.next == null) {
            return false;
        }
        //一个每次走一步一个每次走两步,形成快慢
        n1 = n1.next;
        nn = nn.next.next;
    }
    return true
};
var hasCycle = function(head) {
    //使用flag
    //如果只有两个节点也构不成环形链表
    if(head == null || head.next == null || head.next.next == null) return false
    while(head) {
        if(head.flag) {
            return true
        }else{
            head.flag = true;
            head = head.next;
        }
    }
    return false
};
var detectCycle = function(head) {
    if(head == null || head.next == null || head.next.next == null) return null
    while(head) {
        if(head.flag) {
            return head
        }else{
            head.flag = true;
            head = head.next;
        }
    }
    return null
};

五、链表的相交

  • 160. 相交链表 - 力扣(LeetCode)
  • 难度: 简单
  • 这个题的思路就是如果我相交的话,那么两个指针分别在两个链表走一遍,那么一定会有相交的结点
    • 若两链表 公共尾部 (即 c > 0c>0 ) :指针 A , B 同时指向第一个公共节点node
    • 若两链表 公共尾部 (即 c = 0c=0 ) :指针 A , B 同时指向 null
var getIntersectionNode = function(headA, headB) {
    //如果有一个链表为空的话那么一定不会相交
    if(headA === null || headB === null) return null
    let pA = headA;
    let pB = headB;
    //这里没有相交是不会死循环哦,因为会同时指向null,也是相等;所以退出循环不一定是没有相交
    while(pA != pB) {
        //如果pA指向的为空,则切换到headB ,否则就指向其后继节点
        pA = pA === null? headB: pA.next;
         //如果pB指向的为空,则切换到headA ,否则就指向其后继节点
        pB = pB === null? headA: pB.next;
    }
    return pA
}; 

六、其他链表题

var middleNode = function(head) {
    //使用快慢指针
    let fast = head;
    let slow = head;
    while(fast && fast.next) {
        fast = fast.next.next;
        slow = slow.next;
    }
    return slow
};

以上代码是自己做题的总结,可能不够完美哈哈哈,建议还是多看看LeetCode的题解喔

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值