【数据结构系列】链表

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
img

  • 推荐:kuan 的首页,持续学习,不断总结,共同进步,活到老学到老
  • 导航
    • 檀越剑指大厂系列:全面总结 java 核心技术点,如集合,jvm,并发编程 redis,kafka,Spring,微服务,Netty 等
    • 常用开发工具系列:罗列常用的开发工具,如 IDEA,Mac,Alfred,electerm,Git,typora,apifox 等
    • 数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等
    • 懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
    • 数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨

博客目录

    • 一.认识链表
      • 1.链表定义与分类
      • 2.哨兵节点
      • 3.查询性能
      • 4.插入和删除性能
    • 二.单向链表
      • 1.定义
      • 2.头部添加
      • 3.while 遍历
      • 4.for 遍历
      • 5.迭代器遍历
      • 6.递归遍历
      • 7.尾部添加
      • 8.尾部添加多个
      • 9.根据索引获取
      • 10.插入
      • 11.删除
    • 三.单向链表(带哨兵)
      • 1.带哨兵单向链表
      • 2.获取索引节点
      • 3.插入删除节点
    • 四.双向链表(带哨兵)
    • 五.环形链表(带哨兵)
    • 六.解题模版
      • 1.反转链表再正向遍历
      • 2.递归遍历
    • 七.链表题目
      • 1.两数相加
      • 2.两数相加 II
      • 3.合并两个有序链表
      • 4.删除排序链表中的重复元素
      • 5.环形链表
      • 6.相交链表
      • 7.移除链表元素
      • 8.反转链表
      • 9.回文链表
      • 10.链表的中间结点

一.认识链表

1.链表定义与分类

在计算机科学中,链表是数据元素的线性集合,其每个元素都指向下一个元素,元素存储上并不连续

可以分类为.

  • 单向链表,每个元素只知道其下一个元素是谁

image-20221110083407176

  • 双向链表,每个元素知道其上一个元素和下一个元素

image-20221110083427372

  • 循环链表,通常的链表尾节点 tail 指向的都是 null,而循环链表的 tail 指向的是头节点 head

image-20221110083538273

2.哨兵节点

链表内还有一种特殊的节点称为哨兵(Sentinel)节点,也叫做哑元( Dummy)节点,它不存储数据,通常用作头尾,用来简化边界判断,如下图所示

image-20221110084611550

3.查询性能

随机访问性能

根据 index 查找,时间复杂度 O(n)

4.插入和删除性能

插入或删除性能

  • 起始位置:O(1)
  • 结束位置:如果已知 tail 尾节点是 O(1),不知道 tail 尾节点是 O(n)
  • 中间位置:根据 index 查找时间 + O(1)

二.单向链表

1.定义

根据单向链表的定义,首先定义一个存储 value 和 next 指针的类 Node,和一个描述头部节点的引用

public class SinglyLinkedList {private Node head; // 头部节点private static class Node { // 节点类int value;Node next;public Node(int value, Node next) {this.value = value;this.next = next;}}
}
  • Node 定义为内部类,是为了对外隐藏实现细节,没必要让类的使用者关心 Node 结构
  • 定义为 static 内部类,是因为 Node 不需要与 SinglyLinkedList 实例相关,多个 SinglyLinkedList 实例能共用 Node 类定义

2.头部添加

public class SinglyLinkedList {// ...public void addFirst(int value) {this.head = new Node(value, this.head);}
}
  • 如果 this.head == null,新增节点指向 null,并作为新的 this.head
  • 如果 this.head != null,新增节点指向原来的 this.head,并作为新的 this.head
    • 注意赋值操作执行顺序是从右到左

3.while 遍历

public class SinglyLinkedList {// ...public void loop() {Node curr = this.head;while (curr != null) {// 做一些事curr = curr.next;}}
}

4.for 遍历

public class SinglyLinkedList {// ...public void loop() {for (Node curr = this.head; curr != null; curr = curr.next) {// 做一些事}}
}
  • 以上两种遍历都可以把要做的事以 Consumer 函数的方式传递进来
    • Consumer 的规则是一个参数无返回值,因此像 System.out::println 方法等都是 Consumer
    • 调用 Consumer 时,将当前节点 curr.value 作为参数传递给它
//使用Consumer传参
public void loop4(Consumer<Integer> consumer) {Node p = head;while (p != null) {consumer.accept(p.value);p = p.next;}
}
//测试
@Test
@DisplayName("测试 consumer while遍历 loop4")
public void test5() {SinglyLinkedList list = new SinglyLinkedList();list.addFirst(1);list.addFirst(2);list.addFirst(3);list.addFirst(4);Consumer<Integer> consumer = integer -> System.out.println(integer);list.loop4(consumer);
}

5.迭代器遍历

public class SinglyLinkedList implements Iterable<Integer> {// ...private class NodeIterator implements Iterator<Integer> {Node curr = head;public boolean hasNext() {return curr != null;}public Integer next() {int value = curr.value;curr = curr.next;return value;}}public Iterator<Integer> iterator() {return new NodeIterator();}
}
  • hasNext 用来判断是否还有必要调用 next
  • next 做两件事
    • 返回当前节点的 value
    • 指向下一个节点
  • NodeIterator 要定义为非 static 内部类,是因为它与 SinglyLinkedList 实例相关,是对某个 SinglyLinkedList 实例的迭代

6.递归遍历

public class SinglyLinkedList implements Iterable<Integer> {// ...public void loop() {recursion(this.head);}private void recursion(Node curr) {if (curr == null) {return;}//前面做些事recursion(curr.next);//后面做些事}
}

7.尾部添加

public class SinglyLinkedList {// ...private Node findLast() {if (this.head == null) {return null;}Node curr;for (curr = this.head; curr.next != null; ) {curr = curr.next;}return curr;}public void addLast(int value) {Node last = findLast();if (last == null) {addFirst(value);return;}last.next = new Node(value, null);}
}
  • 注意,找最后一个节点,终止条件是 curr.next == null
  • 分成两个方法是为了代码清晰,而且 findLast() 之后还能复用

8.尾部添加多个

public class SinglyLinkedList {// ...public void addLast(int first, int... rest) {Node sublist = new Node(first, null);Node curr = sublist;for (int value : rest) {curr.next = new Node(value, null);curr = curr.next;}Node last = findLast();if (last == null) {this.head = sublist;return;}last.next = sublist;}
}
  • 先串成一串 sublist
  • 再作为一个整体添加

9.根据索引获取

public class SinglyLinkedList {// ...private Node findNode(int index) {int i = 0;for (Node curr = this.head; curr != null; curr = curr.next, i++) {if (index == i) {return curr;}}return null;}private IllegalArgumentException illegalIndex(int index) {return new IllegalArgumentException(String.format("index [%d] 不合法%n", index));}public int get(int index) {Node node = findNode(index);if (node != null) {return node.value;}throw illegalIndex(index);}
}
  • 同样,分方法可以实现复用

10.插入

public class SinglyLinkedList {// ...public void insert(int index, int value) {if (index == 0) {addFirst(value);return;}Node prev = findNode(index - 1); // 找到上一个节点if (prev == null) { // 找不到throw illegalIndex(index);}prev.next = new Node(value, prev.next);}
}
  • 插入包括下面的删除,都必须找到上一个节点

11.删除

public class SinglyLinkedList {// ...public void remove(int index) {if (index == 0) {if (this.head != null) {this.head = this.head.next;return;} else {throw illegalIndex(index);}}Node prev = findNode(index - 1);Node curr;if (prev != null && (curr = prev.next) != null) {prev.next = curr.next;} else {throw illegalIndex(index);}}
}
  • 第一个 if 块对应着 removeFirst 情况
  • 最后一个 if 块对应着至少得两个节点的情况
    • 不仅仅判断上一个节点非空,还要保证当前节点非空

三.单向链表(带哨兵)

1.带哨兵单向链表

观察之前单向链表的实现,发现每个方法内几乎都有判断是不是 head 这样的代码,能不能简化呢?

用一个不参与数据存储的特殊 Node 作为哨兵,它一般被称为哨兵或哑元,拥有哨兵节点的链表称为带头链表

public class SinglyLinkedListSentinel {// ...private Node head = new Node(Integer.MIN_VALUE, null);
}
  • 具体存什么值无所谓,因为不会用到它的值

2.获取索引节点

加入哨兵节点后,代码会变得比较简单,先看几个工具方法

public class SinglyLinkedListSentinel {// ...// 根据索引获取节点private Node findNode(int index) {int i = -1;for (Node curr = this.head; curr != null; curr = curr.next, i++) {if (i == index) {return curr;}}return null;}// 获取最后一个节点private Node findLast() {Node curr;for (curr = this.head; curr.next != null; ) {curr = curr.next;}return curr;}
}
  • findNode 与之前类似,只是 i 初始值设置为 -1 对应哨兵,实际传入的 index 也是 [-1, \infty)
  • findLast 绝不会返回 null 了,就算没有其它节点,也会返回哨兵作为最后一个节点

3.插入删除节点

这样,代码简化为

public class SinglyLinkedListSentinel {// ...public void addLast(int value) {Node last = findLast();/*改动前if (last == null) {this.head = new Node(value, null);return;}*/last.next = new Node(value, null);}public void insert(int index, int value) {/*改动前if (index == 0) {this.head = new Node(value, this.head);return;}*/// index 传入 0 时,返回的是哨兵Node prev = findNode(index - 1);if (prev != null) {prev.next = new Node(value, prev.next);} else {throw illegalIndex(index);}}public void remove(int index) {/*改动前if (index == 0) {if (this.head != null) {this.head = this.head.next;return;} else {throw illegalIndex(index);}}*/// index 传入 0 时,返回的是哨兵Node prev = findNode(index - 1);Node curr;if (prev != null && (curr = prev.next) != null) {prev.next = curr.next;} else {throw illegalIndex(index);}}public void addFirst(int value) {/*改动前this.head = new Node(value, this.head);*/this.head.next = new Node(value, this.head.next);// 也可以视为 insert 的特例, 即 insert(0, value);}
}
  • 对于删除,前面说了【最后一个 if 块对应着至少得两个节点的情况】,现在有了哨兵,就凑足了两个节点

四.双向链表(带哨兵)

public class DoublyLinkedListSentinel implements Iterable<Integer> {private final Node head;private final Node tail;public DoublyLinkedListSentinel() {head = new Node(null, 666, null);tail = new Node(null, 888, null);head.next = tail;tail.prev = head;}private Node findNode(int index) {int i = -1;for (Node p = head; p != tail; p = p.next, i++) {if (i == index) {return p;}}return null;}public void addFirst(int value) {insert(0, value);}public void removeFirst() {remove(0);}public void addLast(int value) {Node prev = tail.prev;Node added = new Node(prev, value, tail);prev.next = added;tail.prev = added;}public void removeLast() {Node removed = tail.prev;if (removed == head) {throw illegalIndex(0);}Node prev = removed.prev;prev.next = tail;tail.prev = prev;}public void insert(int index, int value) {Node prev = findNode(index - 1);if (prev == null) {throw illegalIndex(index);}Node next = prev.next;Node inserted = new Node(prev, value, next);prev.next = inserted;next.prev = inserted;}public void remove(int index) {Node prev = findNode(index - 1);if (prev == null) {throw illegalIndex(index);}Node removed = prev.next;if (removed == tail) {throw illegalIndex(index);}Node next = removed.next;prev.next = next;next.prev = prev;}private IllegalArgumentException illegalIndex(int index) {return new IllegalArgumentException(String.format("index [%d] 不合法%n", index));}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = head.next;@Overridepublic boolean hasNext() {return p != tail;}@Overridepublic Integer next() {int value = p.value;p = p.next;return value;}};}static class Node {Node prev;int value;Node next;public Node(Node prev, int value, Node next) {this.prev = prev;this.value = value;this.next = next;}}
}

五.环形链表(带哨兵)

双向环形链表带哨兵,这时哨兵既作为头,也作为尾

image-20221229144232651

image-20221229143756065

image-20221229153338425

image-20221229154248800

参考实现

public class DoublyLinkedListSentinel implements Iterable<Integer> {@Overridepublic Iterator<Integer> iterator() {return new Iterator<>() {Node p = sentinel.next;@Overridepublic boolean hasNext() {return p != sentinel;}@Overridepublic Integer next() {int value = p.value;p = p.next;return value;}};}static class Node {Node prev;int value;Node next;public Node(Node prev, int value, Node next) {this.prev = prev;this.value = value;this.next = next;}}private final Node sentinel = new Node(null, -1, null); // 哨兵public DoublyLinkedListSentinel() {sentinel.next = sentinel;sentinel.prev = sentinel;}/*** 添加到第一个* @param value 待添加值*/public void addFirst(int value) {Node next = sentinel.next;Node prev = sentinel;Node added = new Node(prev, value, next);prev.next = added;next.prev = added;}/*** 添加到最后一个* @param value 待添加值*/public void addLast(int value) {Node prev = sentinel.prev;Node next = sentinel;Node added = new Node(prev, value, next);prev.next = added;next.prev = added;}/*** 删除第一个*/public void removeFirst() {Node removed = sentinel.next;if (removed == sentinel) {throw new IllegalArgumentException("非法");}Node a = sentinel;Node b = removed.next;a.next = b;b.prev = a;}/*** 删除最后一个*/public void removeLast() {Node removed = sentinel.prev;if (removed == sentinel) {throw new IllegalArgumentException("非法");}Node a = removed.prev;Node b = sentinel;a.next = b;b.prev = a;}/*** 根据值删除节点* <p>假定 value 在链表中作为 key, 有唯一性</p>* @param value 待删除值*/public void removeByValue(int value) {Node removed = findNodeByValue(value);if (removed != null) {Node prev = removed.prev;Node next = removed.next;prev.next = next;next.prev = prev;}}private Node findNodeByValue(int value) {Node p = sentinel.next;while (p != sentinel) {if (p.value == value) {return p;}p = p.next;}return null;}}

六.解题模版

1.反转链表再正向遍历

可以先将链表反转,然后正向遍历即可实现从后往前遍历。这种方法的时间复杂度为 O(N),其中 N 为链表的长度。

CLASS LISTNODE:DEF __INIT__(SELF, VAL=0, NEXT=NONE):SELF.VAL = VALSELF.NEXT = NEXT
DEF REVERSELIST(HEAD):PREV, CURR = NONE, HEADWHILE CURR:NEXT_NODE = CURR.NEXTCURR.NEXT = PREVPREV = CURRCURR = NEXT_NODERETURN PREVDEF TRAVERSELIST(HEAD):NEW_HEAD = REVERSELIST(HEAD)WHILE NEW_HEAD:PRINT(NEW_HEAD.VAL)NEW_HEAD = NEW_HEAD.NEXT

2.递归遍历

可以使用递归的方式遍历链表,每次遍历到链表末尾时再开始输出节点的值。这种方法的时间复杂度也为 O(N),其中 N 为链表的长度。

CLASS LISTNODE:DEF __INIT__(SELF, VAL=0, NEXT=NONE):SELF.VAL = VALSELF.NEXT = NEXTDEF TRAVERSELIST(HEAD):IF NOT HEAD:RETURNTRAVERSELIST(HEAD.NEXT)PRINT(HEAD.VAL)

需要注意的是,递归遍历链表时,如果链表过长,可能会导致递归层数过深,从而出现栈溢出的情况。因此,如果链表长度较大,建议使用反转链表再正向遍历的方法。

七.链表题目

1.两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

image-20230702111117500

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

题解:

具体实现步骤如下:

  1. 首先,我们需要创建一个新的链表,用来存储两个链表相加的结果。同时,我们需要定义一个指针,用来遍历链表。
  2. 接着,我们可以从两个链表的头节点开始,依次遍历它们的每一个节点,将它们对应位置的数字相加,并记录进位。
  3. 如果其中一个链表已经遍历结束,我们就可以直接将另一个链表剩余的部分接到结果链表的后面(注意,这里还需要考虑进位)。
  4. 最后,如果遍历完成后还有进位,我们就需要在结果链表的末尾添加一个新节点,存储进位。
class Solution:def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:"""链表遍历:param l1::param l2::return:"""res = ListNode(0)cur = rescarry = 0while l1 or l2:x = l1.val if l1 else 0y = l2.val if l2 else 0s = x + y + carrycarry = s // 10cur.next = ListNode(s % 10)cur = cur.nextif l1:l1 = l1.nextif l2:l2 = l2.nextif carry:cur.next = ListNode(carry)return res.next

时间复杂度:O(max(m, n)),其中 m 和 n 分别是 l1 和 l2 的长度。

空间复杂度:O(max(m, n)),我们需要使用一个链表来存储结果。

2.两数相加 II

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例 1:

image-20230703212624541

输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]

示例 2:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[8,0,7]

示例 3:

输入:l1 = [0], l2 = [0]
输出:[0]

题解:

解题思路:

  1. 先把两个链表逆序,使得最低位在链表头部,这样就可以从头部开始相加了。
  2. 定义一个 carry 变量,表示进位,初始值为 0。
  3. 定义一个新的链表 dummyHead,表示相加的结果。
  4. 从链表 1 和链表 2 的头部开始遍历,对于每一位进行相加,同时加上进位 carry。
  5. 如果当前的和大于等于 10,就需要进位,将 carry 变为 1,同时将和减去 10。
  6. 将当前的和插入到新的链表 dummyHead 的末尾。
  7. 遍历完链表 1 和链表 2 后,如果进位 carry 为 1,还需要将 carry 插入到 dummyHead 的末尾。
  8. 最后将 dummyHead 逆序,得到正常的结果链表。
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = next# 翻转链表
def reverseList(head: ListNode) -> ListNode:pre = Nonecurr = headwhile curr:next = curr.nextcurr.next = prepre = currcurr = nextreturn preclass Solution:def addTwoNumbers(self, l11: Optional[ListNode], l22: Optional[ListNode]) -> Optional[ListNode]:l1 = reverseList(l11)l2 = reverseList(l22)res = ListNode(0)cur = rescarry = 0while l1 or l2:x = l1.val if l1 else 0y = l2.val if l2 else 0s = x + y + carrycarry = s // 10cur.next = ListNode(s % 10)cur = cur.nextif l1:l1 = l1.nextif l2:l2 = l2.nextif carry:cur.next = ListNode(carry)return reverseList(res.next)

3.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

image-20230708224612132

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

题解:

要合并两个有序链表,可以使用递归或迭代的方法。下面是使用迭代的方法实现合并两个有序链表的 Python 代码示例:

pythonCopy code
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextdef mergeTwoLists(l1, l2):# 创建一个哑节点(dummy node)作为合并后链表的头节点dummy = ListNode(0)# 创建一个指针指向合并后链表的当前节点current = dummy# 循环比较l1和l2的节点值,并将较小的节点添加到合并后链表中while l1 and l2:if l1.val < l2.val:current.next = l1l1 = l1.nextelse:current.next = l2l2 = l2.nextcurrent = current.next# 处理剩余的节点current.next = l1 if l1 else l2# 返回合并后链表的头节点的下一个节点,即去除哑节点return dummy.next

这段代码中,我们创建了一个哑节点(dummy node)作为合并后链表的头节点。然后使用一个指针current指向合并后链表的当前节点。在循环中,我们比较l1l2的节点值,并将较小的节点添加到合并后链表中,同时移动相应的指针。最后,我们处理剩余的节点,将其添加到合并后链表的尾部。最后返回合并后链表的头节点的下一个节点,即去除哑节点。

你可以调用mergeTwoLists函数,传入两个有序链表的头节点,然后它将返回合并后的有序链表的头节点。

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

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

示例 1:

image-20230708232123777

输入:head = [1,1,2]
输出:[1,2]

示例 2:

image-20230708232133319

输入:head = [1,1,2,3,3]
输出:[1,2,3]

题解:

class Solution:def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:dummy = headwhile dummy and dummy.next:# 如果当前节点和下一个节点的值相同,删除下一个节点if dummy.val == dummy.next.val:dummy.next = dummy.next.nextelse:dummy = dummy.nextreturn head

5.环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

image-20230709095007995

题解:

class Solution:def hasCycle(self, head: Optional[ListNode]) -> bool:"""双指针,一个快指针,一个慢指针,如果有环 一定会相聚:param head::return:"""if head is None or head.next is None:return Falseslow = headfast = head.nextwhile slow != fast:if fast is None or fast.next is None:return Falseslow = slow.nextfast = fast.next.nextreturn True

6.相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

图示两个链表在节点 c1 开始相交**:**

image-20230709232818902

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

  • intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
  • listA - 第一个链表
  • listB - 第二个链表
  • skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
  • skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headAheadB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案

示例 1:

image-20230709232901515

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。

题解:

class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:"""思路:如果存在交点,则肯定在两个链表分别遍历对方时相交:param headA::param headB::return:"""if not headA or not headB:return NonenodeA, nodeB = headA, headBwhile nodeA != nodeB:nodeA = nodeA.next if nodeA else headBnodeB = nodeB.next if nodeB else headAreturn nodeA

7.移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

image-20230710165139573

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

题解:

class Solution:def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:"""链表的合适是操作2个节点,前驱和后继:param head::param val::return:"""# 处理头部节点while head and head.val == val:head = head.next# 处理非头部cur = headwhile cur and cur.next:if cur.next.val == val:cur.next = cur.next.nextelse:cur = cur.nextreturn head

8.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

image-20230721163232649

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

image-20230721163241066

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

题解:

class Solution:def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:"""反转链表:定义前节点和当前节点:param head::return:"""pre, curr = None, headwhile curr is not None:next = curr.nextcurr.next = prepre = currcurr = nextreturn pre

9.回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

image-20230721165615618

输入:head = [1,2,2,1]
输出:true

题解:

class Solution:def isPalindrome(self, head: Optional[ListNode]) -> bool:"""回文链表:param head::return:"""contain = []curr = headwhile curr is not None:contain.append(curr.val)curr = curr.nextcurr2 = headwhile curr2 is not None:if contain.pop() != curr2.val:return Falsecurr2 = curr2.nextreturn True

10.链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

image-20230721172833959

输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。

题解:

class Solution:def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:"""链表的中间结点,如果有2个 返回第二个  快慢指针:param head::return:"""fast, slow = head, headwhile fast is not None and fast.next is not None:slow = slow.nextfast = fast.next.nextreturn slow

觉得有用的话点个赞 👍🏻 呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

img

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SwiftUI 动画进阶:实现行星绕圆周轨道运动

0. 概览 SwiftUI 动画对于优秀 App 可以说是布帛菽粟。利用美妙的动画我们不仅可以活跃界面元素,更可以单独打造出一整套生动有机的世界,激活无限可能。 如上图所示,我们用动画粗略实现了一个小太阳系:8大行星围绕太阳旋转,而卫星们围绕各个行星旋转。 在本篇博文中,您将…

CSDN编程题-每日一练(2023-08-17)

CSDN编程题-每日一练&#xff08;2023-08-17&#xff09; 一、题目名称&#xff1a;计算公式二、题目名称&#xff1a;计算逆波兰表达式的结果三、题目名称&#xff1a;争抢糖豆 一、题目名称&#xff1a;计算公式 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述…

Linux MQTT智能家居(温度,湿度,环境监测,摄像头等界面布局设置)

文章目录 前言一、温度湿度曲线布局二、环境监测界面布局三、摄像头界面布局总结 前言 本篇文章来完成另外三个界面的布局设置。 这里会使用到 feiyangqingyun的一些控件库。 一、温度湿度曲线布局 TempHumtiy.h: #ifndef TEMPHUMTIY_H #define TEMPHUMTIY_H#include <…

第十三课:QtCmd 命令行终端应用程序开发

功能描述&#xff1a;开发一个类似于 Windows 命令行提示符或 Linux 命令行终端的应用程序 一、最终演示效果 QtCmd 不是因为它是 Qt 的组件&#xff0c;而是采用 Qt 开发了一个类似 Windows 命令提示符或者 Linux 命令行终端的应用程序&#xff0c;故取名为 QtCmd。 上述演示…

船舶法兰盘法兰管件3D扫描尺寸测量|三维扫描检测|CAV测量-CASAIM

第一章 服务背景 船舶建造多采用分段建造法&#xff0c;即将零件、预装好的部件在胎架上组合焊接成分段或总段&#xff0c;然后由船台装配成整船的建造方法。而当船体合拢组装时&#xff0c;在船体上遍布着各种各样的管道&#xff0c;这些管道都需要互相完全适配以确保船体安装…

基于SSM的超市管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

分类预测 | MATLAB实现GAPSO-LSSVM多输入分类预测

分类预测 | MATLAB实现GAPSO-LSSVM多输入分类预测 目录 分类预测 | MATLAB实现GAPSO-LSSVM多输入分类预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.分类预测 | MATLAB实现GAPSO-LSSVM多输入分类预测 2.代码说明&#xff1a;要求于Matlab 2021版及以上版本。 程序…

插槽Slot的作用和基本使用;具名插槽的使用;作用域插槽Slot使用;全局事件总线使用;依赖注入Provide/Inject

目录 1_插槽Slot的作用1.1_认识插槽Slot1.2_如何使用插槽slot1.3_插槽的默认内容1.4_多个插槽的效果 2_插槽Slot基本使用3_具名插槽的使用4_作用域插槽Slot使用4.1_渲染作用域4.2_认识作用域插槽4.3_独占默认插槽的缩写 5_全局事件总线使用5.1_全局事件总线mitt库5.2_使用事件总…

C++QT教程3——手册4.11.1自带教程(笔记)——创建一个基于Qt Widget的应用程序

文章目录 创建一个基于Qt Widget的应用程序创建Text Finder项目素材文件 填补缺失的部分设计用户界面完成头文件完成源文件创建资源文件 编译和运行程序 参考文章 创建一个基于Qt Widget的应用程序 本教程介绍如何使用Qt Creator创建一个小型Qt应用程序&#xff0c;名为Text F…

JVM中判定对象是否回收的的方法

引用计数法 引用计数法是一种垃圾回收&#xff08;Garbage Collection&#xff09;算法&#xff0c;用于自动管理内存中的对象。在引用计数法中&#xff0c;每个对象都有一个关联的引用计数器&#xff0c;用于记录对该对象的引用数量。 当一个新的引用指向对象时&#xff0c;…

网工内推 | 外企网工,带专业培训,NP认证优先

01 广州开讯通信技术有限公司 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;职责描述&#xff1a; 1、负责组织制定系统集成项目的技术方案编写、标书的准备、讲解及用户答疑等工作; 2、配合客户经理完成与用户的技术交流、技术方案宣讲、系统演示等工作; 3、配…

关于Neo4j的使用及其基本命令

关于Neo4j的使用 文章目录 关于Neo4j的使用1、启动方式2、创建新节点&#xff0c;节点内有属性3、创建关系4、查询节点5、查询关系6、删除两个节点的关系7、删除节点8、删除某个标签的全部关系9、某个节点添加属性10、删除节点某个属性 1、启动方式 进入bin目录&#xff1a; …

进程|详解~什么是进程 以及 进程创建原理和过程

1.什么是进程 进程是正在运行的程序。 UNIX标准将进程定义为&#xff1a;其中运行着一个或者多个线程的地址空间和这些线程所需要的系统资源(分配给线程线程共享系统资源)。 组成&#xff1a;进程由程序代码、数据、变量(占用着系统内存)、打开的文件(文件描述符)、环境组成…

Java Persistence APl(JPA)——JPA是啥? SpringBoot整合JPA JPA的增删改查 条件模糊查询 多对一查询

目录 引出Jpa是啥&#xff1f;Jpa的使用创建实体类写dao接口类写服务类 crud增删改查增加修改根据id删除全查询分页查询 条件查询模糊查询单条件查询多条件查询模糊查询排序查询 多对一查询定义实体类auto主键策略下新增进行全查询测试 全部代码application.yml配置类pom配置文…

易服客工作室:UberMenu WordPress插件 - 网站超级菜单插件

UberMenu WordPress插件是一个用户友好、高度可定制、响应迅速的 Mega Menu WordPress 插件。它与 WordPress 3 菜单系统一起开箱即用&#xff0c;易于上手&#xff0c;但功能强大&#xff0c;足以创建高度定制化和创意的大型菜单配置。 网址: UberMenu WordPress插件 - 网站超…

【hive】hive分桶表的学习

hive分桶表的学习 前言&#xff1a; 每一个表或者分区&#xff0c;hive都可以进一步组织成桶&#xff0c;桶是更细粒度的数据划分&#xff0c;他本质不会改变表或分区的目录组织方式&#xff0c;他会改变数据在文件中的分布方式。 分桶规则&#xff1a; 对分桶字段值进行哈…

Linux apt 命令

Linux apt 命令 apt&#xff08;Advanced Packaging Tool&#xff09;是一个在 Debian 和 Ubuntu 中的 Shell 前端软件包管理器。 apt 命令提供了查找、安装、升级、删除某一个、一组甚至全部软件包的命令&#xff0c;而且命令简洁而又好记。 apt 命令执行需要超级管理员权限…

爬虫逆向实战(十六)--某建筑市场平台

一、数据接口分析 主页地址&#xff1a;某建筑市场平台 1、抓包 通过抓包可以发现数据接口是list 2、判断是否有加密参数 请求参数是否加密&#xff1f; 无请求头是否加密&#xff1f; 无响应是否加密&#xff1f; 通过查看“响应”模块可以发现&#xff0c;返回的响应是…

Docker容器与虚拟化技术:Docker容器操作、网络模式

目录 一、理论 1.Docker 容器操作 2.Docker 网络 二、实验 1.Docker 容器操作 2.Docker 网络 2.Docker 的网络模式 三、问题 1. 批量删除所有容器未生效 2.使用bridge模式指定IP运行docker报错 3.未显示bridge网络模式名称 四、总结 一、理论 1.Docker 容器操作 &a…

Codeforces Round 893 (Div. 2) D.Trees and Segments

原题链接&#xff1a;Problem - D - Codeforces 题面&#xff1a; 大概意思就是让你在翻转01串不超过k次的情况下&#xff0c;使得a*&#xff08;0的最大连续长度&#xff09;&#xff08;1的最大连续长度&#xff09;最大&#xff08;1<a<n&#xff09;。输出n个数&…