【leetcode】链表 && 双指针

总结

【参考:一文搞懂单链表的六大解题套路 :: labuladong的算法小抄

一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 dummy 节点 【21题】

双指针

【参考:蓝桥杯算法竞赛系列第七章——六道力扣经典带你刷爆双指针_安然无虞的博客-CSDN博客
双指针分为三种:

  • 普通的指针:多是两个指针往同一个方向移动;

  • 对撞指针(多用于有序的情况):两个指针面对面移动(比如一头一尾往中间移动);left,right

  • 快慢的指针:慢指针+快指针。slow,fast

注意:题目中大都是对撞指针和快慢指针

    // 注意循环条件 具体查看876、141题
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        // other code...
    }

简单

21. 合并两个有序链表

【参考:21. 合并两个有序链表 - 力扣(LeetCode)

在这里插入图片描述

在这里插入图片描述

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy=new ListNode(-1); // 虚拟头结点
        ListNode p=dummy; // 哨兵节点 用p遍历并串联起两个列表,最后返回dummy.next即可
        while(list1 !=null && list2!=null){
            if(list1.val > list2.val){
                p.next=list2;
                list2=list2.next;
            }else{
                p.next=list1;
                list1=list1.next;
            }
            p=p.next;// 前进一步
        }
        // 下面两个链表只有一个不为空
        if(list1!=null){
            p.next=list1;
        }
        if(list2!=null){
            p.next=list2;
        }

        return dummy.next;
    }
}

203. 移除链表元素

参考:203. 移除链表元素 - 力扣(LeetCode)

设置虚拟头节点,方便把删除头节点和非头节点的操作统一化

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummy=new ListNode(0);//虚拟头结点
        dummy.next=head;// 把虚拟头结点指向链表头节点
        ListNode prev=dummy;//head的前一个结点
        while(head!=null){
            if(head.val==val){ // head指向的节点就是要被删除的节点
                prev.next=head.next;
            }else{
                prev=head;
            }
            head=head.next;
        }
        return dummy.next; // 虚拟头结点的下一个节点才是列表的头节点
    }
}

206. 反转链表

参考:206. 反转链表 - 力扣(LeetCode)

双指针法

从前往后翻转指针指向

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode temp=null;// 保存cur的下一个节点
        ListNode cur=head; 
        ListNode pre=null;
        while(cur!=null){
            temp=cur.next; // 保存下一个节点
            cur.next=pre; // 改变 cur->next 的指向,此时cur节点已经反转了
            // 继续移动pre和cur指针
            pre=cur; 
            cur=temp;
        }
        return pre;
    }
}

参考:代码随想录

// 递归 
class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(null, head);
    }

    private ListNode reverse(ListNode prev, ListNode cur) {
        if (cur == null) {
            return prev; // 返回链表
        }
        ListNode temp = null;
        temp = cur.next;// 先保存下一个节点
        cur.next = prev;// 反转
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // prev = cur;
        // cur = temp;
        return reverse(cur, temp);
    }
}

递归
有点难理解
参考:递归反转链表的一部分 :: labuladong的算法小抄

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null||head.next==null)
            return head;
        ListNode p=reverseList(head.next);
        head.next.next=head;
        head.next=null;
        return p;
    }
}

876. 链表的中间结点

【参考:876. 链表的中间结点 - 力扣(LeetCode)

快慢指针

本题采用“快慢指针”解决,慢指针一次走一步,快指针一次走两步,不过需要注意的是本题受到节点总数是奇偶的影响,
当节点总数是奇数时,fast.next == NULL,slow就指向了中间结点;
当节点总数是偶数时,fast == NULL,slow就指向了中间结点,
所以循环条件有两个,但是循环条件的先后顺序也是有讲究的,不信你看代码。

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow,fast;
        slow=fast=head;
        // 奇数 || 偶数
        //注意循环条件不能写成fast.next && fast这种形式,原因在于fast走两步后可能就指向空了
    	//再执行循环条件fast.next会导致空指针异常,越界了,但是fast写在前面就不会出现上述情况,因为&&存在短路求值
        while(fast!=null && fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow; //此时slow就指向中间节点
    }
}

141. 环形链表

【参考:141. 环形链表 - 力扣(LeetCode)

每当慢指针 slow 前进一步,快指针 fast 就前进两步。

如果 fast 最终遇到空指针,说明链表中没有环;

如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈,说明链表中含有环。

public class Solution {
    public boolean hasCycle(ListNode head) {
        // 快慢指针初始化指向 head
        ListNode slow = head, fast = head;
        // 快指针走到末尾时停止
        while (fast != null && fast.next != null) {
            // 慢指针走一步,快指针走两步
            slow = slow.next;
            fast = fast.next.next;
            // 快慢指针相遇,说明含有环
            if (slow == fast) {
                return true;
            }
        }
        // 不包含环
        return false;
    }
}

剑指 Offer 22. 链表中倒数第k个节点

【参考:剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode)

快慢指针

本题从1开始计数,即链表的头节点是第1个节点,尾节点是倒数第1个节点。
先让fast走k步,然后fast和slow再一起走直到末尾

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode slow,fast;
        slow=fast=head;
        
        while(k>0){
            fast=fast.next;
            k--;
        }
        while(fast!=null){
            slow=slow.next;
            fast=fast.next;
        }
        return slow; //此时slow就指向倒数第k个节点
    }
}

160. 相交链表

【参考:160. 相交链表 - 力扣(LeetCode)

这题难点在于,由于两条链表的长度可能不同,两条链表之间的节点无法对应:
如果用两个指针 p1 和 p2 分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点 c1。

解决这个问题的关键是,通过某些方式,让 p1 和 p2 能够同时到达相交节点 c1。

如果用两个指针 p1 和 p2 分别在两条链表上前进,我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。

如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1:

当指针 p1 和 p2 指向同一个节点或者都为空时,返回它们指向的节点或者 null。

p1:a1 c1 b1 b2 c1
p2:b1 b2 c1 a1 c1
p1指向c1,p2指向c1,退出while
返回c1

p1:a1 b1 b2
p2:b1 b2 a1
p1指向b2,p2指向a1,进入while
p1=p1.next=null
p2=p2.next=null 退出while,返回null
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    // p1 指向 A 链表头结点,p2 指向 B 链表头结点
    ListNode p1 = headA, p2 = headB;
    while (p1 != p2) {
        // p1 走一步,如果走到 A 链表末尾,转到 B 链表
        if (p1 == null) p1 = headB;
        else            p1 = p1.next;
        // p2 走一步,如果走到 B 链表末尾,转到 A 链表
        if (p2 == null) p2 = headA;
        else            p2 = p2.next;
    }
    return p1;
}

中等

707. 设计链表 ***

参考:707. 设计链表 - 力扣(LeetCode)

单链表

//单链表
class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}
class MyLinkedList {

    int size; // 链表的长度
    ListNode head; // 链表虚拟头节点

    //初始化链表
    public MyLinkedList() {
        size=0;
        head=new ListNode(0); //虚拟头结点
    }
    
    //获取第index个节点的数值
    public int get(int index) {
         //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode curNode=head;
        //包含一个虚拟头节点,所以查找第 index+1 个节点
        for(int i=0;i<=index;i++){
            curNode=curNode.next;
        }
        return curNode.val;
    }
    
    //在链表最前面插入一个节点
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    //在链表的最后插入一个节点
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        //找到要插入节点的前驱
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
        size++;
    }
    
    //删除第index个节点
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        //找到要删除节点的前驱
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
        size--;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

双链表

参考:设计链表【官方】 - 设计链表 - 力扣(LeetCode)

//双链表
class ListNode {
      int val;
      ListNode next,prev;
      ListNode(int x) {val = x;}
}
    
class MyLinkedList {
 
    int size;
    ListNode head,tail;//Sentinel node 虚拟头尾节点

    /** Initialize your data structure here. */
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
        tail = new ListNode(0);
        head.next = tail;
        tail.prev = head;
    }
    
    /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    public int get(int index) {
        if(index < 0 || index >= size){return -1;}
        ListNode cur = head;

        // 通过判断 index < (size - 1) / 2 来决定是从头结点还是尾节点遍历,提高效率
        if(index < (size - 1) / 2){
            for(int i = 0; i <= index; i++){
                cur = cur.next;
            }            
        }else{
            cur = tail;
            for(int i = 0; i <= size - index - 1; i++){
                cur = cur.prev;
            }
        }
        return cur.val;
    }
    
    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    public void addAtHead(int val) {
        ListNode cur = head;
        ListNode newNode = new ListNode(val);
        
        newNode.next = cur.next;
        cur.next.prev = newNode;
        cur.next = newNode;
        newNode.prev = cur;
        
        size++;
    }
    
    /** Append a node of value val to the last element of the linked list. */
    public void addAtTail(int val) {
        ListNode cur = tail;
        ListNode newNode = new ListNode(val);
        
        newNode.next = tail;
        newNode.prev = cur.prev;
        cur.prev.next = newNode;
        cur.prev = newNode;
        
        size++;
    }
    
    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    public void addAtIndex(int index, int val) {
        if(index > size){return;}
        if(index < 0){index = 0;}
        ListNode cur = head;
        for(int i = 0; i < index; i++){
            cur = cur.next;
        }
        ListNode newNode = new ListNode(val);
        newNode.next = cur.next;
        cur.next.prev = newNode;
        newNode.prev = cur;
        cur.next = newNode;
        size++;
    }
    
    /** Delete the index-th node in the linked list, if the index is valid. */
    public void deleteAtIndex(int index) {
        if(index >= size || index < 0){return;}
        ListNode cur = head;
        for(int i = 0; i < index; i++){
            cur = cur.next;
        }
        cur.next.next.prev = cur;
        cur.next = cur.next.next;
        size--;
    }
}

19. 删除链表的倒数第 N 个结点

参考:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

快慢指针

参考:双指针技巧总结 :: labuladong的算法小抄
参考:代码随想录


class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead=new ListNode(0); // 虚拟头节点
        dummyHead.next=head;
        ListNode fast,slow;
        fast=slow=dummyHead;
        // 快指针先前进 n 步
        while(n>0){
            fast=fast.next;
            n--;
        }
        if(fast==null){
            // 如果此时快指针走到头了,说明倒数第 n 个节点就是第一个节点
            return head.next; // 返回第二个节点
        }
        fast=fast.next; // fast再提前走一步,因为需要让后面slow指向删除节点的上一个节点
        // 让慢指针和快指针同步向前
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        // slow.next 就是倒数第 n 个节点,删除它
        slow.next=slow.next.next;
        return dummyHead.next; // 返回第一个节点

    }
}

142. 环形链表 II

参考:142. 环形链表 II - 力扣(LeetCode)

参考:双指针技巧总结 :: labuladong的算法小抄
在这里插入图片描述

参考:代码随想录


public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast,slow;
        fast=slow=head;
        while (fast != null && fast.next != null) { // 因为fast一次走两步
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;
        }
        if (fast == null || fast.next == null){
            return null; //无环
        }
        
        // 此时fast和slow已经在环中相遇了,接下来找入环的第一个节点
        
        slow=head; // 重新指向head再走一遍
        while(slow!=fast){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
     
    }
}

2. 两数相加

【参考:2. 两数相加 - 力扣(LeetCode)

labuladong

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        # 在两条链表上的指针
        p1=l1
        p2=l2 
        #  虚拟头结点(构建新链表时的常用技巧) 最后返回dummy.next就可以
        dummy=ListNode(-1) 
        p=dummy # 指针 p 负责构建新链表
        carry=0 # 记录进位

        # 开始执行加法,两条链表走完且没有进位时才能结束循环
        while p1!=None or p2!=None or carry>0:
            val=carry # 两数相加值 先加上上次的进位

            if p1!=None:
                val+=p1.val
                p1=p1.next
            if p2!=None:
                val+=p2.val
                p2=p2.next
                
            # 处理进位情况    
            carry=val // 10
            val=val % 10
            # 构建新节点
            p.next=ListNode(val)
            p=p.next
            
        # 返回结果链表的头结点(去除虚拟头结点)
        return dummy.next



       # 下面这样也行
        while p1!=None or p2!=None:
            ...

        if carry==1:
            p.next=ListNode(carry)
            
        
        return dummy.next

困难

23. 合并K个升序链表

【参考:23. 合并K个升序链表 - 力扣(LeetCode)

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) 
            return null;
            
        ListNode dummy=new ListNode(-1);
        ListNode p=dummy;

        PriorityQueue<ListNode> pq=new PriorityQueue<>(
            lists.length,(ListNode a,ListNode b)->{return a.val-b.val;}
        );

        for(ListNode head:lists){
            if (head != null)
                pq.add(head);
        }

        while(!pq.isEmpty()){
            ListNode temp=pq.poll();// 最小节点
            p.next=temp;
            p=p.next;
            if(temp.next!=null){
                pq.add(temp.next);// 把该节点的下一个节点也加入进去
            }          
        }
        return dummy.next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值