一篇通关代码随想录 - 链表

链表

  • 环形链表
  • 链表相交
  • 删除链表的倒数第 N 个结点
  • 两两交换链表中的节点
  • 反转链表
  • 设计链表
  • 移除链表元素

环形链表

142. 环形链表 II

思路

  • 如果链表有环,如何找出链表开始入环的第一个节点?
  • 利用快慢指针,先找到两个指针相遇的节点。没有相遇的节点,说明链表无环
  • 之后,定义一个指针从头出发,定义另一个指针从相遇的节点出发
  • 当这两个节点相遇的时候,相遇的节点就是入环的第一个节点。
    一篇通关代码随想录 - 链表_第1张图片

代码

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                ListNode index1 = fast;
                ListNode index2 = head;
                while(index1 != index2){
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

复杂度

时间复杂度:O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n。
空间复杂度:O(1)

相似题目

141. 环形链表
★287. 寻找重复数


链表相交

面试题 02.07. 链表相交

思路

2个链表相交,则说明从相交节点开始,2个链表长度就一样。

思路一

  • 2个链表从相交节点后面长度都一样,前面的不相交的节点长度有可能一样,也可能不一样
  • 不相交节点长度一样:使用2个指针同时出发,直到遇到相同节点,则说明找到了相交起始节点。
  • 不相交节点长度不一样:使用2个指针,先让指向较长链表的指针移动到与较短链表相同的位置,然后再同时出发,直到遇到相同节点,则说明找到了相交起始节点。

思路二 我走过你走过的路

我先走我的路,走完我的路再走你的路
你先走你的路,走完你的路再走我的路
如果我们的路相交,我们终会相遇
如果我们的路不相交,我们各自抵达终点

  • 2个指针一个从headA链表出发,一个从headB链表出发
  • 如果第一个指针遍历完headA链表,则重新指向headB链表
  • 如果第二个指针遍历完headB链表,则重新指向headA链表
  • 如果2个指针相遇,有2中情况:
  • 情况1:2个指针指向的节点不为null,则说明2个链表相交
  • 情况2:2个指针指向的节点为null,则说明2个链表不相交

代码

思路一

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int countA = 0;
        int countB = 0;
        ListNode curA = headA;
        ListNode curB = headB;
        // 统计链表A的长度
        while(curA != null){
            countA++;
            curA = curA.next;
        }
        // 统计链表B的长度
        while(curB != null){
            countB++;
            curB = curB.next;
        }
        curA = headA;
        curB = headB;
        // 计算他们的长度差
        int diff = countA - countB;
        // 如果A链表长
        if(diff > 0){
            while(diff-- > 0){
                curA = curA.next;
            }
            // 如果B链表长
        }else if(diff < 0){
            while(diff++ < 0){
                curB = curB.next;
            }
        }
        // 如果curA到了最后,说明2个链表不相交
        while(curA != null){
            if(curA == curB) return curA;
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}

思路二

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        while(curA != curB){
            // 如果curA到达null,则指向headB
            if(curA == null){
                curA = headB;
            }else{
                curA = curA.next;
            }
            // 如果curB到达null,则指向headA
            if(curB == null){
                curB = headA;
            }else{
                curB = curB.next;
            }
        }
        // 有可能是2个链表不相交,最后都到达了null
        // 有可能是2个链表相交
        return curA;
    }
}

复杂度

时间复杂度:O(a+b)
空间复杂度:O(1)


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

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

思路

因为链表是离散存储在内存中的,并且题目给的是单链表,所以无法从后开始定位节点。
如果是双向链表,则可以遍历到最后一个节点后,再向前遍历N个节点,则为倒数第N个节点。

  • 使用双指针,让这2个指针的间距为N,如果快指针到达NULL,则慢指针刚好到达倒数第N个节点。
  • 因为题目要求删除倒数第N个节点,所以可以调节2个指针的间距为N + 1,这样就可以直接删除倒数第N个节点。
  • 注意: 涉及到删除链表中的元素,要考虑链表第一个节点的删除情况,通常是使用虚拟头结点

代码

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode slow = dummyHead;
        ListNode fast = dummyHead;

        //先让快指针走n + 1步
        for(int i = 0; i <= n; i++){
            fast = fast.next;
        }

        while(fast != null){
            slow = slow.next;
            fast = fast.next;
        }
        slow.next = slow.next.next;
        return dummyHead.next;
    }
}

复杂度

时间复杂度: O(n)
空间复杂度: O(1)


两两交换链表中的节点

24. 两两交换链表中的节点

思路

  • 两两交换相邻的两个节点,如果最后剩一个节点,则不进行操作。不允许改变节点中的值
  • 这里要考虑头节点的交换情况,所以建议使用虚拟头结点,来进行统一操作

一篇通关代码随想录 - 链表_第2张图片
一篇通关代码随想录 - 链表_第3张图片

代码

迭代版本

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode cur = dummyHead;
        while(cur.next != null && cur.next.next != null){
            ListNode temp = cur.next.next.next;
            ListNode firstNode = cur.next;
            ListNode secondNoed = cur.next.next;
            cur.next = secondNoed;
            secondNoed.next = firstNode;
            firstNode.next = temp;
            cur = cur.next.next;
        }
        return dummyHead.next;
    }
}

递归版本

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;
        // 每次2个节点交换后,前面的节点
        ListNode newHead = head.next;
        head.next = swapPairs(newHead.next);
        newHead.next = head;
        // 返回前面的节点
        return newHead;
    }
}

复杂度

时间复杂度:O(n)
空间复杂度:O(1)

相似题目

★25. K 个一组翻转链表


反转链表

206. 反转链表

思路

  • 反转链表就是让下一个节点指向上一个节点
  • 如果是第一个节点,则让它指向null
  • 反转结束后,从原来的链表上看:
  • pre最终指向反转这一段的末尾
  • cur指向反转这一段后续的下一个节点

代码

class Solution {
    public ListNode reverseList(ListNode head) {
        /**
        cur: 代表遍历到的节点
        pre: 代表cur的上一个节点
        temp: 代表临时记录cur的next指针
        */

        ListNode pre = null;
        ListNode cur = head;
        while(cur != null){
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //反转结束后,从原来的链表上看:
        //pre最终指向反转这一段的末尾
        //cur指向反转这一段后续的下一个节点
        return pre;
    }
}

复杂度

时间复杂度:O(n)
空间复杂度:O(1)

相似题目

92. 反转链表 II
234. 回文链表


设计链表

707. 设计链表

思路

  • 对于链表的添加或删除操作,要考虑到头节点的操作
  • 所以通常设置虚拟头节点

代码

class ListNode{
    public int val;
    public ListNode next;
    public ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}

class MyLinkedList {

    //size存储链表元素的个数
    private int size;
    //虚拟头结点
    private ListNode head;

    public MyLinkedList() {
        //初始化 MyLinkedList 链表。
        this.head = new ListNode(-1);
        this.size = 0;
    }
    
    public int get(int index) {
        //获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
        if(index < 0 || index >= this.size){
            return -1;
        }
        ListNode currentNode = head;
        for(int i = 0; i <= index; i++){
            currentNode = currentNode.next;
        }
        return currentNode.val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    public void addAtIndex(int index, int val) {
        //将一个值为 val 的节点插入到链表中下标为 index 的节点之前。
        //如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        ListNode currentNode = head;
        for(int i = 0; i < index; i++){
            currentNode = currentNode.next;
        }
        ListNode temp = currentNode.next;
        ListNode newNode = new ListNode(val);
        currentNode.next = newNode;
        newNode.next = temp;
    }   
    
    public void deleteAtIndex(int index) {
        if(index < 0 || index >= this.size){
            return;
        }
        size--;
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}

复杂度

时间复杂度: 涉及 index 的相关操作为 O(index), 其余为 O(1)
空间复杂度: O(n)


移除链表元素

移除链表元素

思路

  • 删除链表节点时要考虑第一个节点的删除
  • 可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了

一篇通关代码随想录 - 链表_第4张图片

代码

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummyHead = new ListNode(0, head);
        ListNode p0 = dummyHead;
        ListNode cur = head;
        while(cur != null){
            if(cur.val == val){
                p0.next = cur.next;
            }else{
                p0 = cur;
            }
            cur = cur.next;
        }
        return dummyHead.next;
    }
}

复杂度

时间复杂度: O(n)
空间复杂度: O(1)

相似题目

237. 删除链表中的节点

你可能感兴趣的:(LeetCode,链表,数据结构)