Python蓝桥杯训练:基本数据结构 [链表]
Python蓝桥杯训练:基本数据结构 [链表]
文章目录
- Python蓝桥杯训练:基本数据结构 [链表]
- 一、链表理论基础知识
- 二、有关链表的一些常见操作
- 三、力扣上面一些有关链表的题目练习
- 1、[移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/)
- 2、[设计链表](https://leetcode.cn/problems/design-linked-list/)
- 3、[反转链表](https://leetcode.cn/problems/reverse-linked-list/)
- 4、[两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)
- 5、[删除链表的倒数第N个节点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
- 6、[相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/)
- 7、[环形链表Ⅱ](https://leetcode.cn/problems/linked-list-cycle-ii/)
本次博客我是通过Notion软件写的,转md文件可能不太美观,大家可以去我的博客中查看:北天的 BLOG,持续更新中,另外这是我创建的编程学习小组频道,想一起学习的朋友可以一起!!!
一、链表理论基础知识
链表是一种特殊的数据结构,它是由节点(node)组成的。每个节点都存储了一个数据元素和一个指向下一个节点的指针。指向最后一个节点的指针是空的。因此,我们可以从第一个节点开始,通过沿着指针移动到下一个节点,直到到达最后一个节点为止。
链表有两种类型:单向链表和双向链表。在单向链表中,每个节点只指向下一个节点,而在双向链表中,每个节点都指向下一个节点和前一个节点。
链表具有动态内存分配,因此我们可以在不需要预先知道链表大小的情况下在链表中插入或删除元素。
在 Python 中,我们可以使用类来实现链表。每个节点可以是一个类的实例,其中包含数据元素和指向下一个节点的指针。
操作链表的常见任务包括插入节点、删除节点、查找节点、遍历链表等。
常用的链表操作有单链表、双向链表、循环链表、双向循环链表等。
下面我们来简单的介绍一下单链表、双向链表、循环链表、双向循环链表:
- 单链表:在单链表中,每个节点只包含一个指向下一个节点的指针。指向最后一个节点的指针是空的。
- 双向链表:在双向链表中,每个节点都包含一个指向下一个节点的指针和一个指向前一个节点的指针。
- 循环链表:在循环链表中,最后一个节点的指针不是空的,而是指向第一个节点。因此,我们可以从任何节点开始,通过沿着指针移动到下一个节点,直到回到第一个节点为止。
- 双向循环链表:双向循环链表是循环链表的双向版本,即每个节点都包含一个指向下一个节点的指针和一个指向前一个节点的指针,并且最后一个节点的指针指向第一个节点,第一个节点的指针指向最后一个节点。
二、有关链表的一些常见操作
-
插入:在链表的任意位置插入新节点
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 在指定位置插入一个新节点def insert_at_pos(self, pos, data):# 创建一个节点new_node = Node(data)# 如果链表为空if self.head is None:self.head = new_nodereturn# 如果位置为0,则在开头插入if pos == 0:new_node.next = self.headself.head = new_nodereturn# 遍历链表找到位置temp = self.headcount = 0while temp is not None:if count == pos - 1:breaktemp = temp.nextcount += 1# 插入节点new_node.next = temp.nexttemp.next = new_node
-
删除:从链表中删除指定的节点
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 删除指定数据的节点def delete_node(self, data):# 如果链表为空if self.head is None:return# 如果头节点有数据if self.head.data == data:self.head = self.head.nextreturn# 遍历链表temp = self.headwhile temp.next is not None:if temp.next.data == data:breaktemp = temp.next# 删除节点if temp.next is not None:temp.next = temp.next.next
-
查询:查询链表中指定元素的位置
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 找到指定数据的位置def find_pos(self, data):# 如果链表为空if self.head is None:return -1# 遍历链表temp = self.headcount = 0while temp is not None:if temp.data == data:return counttemp = temp.nextcount += 1# 元素未找到return -1
-
修改:修改链表中指定元素的值
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 更新指定位置的节点def update_at_pos(self, pos, data):# 如果链表为空if self.head is None:return#遍历链表找到位置temp = self.headcount = 0while temp is not None:if count == pos:breaktemp = temp.nextcount += 1#更新节点if temp is not None:temp.data = data
-
遍历:从头到尾遍历整个链表
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 遍历链表def traverse(self):temp = self.headwhile temp is not None:print(temp.data)temp = temp.next
-
计数:计算链表中节点的数量
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 统计链表中节点的个数def count_nodes(self):temp = self.headcount = 0while temp is not None:count += 1temp = temp.nextreturn count
-
排序:对链表中的元素进行排序
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 对链表进行排序def sort_linked_list(self):temp = self.headwhile temp is not None:current = tempwhile current.next is not None:if current.data > current.next.data:current.data, current.next.data = current.next.data, current.datacurrent = current.nexttemp = temp.next
-
反转:将链表中的元素反转
# 节点类 class Node:def __init__(self, data):self.data = dataself.next = None# 链表类 class LinkedList:def __init__(self):self.head = None# 反转链表def reverse(self):prev = Nonecurrent = self.headwhile current is not None:next = current.nextcurrent.next = prevprev = currentcurrent = nextself.head = prev
三、力扣上面一些有关链表的题目练习
1、移除链表元素
示例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
输出:[]
提示:
列表中的节点数目在范围 [0, 104] 内
1 <= Node.val <= 50
0 <= val <= 50
那么什么是虚拟节点呢?
虚拟节点(dummy node)是在链表头部创建一个不存储任何数据的特殊节点,用来解决链表操作问题。通常情况下,在链表头部插入或移除元素时,需要特殊处理,以避免对链表头部的直接操作。使用虚拟节点,可以在链表头部插入和移除元素时统一处理,从而简化代码。
# Definition for singly-linked list.
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def removeElements(self, head: ListNode, val: int) -> ListNode:dummy = ListNode(0)dummy.next = headprev = dummycurrent = headwhile current:if current.val == val:prev.next = current.nextelse:prev = currentcurrent = current.nextreturn dummy.next
上述代码的具体实现思路如下:
- 创建一个虚拟节点,该节点的值不重要,只是用来作为链表的头部。
- 创建两个指针,一个指向虚拟节点(prev),一个指向链表头(current)。
- 在链表中遍历每一个节点,如果该节点的值等于给定值val,则将prev的next指向该节点的下一个节点,即跳过该节点。如果该节点的值不等于给定值val,则将prev指向该节点。
- 遍历完整个链表后,返回虚拟节点的next。
- 链表的遍历顺序:确保指针正确的遍历链表的每一个节点。
- 变量的更新:确保在遍历每一个节点时,更新prev和current的值。
- 虚拟节点的初始化:创建一个虚拟节点并且初始化它的next值为链表头。
- 返回值:确保返回虚拟节点的next,而不是链表头。
如何要对上述代码进行示例测试的话,需要注意这跟之前的数组不一样,具体例子如下:
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(6)
head.next.next.next = ListNode(3)
head.next.next.next.next = ListNode(4)
head.next.next.next.next.next = ListNode(5)
head.next.next.next.next.next.next = ListNode(6)
a = Solution()
result = a.removeElements(head, 6)
node = result
while node:print(node.val, end=" ")node = node.next
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV18B4y1s7R9/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
2、设计链表
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点
示例:
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3
提示:
0 <= index, val <= 1000
请不要使用内置的 LinkedList 库。
get, addAtHead, addAtTail, addAtIndex 和 deleteAtIndex 的操作次数不超过 2000。
其中get(index)实现思路是:
- 先判断给定的索引index是否合法,即是否小于0或大于链表的长度,如果不合法返回-1。
- 定义一个当前节点cur,并将其设为头节点的下一个节点(即链表的第一个节点)。
- 进入循环,当index不为0时,cur更新为其下一个节点,index减1。
- 当循环结束后,cur的val属性即为所需要的值,返回该值。
其中addAtHead(val)实现思路是:
- 创建新节点并设置其值。
- 将新节点的next指针指向当前的链表头部。
- 将新节点设为链表头部。
- 更新链表的大小。
其中addAtTail(val)实现思路是:
- 创建新节点并设置其值。
- 创建当前指针,从链表头开始循环,直到找到链表的末尾。
- 将新节点添加到末尾。
- 更新链表的大小。
其中addAtIndex实现思路是:
- 检查给定索引是否小于0或大于链表的大小,如果是,返回-1。
- 创建新节点并设置其值。
- 创建当前指针,从链表头开始循环,直到找到给定索引处的节点的前一个节点。
- 将新节点的next指针指向给定索引处的节点。
- 将前一个节点的next指针指向新节点。
- 更新链表的大小。
其中deleteAtIndex(index)实现思路是:
- 检查给定索引是否小于0或大于等于链表的大小,如果是,则返回。
- 否则,创建一个当前指针,并从链表头开始循环,直到找到给定索引前一个节点。
- 删除该节点并更新链表的大小。
class Node(object):def __init__(self, x=0):self.val = xself.next = Noneclass MyLinkedList(object):def __init__(self):self.head = Node()self.size = 0 # 设置一个链表长度的属性,便于后续操作,注意每次增和删的时候都要更新def get(self, index):""":type index: int:rtype: int"""if index < 0 or index >= self.size:return -1cur = self.head.nextwhile(index):cur = cur.nextindex -= 1return cur.valdef addAtHead(self, val):""":type val: int:rtype: None"""new_node = Node(val)new_node.next = self.head.nextself.head.next = new_nodeself.size += 1def addAtTail(self, val):""":type val: int:rtype: None"""new_node = Node(val)cur = self.headwhile(cur.next):cur = cur.nextcur.next = new_nodeself.size += 1def addAtIndex(self, index, val):""":type index: int:type val: int:rtype: None"""if index < 0:self.addAtHead(val)returnelif index == self.size:self.addAtTail(val)returnelif index > self.size:returnnode = Node(val)pre = self.headwhile(index):pre = pre.nextindex -= 1node.next = pre.nextpre.next = nodeself.size += 1def deleteAtIndex(self, index):""":type index: int:rtype: None"""if index < 0 or index >= self.size:returnpre = self.headwhile(index):pre = pre.nextindex -= 1pre.next = pre.next.nextself.size -= 1
# 双链表
# 相对于单链表, Node新增了prev属性
class Node:def __init__(self, val):# 存储节点的值self.val = val# 指向前一个节点self.prev = None# 指向下一个节点self.next = None# 双链表的实现
class MyLinkedList:def __init__(self):# 虚拟节点,存储的值可以是0或者任意值self._head, self._tail = Node(0), Node(0) # 虚拟节点的前一个节点和后一个节点都是自己self._head.next, self._tail.prev = self._tail, self._head# 添加的节点数self._count = 0 def _get_node(self, index: int) -> Node:# 当index小于_count//2时, 使用_head查找更快, 反之_tail更快if index >= self._count // 2:# 使用prev往前找node = self._tailfor _ in range(self._count - index):node = node.prevelse:# 使用next往后找node = self._head for _ in range(index + 1):node = node.next# 返回找到的节点return nodedef get(self, index: int) -> int:"""获取链表中第 index 个节点的值。如果索引无效,则返回 -1。"""# 如果index是合法的,即在0和_count-1的范围内if 0 <= index < self._count:# 找到对应的节点node = self._get_node(index)# 返回该节点的值return node.valelse:# 如果index不合法,返回-1return -1def addAtHead(self, val: int) -> None:"""在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。"""# 调用_update方法,并传入链表的头节点,头节点的下一个节点,以及要插入的值valself._update(self._head, self._head.next, val)def addAtTail(self, val: int) -> None:"""将值为 val 的节点附加到链表的最后一个元素。"""# 调用_update方法,并传入链表的尾节点的前一个节点,尾节点,以及要插入的值valself._update(self._tail.prev, self._tail, val)def addAtIndex(self, index: int, val: int) -> None:"""在链表的第 index 个节点之前添加一个值为 val 的节点。如果索引等于链表的长度,则该节点将追加到链表的末尾。如果索引大于长度,则不会插入该节点。"""# 如果索引小于0,则将索引设置为0;如果索引大于链表长度,则直接返回if index < 0:index = 0elif index > self._count:return# 获取索引对应的节点node = self._get_node(index)# 调用_update方法,并传入该节点的前一个节点,该节点,以及要插入的值valself._update(node.prev, node, val)def _update(self, prev: Node, next: Node, val: int) -> None:"""更新节点:param prev: 相对于更新的前一个节点:param next: 相对于更新的后一个节点:param val: 要添加的节点值"""# 计数累加self._count += 1# 创建一个新节点node = Node(val)# 设置前后节点的指向prev.next, next.prev = node, nodenode.prev, node.next = prev, nextdef deleteAtIndex(self, index: int) -> None:"""如果索引有效,则删除链表中的第 index 个节点。"""# 如果 index 有效if 0 <= index < self._count:# 获取 index 节点node = self._get_node(index)# 计数-1self._count -= 1# 将前后节点的指向跳过该节点node.prev.next, node.next.prev = node.next, node.prev
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1FU4y1X7WD/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
3、反转链表
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
- 使用递归的方法,从链表的最后一个节点开始递归,每次递归都把当前节点的 next 指向前一个节点,最后返回新的头节点。
- 使用循环的方法,通过循环,每次取出当前节点,并把当前节点的 next 指向前一个节点,最后把头节点指向最后一个节点。
递归的代码实现会非常的简洁,但是有时候会很难理解每一步的含义,所以我们可以先尝试使用第二种循环的方法,也就是使用双指针的方法反转链表。
- 创建两个指针 pre 和 cur,pre 指向当前节点的前一个节点,cur 指向当前节点。
- 使用 cur.next 来移动 cur,同时使用 pre 和 cur 实现反转。
- 在每一轮循环中,cur 移动到下一个节点,pre 指向当前节点,cur.next 指向 pre。
- 循环结束后,链表的头和尾已经反转。
- 返回新的链表头,即原来的链表尾。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution:def reverseList(self, head: ListNode) -> ListNode:prev = Nonecurr = headwhile curr:next_node = curr.nextcurr.next = prevprev = currcurr = next_nodereturn prev
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution:def reverseList(self, head: ListNode) -> ListNode:# 终止条件:当前节点为空或当前节点的下一个节点为空if not head or not head.next:return head# 先递归到最后一个节点,让最后一个节点的下一个节点指向前一个节点new_head = self.reverseList(head.next)head.next.next = headhead.next = None# 返回新的头节点return new_head
我们还可以在原函数reverseList里面自己定义一个反转函数实现反转操作,具体实现过程如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:def reverseList(self, head: ListNode) -> ListNode:"""反转单链表:param head: 链表的头节点:return: 反轮后的链表头节点"""def reverse(pre, cur):"""递归函数,每次传入前一个节点和当前节点,反转链表:param pre: 前一个节点:param cur: 当前节点:return: 反转后的链表头节点"""if not cur:# 当前节点为空,说明已经到达末尾,返回前一个节点return pre# 保存当前节点的下一个节点tmp = cur.next# 反转链表,当前节点的下一个节点指向前一个节点cur.next = pre# 递归下一个节点return reverse(cur, tmp)# 从head开始,pre为Nonereturn reverse(None, head)
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1nB4y1i7eL/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
4、两两交换链表中的节点
示例1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例2:
输入:head = []
输出:[]
示例3:
输入:head = [1]
输出:[1]
提示:
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100
- 递归实现:通过递归的方式对链表进行交换,并在递归过程中更新节点的指向。
- 迭代实现:通过迭代的方式对链表进行交换,通过不断更新节点的指向实现交换。
和上面的反转链表题目操作类似,我们还是先使用双指针迭代实现,然后再用递归的方法实现。
- 定义一个虚拟节点dummy,其next指向head。
- 定义一个指针pre指向dummy,表示要交换的前一个节点。
- 循环条件为pre的next指向的节点不为空且该节点的下一个节点也不为空。
- 定义指针cur指向pre的next指向的节点,定义指针post指向cur的next指向的节点。
- 交换cur和post两个节点:cur的next指向post的next指向的节点,post的next指向cur。
- 将pre的next指向post。
- 将pre指向pre的next指向的节点的next指向的节点。
- 返回dummy的next指向的节点。
具体双指针代码实现如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:def swapPairs(self, head: ListNode) -> ListNode:# 定义虚拟节点dummydummy = ListNode(0)dummy.next = headpre = dummywhile pre.next and pre.next.next:cur = pre.nextpost = cur.nextcur.next = post.nextpost.next = curpre.next = postpre = pre.next.nextreturn dummy.next
使用虚拟节点递归实现的思路:
- 定义虚拟节点dummy,并将其next指向头节点。
- 如果链表不为空,则从头节点开始递归,每次递归都会交换当前节点与其后面的节点。
- 对于每次递归,通过将当前节点的next指向其后面的节点的next指针,并递归当前节点的next指针,实现链表节点的交换。
- 递归边界为当前节点的next指针为None或只有一个节点时。
- 完成递归后,返回头节点即可。
具体递归代码实现如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:def swapPairs(self, head: ListNode) -> ListNode:# 定义虚拟节点dummydummy = ListNode(0)dummy.next = headif head and head.next:next_node = head.next.nexthead.next.next = headhead.next = self.swapPairs(next_node)dummy.next = head.nextreturn dummy.next
如果使用动态状态方程写的话可以更加简洁:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:def swapPairs(self, head: ListNode) -> ListNode:# 定义虚拟节点dummydummy = ListNode(0)dummy.next = headif head and head.next:head.next, dummy.next.next, dummy.next = self.swapPairs(head.next.next), head, head.nextreturn dummy.next
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1YT411g7br/?spm_id_from=333.788&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
5、删除链表的倒数第N个节点
示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例2:
输入:head = [1], n = 1
输出:[]
示例3:
输入:head = [1,2], n = 1
输出:[1]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
- 定义虚拟节点dummy,把dummy的next指向head,并定义快指针fast和慢指针slow,都指向dummy。
- 先将快指针移动n+1步,使其到达链表的结尾或倒数第n个位置。
- 然后同时移动快指针和慢指针,直到快指针到达链表的末尾。
- 最后把慢指针的下一个节点删除。
- 返回dummy的next,作为新的链表的头结点。
具体代码实现:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:dummy = ListNode(0)dummy.next = headslow = dummyfast = dummyfor i in range(n + 1):fast = fast.nextwhile fast:slow = slow.nextfast = fast.nextslow.next = slow.next.nextreturn dummy.next
我们还可以换一种写法来实现:
首先,我们新增一个虚拟节点 dummy,它的下一个节点是原链表的头节点 head。然后,我们定义两个指针 slow 和 fast,它们都从 dummy 出发,最开始 fast 指针先往前走 n 步。接着,两个指针同时向前走,当 fast 指针到达结尾后,slow 的下一个节点就是倒数第 N 个节点。最后,我们直接删除该节点,然后返回 dummy.next 即可。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:dummy = ListNode()dummy.next = headslow, fast = dummy, dummywhile(n!=0): #fast先往前走n步fast = fast.nextn -= 1while(fast.next!=None):slow = slow.nextfast = fast.next#fast 走到结尾后,slow的下一个节点为倒数第N个节点slow.next = slow.next.next #删除return dummy.next
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1vW4y1U7Gf/?spm_id_from=333.788
6、相交链表
图示两个链表在节点 c1 开始相交:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzDLGQGH-1676269812178)(https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_statement.png)]
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例1:
输入: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 中第四个节点) 在内存中指向相同的位置。
示例2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
listA 中节点数目为 m
listB 中节点数目为 n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]
进阶:你能否设计一个时间复杂度 O(m + n)
、仅用 O(1)
内存的解决方案?
-
创建两个指针pA和pB,分别指向两个链表的头结点headA和headB。
-
先判断两个链表是否存在交点:
a. 如果pA == pB,则返回pA,说明此时两个指针所指向的结点就是交点。
b. 如果pA != pB,则继续遍历:
i. 若pA存在,则将pA移动到pA.next,否则将pA指向headB;
ii. 若pB存在,则将pB移动到pB.next,否则将pB指向headA。
-
循环2的步骤,直到pA == pB或pA和pB均不存在,此时说明两个链表没有交点或已经找到了交点。
知道了具体实现思路,那么写出代码也就不是很难了,具体代码实现是:
# Definition for singly-linked list.
class ListNode:def __init__(self, x):self.val = xself.next = Noneclass Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:pA = headApB = headBwhile pA != pB:pA = pA.next if pA else headBpB = pB.next if pB else headAreturn pA
我们使用的这种方法的优点在于,不论两个链表的长度是否相等,最多只需要遍历两次链表,时间复杂度为O(m + n),其中m和n分别是两个链表的长度。
7、环形链表Ⅱ
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引
进阶:你是否可以使用 O(1)
空间解决此题?
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution:def detectCycle(self, head: ListNode) -> ListNode:fast = slow = headwhile fast and fast.next:fast = fast.next.nextslow = slow.nextif fast == slow:slow = headwhile slow != fast:slow = slow.nextfast = fast.nextreturn slowreturn None
上述算法的实现步骤为:
- 创建两个指针fast和slow,分别指向head。
- 每次将fast移动到fast.next.next,将slow移动到slow.next。
- 如果fast == slow,说明链表存在环。此时,将slow指向head,同时保持fast不变,再次每次将slow移动到slow.next,将fast移动到fast.next。如果在某一时刻slow == fast,则说明slow和fast所指向的结点即为环的入口。
- 如果fast不存在或fast.next不存在,则说明链表不存在环,返回None。
B站上面卡尔老师对于这道题的思路讲得很不错,在这里推荐大家去看看他的教学视频:
https://www.bilibili.com/video/BV1if4y1d7ob/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=eae0406d86828f39f6ac3a3b0e8a2a34
相关文章:
Python蓝桥杯训练:基本数据结构 [链表]
Python蓝桥杯训练:基本数据结构 [链表] 文章目录Python蓝桥杯训练:基本数据结构 [链表]一、链表理论基础知识二、有关链表的一些常见操作三、力扣上面一些有关链表的题目练习1、[移除链表元素](https://leetcode.cn/problems/remove-linked-list-element…...
华为OD机试 - 找字符(Python)| 真题+思路+代码
找字符 题目 给定两个字符串, 从字符串2中找出字符串1中的所有字符, 去重并按照 ASCII 码值从小到大排列。 输入 字符范围满足 ASCII 编码要求, 输入字符串1长度不超过1024, 字符串2长度不超过100。 输出描述 按照 ASCII 由小到大排序 示例一 输入 bach bbaaccddf…...
使用继承与派生的6大要点
概述 面向对象编程技术非常看重软件的可重用性,在C中,可重用性是通过继承机制来实现的。继承机制允许程序员在保持原有类的数据和功能的基础上进行扩展,增加新的数据和功能,从而构成一个新的类,也称为派生类。原有类&a…...
加一-力扣66-java高效方案
一、题目描述给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。示例 1:输入:di…...
记一次 .NET 某游戏网站 CPU爆高分析
一:背景 1. 讲故事 这段时间经常有朋友微信上问我这个真实案例分析连载怎么不往下续了,关注我的朋友应该知道,我近二个月在研究 SQLSERVER,也写了十多篇文章,为什么要研究这东西呢? 是因为在 dump 中发现…...
集群使用——资源管理和租户创建
概述 OceanBase 数据库是多租户的分布式数据库,租户使用的资源建立在资源池上。资源池包含了资源单元,而资源单元则规定了具体资源的量化(如 CPU、Memory、Disk_Size 和 IOPS 等)。 创建租户前,必须规定租户使用的资源…...
谷歌浏览器登录失败,提示【无法同步到“...@gmail.com”】
首先安装Chrome同步助手(Chrome-Sync-Helper,看了很多博客,谷歌浏览器同步问题好像都要用这个),改成.rar,解压,文件夹_metadata重命名为metadata,然后添加到谷歌浏览器的扩展程序中。…...
75 111111
选择题(共75题,合计75.0分) 1. 选项ABCD中显示了所创造的商业价值以及在产品中实施各种功能需要进行的开发工作。团队应优先实施哪项功能? The business value created and the development effort needed to implement the various features in the product are sh…...
分销系统逻辑
相关概念 主营商户: 提供分销商品和佣金的商户分销商: 拥有自己的销售渠道,能够帮助推动产品销售的个人或商户消费者: 购买分销商品的人。佣金: 主营商户返还给经销商的比例抽成 分销功能设计 (1)分销商准入规则设计 无规则: 没有分销商的准入门槛限制…...
MySQL视图特性
文章目录MySQL视图特性基本使用准备测试表创建视图修改视图影响基表修改基表影响视图删除视图视图规则和限制MySQL视图特性 视图的概念 视图是一个虚拟表,其内容由查询定义,同真实的表一样,视图包含一系列带有名称的列和行数据。视图中的数据…...
RabbitMQ详解(二):Docker安装RabbitMQ
在Docker上安装部署RabbitMQ方便快捷,不需要额外安装Erlang环境,所以写该篇文章先来介绍如何在Docker上部署RabbitMQ。 一、安装并运行 (1)、在docker hub 中查找rabbitmq镜像 docker search rabbitmq:3.9.12-management带有“mangement”的版本&…...
如何使用代码注释:关于JavaScript与TypeScript 注释和文档的自动生成
如何使用代码注释:关于JavaScript与TypeScript 注释和文档的自动生成jcLee95:https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/detail…...
Echarts 设置面积区域图(areaStyle核心)
第011个点击查看专栏目录Echarts折线区域面积图的视觉效果更加饱满丰富,在系列不多的场景下尤其适用。区域面积图将折线到坐标轴的空间设置背景色,用区域面积表达数据。通过 areaStyle 设置折线图的填充区域样式,将其设为为 {} 表示使用默认…...
pandas——字符串处理【建议收藏】
pandas——字符串处理 作者:AOAIYI 创作不易,如果觉得文章不错或能帮助到你学习,记得点赞收藏评论一下哦 文章目录pandas——字符串处理一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤1.cat() 拼接字符串2.split()切片字符串…...
反射,枚举,lambda表达式
目录 1、反射 1.1 基本概念 1.2 反射相关的类 1.3 创建 Class 对象 1.4 反射的使用 1.4.1 通过反射创建对象: 1.4.2 获取私有的构造方法 1.4.3 获取私有的成员变量 1.4.4 获取私有的方法 1.5 总结 2、枚举 2.1 认识枚举 2.2 使用枚举 2.3 枚举与反射…...
.Net Core对于RabbitMQ封装分布式事件总线
首先我们需要了解到分布式事件总线是什么; 分布式事件总线是一种在分布式系统中提供事件通知、订阅和发布机制的技术。它允许多个组件或微服务之间的协作和通信,而无需直接耦合或了解彼此的实现细节。通过事件总线,组件或微服务可以通过发布…...
GPIO功能描述
GPIO 文章目录 GPIO1. 功能描述1.1 OSCI/OSCO 引脚1.3 HSEIN/HSEOUT引脚1.2 Bit-Band1.4 VRTCAFx引脚1.5 EWKUPx引脚1.6 QSPI0 引脚1.7 LVDIN引脚1.8 SARADC引脚1.9 ADCIN引脚2. 测试项描述2.1 PAD Location2.2 LBOR和BOR复位2.3 驱动能力2.4 模拟态\高阻态2.5 SWD\JTAG2.6 输出…...
指派问题与匈牙利法讲解
指派问题概述:实际中,会遇到这样的问题,有n项不同的任务,需要n个人分别完成其中的1项,每个人完成任务的时间不一样。于是就有一个问题,如何分配任务使得花费时间最少。通俗来讲,就是n*n矩阵中&a…...
day5——冒泡排序,选择排序和插入排序的学习
选择排序冒泡排序插入排序 选择排序 选择排序的基本思路就是: 首先假定第一个的下表为所有元素中最小的一个, 然后用后面的每一个元素跟这个元素进行比较, 如果后面的元素比这个元素更小一点, 那么就将找到的最小的元素的下标和…...
Windows 数据类型 (Windows Data Types)
参考:https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types 要求 要求值最低受支持的客户端Windows XP [仅限桌面应用]最低受支持的服务器Windows Server 2003 [仅限桌面应用]HeaderBaseTsd.h;WinDef.h;WinNT.hAPIENTRY 系统函数的调用约…...
九龙证券|本周5只新股申购,特斯拉、蔚来、理想的供应商来A股了!
据现在组织,2月13日到17日共有5只新股申购,其间上证主板2只,深证主板1只,北交所2只。 2月14日发动打新的深证主板新股多利科技成立于2010年,是一家专心于轿车冲压零部件及相关模具的开发、出产与出售的企业。从2020年…...
设计模式(持续更新)
本文主要是记录java的设计模式在实际工作中的应用案例,或者是对设计模式的个人理解及备忘 一、单例模式Singleton 工作场景(静态类): 在外部系统对接中,需要调用外部系统A的接口,但是接口是有身份校验的…...
Prometheus 告警规则
Prometheus 告警规则 Prometheus官方内置的第三方报警通知包括:邮件、 即时通讯软件(如Slack、Hipchat)、移动应用消息推送(如Pushover)和自动化运维工具(例如:Pagerduty、Opsgenie、Victorops) Promethe…...
mulesoft MCIA 破釜沉舟备考 2023.02.13.02
mulesoft MCIA 破釜沉舟备考 2023.02.13.03 1. According to MuleSoft, which deployment charcateristic applies to a microservices application architecture?2. A mule application designed to fulfil two requirements3. A mule application must periodically process…...
获取DLL运行时路径的方法
之前项目中发现的问题,记录下解决方案1. 问题背景OVVRNTool项目中,底层图像基本操作功能由DLL库函数提供,上层基于DLL封装了两个应用CMD和GUI,然后通过Qt打包分发;发布是直接采用绿色免安装的方式打包,具体…...
“华为杯”研究生数学建模竞赛2006年-【华为杯】D题:学生面试中教师安排的优化与算法(附获奖论文)
赛题描述 高校自主招生是高考改革中的一项新生事物,现在仍处于探索阶段。某高校拟在全面衡量考生的高中学习成绩及综合表现后再采用专家面试的方式决定录取与否。该校在今年自主招生中,经过初选合格进入面试的考生有N人,拟聘请老师M人。每位学生要分别接受4位老师(简称该学…...
【JavaScript】复习 【对象参数】【函数参数】
js不会检查任何参数类型,任何参数都可以作为参数传递 1、对象参数 改变量随便改,改对象要看这个对象是不是有多个变量同时指向这个对象 const 用来定义常量,只能赋值一次。 变量------->对象------->属性 被const修饰的对象 …...
如何批量提取文件名到excel表格?
批量提取文件名到excel表格?关于这个问题相信很多人都遇到过,大多数人在第一次碰到的时候都不知道如何下手,大家都会立即在百度里面搜索相关方法教程,小编也试着搜索了一下,发现找到的很多方法都大同小异,需…...
CUDA线程层次一文搞懂|参加CUDA线上训练营
设备术语 Host:CPU 和 内存 (host memory)Device:GPU 和显存 (device memory) CUDA 线程层次 CUDA 线程层次分为: Thread 所有线程执行相同的核函数并行执行 Thread Block 执行在一个 Streaming Multiprocessor (SM)…...
Linux文件默认权限:umask
umask就是指定目前用户在建立文件或目录时候的权限默认值 查看方式有两种:一种可以直接输入umask,就可以看到数字类型的权限设置值,一种则是加入umask后加入-S(Symbolic)选项,就会以符号类型的方式来显示出…...
网络营销推广的三斧/seo外链收录
word2vec是早期NLP的必要预处理过程,其用于生成词的向量表示(embeding)。 其将单词映射为固定长度的向量(embeding向量),而通过向量表示可以更好地表达不同词之间的相关性,因此会使得后续的分类…...
怎么区分网站是模板做的/淘宝新店怎么快速做起来
为什么80%的码农都做不了架构师?>>> canvas 英音 /knvəs/ 美音 /knvəs/ 帆布, 画布 canvas的基本介绍 canvas是html5中新增的一个画布标签。这个标签的默认宽高为300*150设置canvas标签的宽高需要使用表格的形式,width和height…...
聊城做网站建设的公司/软文例文 经典软文范例
1. 点击密码右边的刷新图片,然后点设置个人密码 2. 设置密码...
图片设计师网站/百度数据库
SSH(安全外壳)是用于基于Linux的系统,路由器,交换机,防火墙,设备和其他资产的最常见的远程管理协议。尽管SSH守护程序提供了出色的强化功能,以增强您的身份验证方法和访问控制,但SSH…...
wordpress怎么制作订单号管理/seo描述是什么意思
转:/********************** * 课题:将窗体中鼠标所在控件名显示在窗体标题上 * 作者:ZAXX * QQ : 94027486 * 本课题可简单扩展:获取屏幕上鼠标所在位置的窗口句柄与类名 **********************/using System;using System.Draw…...
全国企业信用信息查询/东莞网络优化哪家公司好
可以添加一列,命名为NEW,此列的值用随机函数表示。 然后选中NEW列,点A-Z,此时EXCEL会让我们选择是扩展其他列,还是仅排序当前选中列,选扩展。 然后其他列就被随机排序了。 注意到:由于排序后&am…...