代码随想录 LeetCode链表篇 Java

news2025/1/21 21:55:26

文章目录

  • (简单)203. 移除链表元素
  • (中等)707. 设计链表
  • (简单)206. 反转链表
  • (中等)24. 两两交换链表中的节点
  • (中等)19. 删除链表的倒数第 N 个结点
  • (简单)面试题 02.07. 链表相交
  • (*中等)142. 环形链表 II


(简单)203. 移除链表元素

在这里插入图片描述
在这里插入图片描述
我的思路:

要想移除链表中的某一个节点,那么就需要知道该节点的前一个节点。

其次是如果要删除的节点就是第一个,那么就先处理第一个节点就是要删除节点的情况。

/**
 * 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) {
        if (head == null) {
            return null;
        }
        while (head != null && head.val == val) {
            head = head.next;
        }
        if (head == null) {
            return null;
        }

        ListNode pre = head;
        ListNode p = head.next;
        while (p != null) {
            if (val == p.val) {
                pre.next = p.next;
            } else {
                pre = p;
            }
            p = p.next;
        }
        return head;
    }
}

在这里插入图片描述
官方思路,迭代

使用迭代的方法删除链表中所有节点值等于特定值的节点。

用temp表示当前节点。如果temp的下一个节点不为空且下一个节点的节点值等于给定的val,则需要删除下一个节点。删除下一个节点可以通过以下做法实现:

temp.next = temp.next.next;

如果temp的下一个节点的节点值不等于给定的val,则保留下一个节点,将temp移动到下一个节点即可。

当temp的下一个节点为空时,链表遍历结束,此时所有节点值等于val的节点都被删除。

具体实现方面,由于链表的头节点head有可能需要被删除,因此创建哑节点preHead,令preHead.next=head,初始化p=preHead,然后遍历链表进行删除操作。最终返回preHead.next即为删除操作后的头节点。
在这里插入图片描述

/**
 * 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) {
        if (head == null) {
            return null;
        }

        ListNode preHead = new ListNode(-1);
        preHead.next = head;
        ListNode p = preHead;
        while (p.next != null) {
            if (p.next.val == val) {
                p.next = p.next.next;
            } else {
                p = p.next;
            }
        }
        return preHead.next;
    }
}

(中等)707. 设计链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

设计一个链表的节点类,然后编写代码即可

class MyLinkedList {

    private ListNode preHead;

    public MyLinkedList() {
        preHead = new ListNode(-1);
    }

    public int get(int index) {

        ListNode p = preHead.next;
        int i = 0;
        while (p != null && index != i) {
            p = p.next;
            i++;
        }
        if (p != null) {
            return p.val;
        } else {
            return -1;
        }
    }

    public void addAtHead(int val) {
        ListNode newNode = new ListNode(val);
        newNode.next = preHead.next;
        preHead.next = newNode;
    }

    public void addAtTail(int val) {
        ListNode newNode = new ListNode(val);
        ListNode p = preHead;
        while (p.next != null) {
            p = p.next;
        }
        p.next = newNode;
    }

    public void addAtIndex(int index, int val) {
        int i = 0;
        ListNode q = preHead;
        ListNode p = preHead.next;
        ListNode newNode = new ListNode(val);
        while (p != null && index != i) {
            p = p.next;
            q = q.next;
            i++;
        }
        if (p != null) {
            newNode.next = p;
            q.next = newNode;
        } else {
            if (i == index) {
                q.next = newNode;
            }
        }
    }

    public void deleteAtIndex(int index) {
        ListNode q = preHead;
        ListNode p = preHead.next;
        int i = 0;
        while (p != null && index != i) {
            q = q.next;
            p = p.next;
            i++;
        }
        if (p != null) {
            q.next = p.next;
        }
    }
}

class ListNode {
    int val;
    ListNode next;

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

/**
 * 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);
 */

官方思路,方法一:单向链表

实现单向链表,即每个节点仅存储本身的值和后继节点,除此之外,还需要一个哨兵(sentinel)节点作为头节点,和一个size参数保存有效节点数。如下图所示。

在这里插入图片描述
初始化时,只需创建头节点head和size即可。

实现get(index)时,先判断有效性,再通过循环来找到对应的节点的值。

官方代码,相较于我自己的代码,区别在于加入了size熟悉,方便判断参数index是否合法

class MyLinkedList {

    ListNode preHead;
    int size;

    public MyLinkedList() {
        size = 0;
        preHead = new ListNode(-1);
    }

    public int get(int index) {
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode cur = preHead;
        for (int i = 0; i <= index; i++) {
            cur = cur.next;
        }
        return cur.val;
    }

    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    public void addAtTail(int val) {
        addAtIndex(size, val);
    }

    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) {
            return;
        }
        size++;
        ListNode q = preHead;
        for (int i = 0; i < index; i++) {
            q = q.next;
        }
        ListNode newNode = new ListNode(val);
        newNode.next = q.next;
        q.next = newNode;
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        ListNode cur = preHead;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
    }
}

class ListNode {
    int val;
    ListNode next;


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

/**
 * 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);
 */

官方思路,方法二:双向链表

实现双向链表,即每个节点都要存储本身的值,后继节点和前驱节点。除此之外,需要一个哨兵节点作为头节点head和一个哨兵节点作为尾节点tail。仍然需要一个size参数保存有效节点数。

双向链表的代码比单向链表复杂一点,因为总是要判断那一边离要操作的index的位置较近

在这里插入图片描述

class MyLinkedList {

    ListNode head;
    ListNode tail;
    int size;

    public MyLinkedList() {
        size = 0;
        head = new ListNode(-1);
        tail = new ListNode(-1);
        head.next = tail;
        tail.prev = head;
    }

    public int get(int index) {
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode cur;

        //判断,距离头节点近,还是尾节点近
        if (index < size - index - 1) {
            cur = head;
            for (int i = 0; i <= index; i++) {
                cur = cur.next;
            }
        } else {
            cur = tail;
            for (int i = size; i > index; i--) {
                cur = cur.prev;
            }
        }

        return cur.val;
    }

    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    public void addAtTail(int val) {
        addAtIndex(size, val);
    }

    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) {
            return;
        }
        ListNode p, q;
        //离head近
        if (index < size - index + 1) {
            q = head;
            for (int i = 0; i < index; i++) {
                q = q.next;
            }
            p = q.next;

        } else {
            p = tail;
            //离tail近
            for (int i = 0; i < size - index; i++) {
                p = p.prev;
            }
            q = p.prev;
        }
        size++;
        ListNode newNode = new ListNode(val);
        newNode.next = p;
        q.next = newNode;
        newNode.prev = q;
        p.prev = newNode;
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        ListNode p, q;
        if (index < size - index + 1) {
            q = head;
            for (int i = 0; i < index; i++) {
                q = q.next;
            }
            p = q.next;

        } else {
            p = tail;
            for (int i = 0; i < size - index; i++) {
                p = p.prev;
            }
            q = p.prev;
        }
        size--;
        q.next = p.next;
        p.next.prev = q;
    }
}

class ListNode {
    int val;
    ListNode next;
    ListNode prev;


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

/**
 * 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);
 */

(简单)206. 反转链表

在这里插入图片描述
在这里插入图片描述
在遍历链表时,将当前节点的next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。

/**
 * 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 pre = head;
        ListNode p = head.next;
        pre.next = null;
        ListNode q;
        while (p != null) {
            q = p.next;
            p.next = pre;
            pre = p;
            p = q;
        }
        return pre;
    }
}

复杂度分析:

  • 时间复杂度:O(n),其中n是链表的长度。需要遍历链表一次。
  • 空间复杂度:O(1)。

官方思路,递归

递归版本复杂一点,其关键在于反向工作。假设链表的其余部分已经被翻转

假设链表为:
n1->n2->n3->…->nk->nk+1->…->nm

若从节点nk+1到nm已经被反转,而现在需要将nk+1指向nk

n1->n2->n3->…->nk->nk+1<-…<-nm

nk.next.next = nk

需要注意的是,n1的下一个节点必须指向空。如果忽略了这一点,链表中可能产生环。

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

复杂度分析:

  • 时间复杂度:O(n),其中n是链表的长度。需要对链表的每个节点进行反转操作
  • 空间复杂度:O(n),其中n是链表的长度。空间复杂度主要取决于递归调用的栈空间,最多为n层。

(中等)24. 两两交换链表中的节点

在这里插入图片描述
官方思路,方法一:递归

可以通过递归的方式实现两两交换链表中的节点。

递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。

如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点编程新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。

链表中的其余节点的两两交换可以递归地实现。在对链表中地其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。

用head表示原始链表的头节点,新的链表的第二个节点,用newHead表示新的链表的头节点,原始链表的第二个节点,则原始链表中的其余节点的头节点是newHead.next。令head.next=swapPairs(newHead.next),表示将其余节点进行两两交换,交换后新的头节点为head的下一个节点。然后令newHead.next = head,即完成了所有节点的交换。最后返回新的链表的头节点newHead。

在这里插入图片描述

/**
 * 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 swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = head.next;
        head.next = swapPairs(newHead.next);
        newHead.next = head;

        return newHead;
    }
}

复杂度分析:

  • 时间复杂度:O(n),其中n是链表的节点数量。需要对每个节点进行更新指针的操作。
  • 空间复杂度:O(n),其中n是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。

官方思路,方法二:迭代

也可以通过迭代的方式实现两两交换链表中的节点。

创建哑节点dummyHead,令dummyHead.next = head。令temp表示当前到达的节点,初始时temp = dummyHead。每次需要交换temp后面的两个节点。

如果temp的后面没有节点或者只有一个节点,则没有更多的节点需要交换,因此结束交换。否则,获得temp后面的两个节点node1和node2,通过更新节点的指针关系实现两两交换节点。

具体而言,交换前的节点关系是temp->node1->node2
交换后的节点关系要变成temp->node2->node1

temp.next = node2
node1.next = node2.next
node2.nexr = node1

完成上面的3步操作后,再令temp=node1,对链表的其余节点进行两两交换,直到全部节点都被两两交换。

两两节点交换之后,新的链表的头节点是dummyHead.next。

/**
 * 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 swapPairs(ListNode head) {

        if (head == null || head.next == null) {
            return head;
        }

        ListNode preHead = new ListNode(-1);
        preHead.next = head;
        ListNode temp = preHead;
        while (temp.next!=null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            temp = node1;
        }
        return preHead.next;
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(n),其中n表示链表的节点数量。需要对每个节点进行更新指针的操作,
  • 空间复杂度:O(1)

(中等)19. 删除链表的倒数第 N 个结点

在这里插入图片描述
在这里插入图片描述

我的思路,首先顺序遍历一遍链表,统计该链表有多少个节点,然后题目中给出的是倒数第N个结点,算出要删除的节点是正数的第多少个,删除它

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        int count = 0;
        ListNode p = head;
        while (p != null) {
            count++;
            p = p.next;
        }
        n = count - n + 1;
        ListNode pre = new ListNode(-1);
        pre.next = head;
        ListNode q = pre;
        p = head;
        for (int i = 1; i < n; i++) {
            q = q.next;
            p = p.next;
        }
        q.next = p.next;
        return pre.next;
    }
}

在这里插入图片描述

复杂度分析:

  • 时间复杂度:O(L),其中,L是链表的长度
  • 空间复杂度:O(1)

官方其他思路,栈

在遍历链表的同时将所有节点一次入栈。根据栈【先进后出】的原则,弹出栈的第n个节点就是需要删除的节点,并且,目前栈顶的节点就是待删除节点的前驱节点。

在这里插入图片描述

import java.util.Stack;

/**
 * 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 removeNthFromEnd(ListNode head, int n) {
        ListNode preHead = new ListNode(-1, head);
        Stack<ListNode> stack = new Stack<>();
        ListNode p = preHead;
        while (p != null) {
            stack.push(p);
            p = p.next;
        }
        for (int i = 0; i < n; i++) {
            stack.pop();
        }
        ListNode prev = stack.peek();
        prev.next = prev.next.next;
        return preHead.next;
    }
}

复杂度分析:

  • 时间复杂度:O(L),其中L是链表的长度
  • 空间复杂度:O(L),其中L是链表的长度。主要为栈的开销

官方其他思路,双指针

由于需要删除倒数第n个节点,因此可以使用两个指针first和second同时对链表进行遍历,其中first是快指针,second是慢指针

first比second超前n个节点,也就是first和second中间间隔了n个节点,那么当first指向空时,second正好指向的是要删除节点的前一个节点

在这里插入图片描述

/**
 * 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 removeNthFromEnd(ListNode head, int n) {

        ListNode preHead = new ListNode(-1, head);

        ListNode first = preHead;
        ListNode second = preHead;
        for (int i = 0; i <= n; i++) {
            first = first.next;
        }

        while (first != null) {
            second = second.next;
            first = first.next;
        }

        second.next = second.next.next;
        return preHead.next;
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(L),其中L是链表的长度
  • 空间复杂度:O(1)

(简单)面试题 02.07. 链表相交

在这里插入图片描述
在这里插入图片描述
我的思路,将节点存到HashSet中,当要加入某节点时,如果set中已经存在该节点,那么该节点就是两个链表相交的节点

import java.util.HashSet;

/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode(int x) {
 * val = x;
 * next = null;
 * }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p = headA;
        ListNode q = headB;
        HashSet<ListNode> set = new HashSet<>();
        ListNode ans = null;
        while (p != null || q != null) {

            if (p != null) {
                if (set.add(p)) {
                    p = p.next;
                } else {
                    ans = p;
                    break;
                }
            }
            if (q != null) {
                if (set.add(q)) {
                    q = q.next;
                } else {
                    ans = q;
                    break;
                }
            }
        }
        return ans;
    }
}

官方思路,方法一:哈希集合

判断两个集合是否相交,可以使用哈希集合存储链表节点。

首先判断链表headA,并将链表headA中的每个节点加入哈希集合中。然后遍历链表headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:

  • 如果当前节点不在哈希集合中,则继续遍历下一个节点
  • 如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表中headB中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点

如果链表headB中的所有节点都不在哈希集合中,则两个链表不相交,返回null。

在这里插入图片描述

import java.util.HashSet;

/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode(int x) {
 * val = x;
 * next = null;
 * }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        HashSet<ListNode> set = new HashSet<>();
        ListNode temp = headA;
        while (temp != null) {
            set.add(temp);
            temp = temp.next;
        }
        temp = headB;
        while (temp != null) {
            if (set.contains(temp)) {
                return temp;
            }
            temp = temp.next;
        }
        return null;
    }
}

复杂度分析:

  • 时间复杂度:O(m+n),其中m+n分别表示headA和headB的链表长度之和。需要遍历两个链表各一次
  • 空间复杂度:O(m),其中m是链表headA的长度。需要使用哈希集合存储headA中的全部节点。

官方思路,方法二:双指针

使用双指针方法,可以将空间复杂度降至O(1)

只有当链表headA和headB不为空时,两个链表才可能相交。所以,首先判断两个链表是否为空

当链表headA和headB都不为空时,创建两个指针pA和pB,初始时分别指向两个链表的头节点headA和headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:

  • 每步操作需要同时更新pA和pB
  • 如果指针A不为空,则将指针pA移动到下一个节点;如果指针pB不为空,则将指针pB移动到下一个节点
  • 如果指针pA为空,则将指针pA移动到链表headB的头节点;如果指针pB为空,则将指针pB移动到headA的头节点
  • 当指针pA和pB指向同一个节点或者都为空时,返回他们指向的节点或者null

官方给出的证明:
在这里插入图片描述

import java.util.HashSet;

/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode(int x) {
 * val = x;
 * next = null;
 * }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }

        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = (pA == null ? headB : pA.next);
            pB = (pB == null ? headA : pB.next);
        }
        return pA;
    }
}

(*中等)142. 环形链表 II

在这里插入图片描述

我的思路:用HashSet存储节点,当添加某一结点时,如果该节点已经存在于set中,那么就说明该节点是进入环形链表的第一个节点
在这里插入图片描述

import java.util.HashSet;

/**
 * Definition for singly-linked list.
 * class ListNode {
 * int val;
 * ListNode next;
 * ListNode(int x) {
 * val = x;
 * next = null;
 * }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {

        if (head == null) {
            return head;
        }

        ListNode p = head;
        HashSet<ListNode> set = new HashSet<>();
        ListNode ans = null;
        while (p != null) {
            if (set.add(p)) {
                p = p.next;
            } else {
                ans = p;
                break;
            }
        }
        return ans;
    }
}

复杂度分析:

  • 时间复杂度:O(N),其中N为链表中节点的数目。恰好需要访问链表中的每一个节点。
  • 空间复杂度:O(N),其中N为链表中的节点数目。我们将链表中的每个节点都保存在哈希表当中

官方其他思路,快慢指针

使用两个指针,fast和slow。它们起始都位于链表的头部。随后,slow指针每次向后移动一个位置,而fast指针每次向后移动两个位置。如果链表中存在环,则fast指针最终再次与slow指针在环中相遇。

在这里插入图片描述
设链表中环外的部分的长度为a。slow指针进入环后,又走了b的距离与fast相遇。此时fast指针已经走完了环的n圈。因此,它走过的距离是a+n(b+c)+b=a+(n+1)b+nc

根据题意,任意时刻,fast走过的距离都是slow指针的两倍。因此,有:

a+(n+1)b+nc=2(a+b) => a=c+(n-1)(b+c)

有了上面推导出来的式子,就会发现,从相遇点到入环点的距离加上n-1圈的环长,恰好等于从链表头部到入环点的距离。

因此,当发现slow与fast相遇时,再额外使用一个指针,它指向链表头部;随后,他和slow每次向后移动一个位置。最终,它们会在入环点相遇

在这里插入图片描述

import java.util.HashSet;

/**
 * Definition for singly-linked list.
 * class ListNode {
 * int val;
 * ListNode next;
 * ListNode(int x) {
 * val = x;
 * next = null;
 * }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {

        if (head == null) {
            return null;
        }

        ListNode slow = head;
        ListNode fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (fast == slow) {
                ListNode p = head;
                while (p != slow) {
                    p = p.next;
                    slow = slow.next;
                }
                return p;
            }
        }
        return null;
    }
}

复杂度分析:

  • 时间复杂度:O(N),其中N为链表中节点的数目。在最初判断快慢指针是否相遇时,slow指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为O(N)+O(N)=O(N)
  • 空间复杂度:O(1),只使用了fast,slow,p指针

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/527260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Schlumberger ECLIPSE CRACK

Schlumberger ECLIPSE CRACK 工业和工程软件旨在模拟Schlumberger ECLIPSE Simulation的碳氢化合物&#xff0c;该模拟与Shelberger的技术服务有关&#xff0c;以及用于预测和历史的容器中当前方程数量的最新和最新解决方案集。动态行为是各种传统和不寻常的石油和天然气模式。…

DevExpress:报表控件绑定数据库数据源的三种方式(Winform)

1.写在前面 如果你是和我一样&#xff0c;第一次接触DevExpress&#xff0c;并且因为网上资源眼花缭乱无从下手&#xff0c;然后脑子一转直接到DevExpress官网寻找官方使用文档的&#xff0c;那我们的了解顺序应该差不多是一致的。 DevExpress官网&#xff1a;https://www.de…

【笔试强训选择题】Day12.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目录…

Facebook 手机应用广告:在移动时代实现营销突破

在移动时代&#xff0c;手机已经成为人们生活的重要组成部分。随着移动互联网的普及&#xff0c;人们更频繁地使用手机来浏览社交媒体、获取信息和进行购物。 对于企业而言&#xff0c;如何在移动平台上实现营销突破&#xff0c;吸引用户的注意力和提升品牌价值&#xff0c;是…

数说热点|社恐人群运动健身指南:不想去健身房,那就在家找面墙

连杰伦都开始跳操了&#xff0c;你还不动动动动动起来&#xff1f; 随着《运动者联盟》这档体育挑战真人秀节目的完美收官&#xff0c;忙碌生活中的运动激情似乎又被点燃了。5月9日&#xff0c;周杰伦现身厦门&#xff0c;在活动现场和刘耕宏合体跳起了《本草纲目》&#xff0…

音视频技术开发周刊 | 293

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 谷歌全面反攻 ChatGPT&#xff01;PaLM 2、Gemini 双杀&#xff0c;Bard 正式开放 以上是2023 Google I/O 大会的重点内容&#xff0c;AI含量极高。 谷歌推拥有26000个H10…

uni-app框架的小程序开发环境

文章目录 一、下载微信开发工具安装 二、构建uni-app开发环境2.1 Node.js下载与安装2.2 下载HBuilder-X2.3 创建uni-app项目2.4 填入uni-app专属标识和小程序标识 在阅读此博文前&#xff0c;需要注册完毕小程序账号 一、下载微信开发工具 微信开发工具下载地址 下载稳定版即…

代码随想录算法训练营day41 | 343. 整数拆分,96.不同的二叉搜索树

代码随想录算法训练营day41 | 343. 整数拆分&#xff0c;96.不同的二叉搜索树 343. 整数拆分解法一&#xff1a;动态规划 96.不同的二叉搜索树解法一&#xff1a;动态规划 总结 343. 整数拆分 教程视频&#xff1a;https://www.bilibili.com/video/BV1Mg411q7YJ 1、dp[i]代表…

【Linux命令】脚本里常用的几个命令sort,uniq,tr,cut,split,eval

脚本里常用的命令 一、SORT命令1.1、语法格式1.2常用选项 二、uniq命令2.1命令格式2.2常用选项2.3小实验&#xff0c;过滤出现三次以上的IP地址 三、tr命令3.1语法格式3.2常用选项3.3实验 四、cut命令4.1语法格式4.2常用选项 五、split命令5.1语法格式5.2常用选项 六、eval七、…

一个优秀系统构架师应具备的能力

作为软件开发的设计架构师&#xff0c;那么必须拥有一定的编程技能&#xff0c;同时有高超的学习新的架构设计、程序设计技能。另外&#xff0c;我觉得作为软件架构师&#xff0c;还必须了解一定的硬件、网络、服务器的基本知识。要不然&#xff0c;你都不知道有些什么材料可以…

基于MWORKS的电动汽车电平衡分析

1 引言 随着电动汽车的快速发展、电池技术的进步和智能电力管理系统的应用&#xff0c;电动汽车电平衡已经成为了电动汽车技术研究中的重要问题之一。 电动汽车电平衡是指车辆发电机、蓄电池、整车用电器在一定时间内的电能供给与消耗达到平衡状态。如果车辆电能产生与消耗无法…

USART硬件流控制概念以及原理(硬件控制流以及软件控制流)

USART 数据流控制 也就是 USART_HardwareFlowControl 一、流控制的作用 这里讲到的 “流”&#xff0c;指的是数据流&#xff1b;在数据通信中&#xff0c;流控制是管理两个节点之间数据传输速率的过程&#xff0c;以防止出现接收端的数据缓冲区已满&#xff0c;而发送端依然…

ResNet 论文理解含视频

ResNet 论文理解 论文理解 ResNet 网络的论文名字是《Deep Residual Learning for Image Recognition》&#xff0c;发表在2016年的 CVPR 上&#xff0c;获得了 最佳论文奖。ResNet 中的 Res 也是 Residual 的缩写&#xff0c;它的用意在于基于 残差 学习&#xff0c;让神经网…

真实业务场景使用-门面模式(外观)设计模式

1.前言 最近接到要修改的业务功能&#xff0c;这个业务增删改查很多功能都需要校验时间&#xff0c;比如&#xff1a; 1.失效时间不能超过自己父表的失效时间&#xff0c; 2.失效时间不能是当前时间 3.失效时间不能早于生效时间 类似这样的不同的判断还有很多&#xff0c;…

软考A计划-真题-分类精讲汇总-第十章(程序设计语言)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

【GO 编程语言】切片与Map

切片与Map 文章目录 切片与Map一、切片 Slice1.定义切片2.make 函数创建切片3.切片扩容与遍历4.在已有数组上创建切片5.切片是引用类型7.深拷贝、浅拷贝 二、Map1.Map 初始化2.map 的使用3.map 的遍历4.map 结合 slice 一、切片 Slice 1.定义切片 Go 语言切片是对数组的抽象。…

设置ELK集群账号密码

一、设置ELK集群账号密码 切换到es用户 主节点生成证书 cd /home/es/elasticsearch-7.6.2/bin ./elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass "" 将主节点证书发给其他两个节点 修改配置文件&#xff0c;启用x-pack&#xff1a;cat /…

Linux指令运行原理和权限

Linux指令运行原理和权限 一.命名行解释器二.权限1.用户分类2.什么是权限3.增删权限4.更改权限的拥有者5.三个概念1.权限掩码2.目录权限3.粘滞位 三.权限总结 一.命名行解释器 那么命令行解释器存在的意义&#xff1a;1.进行命令的解释。2.保护os&#xff0c;对于非法的请求&am…

TimesNet:用于一般时间序列分析的时间二维变化模型

摘要 时间序列分析在天气预报、异常检测和动作识别等广泛应用中具有极其重要的意义。本文重点研究时间变量建模&#xff0c;这是广泛分析任务的共同关键问题。以前的方法试图直接从一维时间序列完成此操作&#xff0c;由于错综复杂的时间模式&#xff0c;这极具挑战性。基于对…

使用flask获取树莓派摄像头监控视频

目录 1、安装flask库 2、使用flask打开网页传输视频 2.1 在树莓派终端桌面上&#xff0c;新建一个flask文件夹 2.2 在flask文件夹里面&#xff0c;新建一个template文件夹和app.py文件 2.3 在template文件夹里面&#xff0c;新建一个index.html文件 2.4 使用flask运行代码…