python实现Leetcode链表题型全解(反转,合并,删除,环,公共节点,复制,相加,重复,回文)
1.反转链表
输入一个链表,反转链表后,输出新链表的表头
主要的思想是用两个指针,其中newHead指向的是反转成功的链表的头部,currentHead指向的是还没有反转的链表的头部:
初始状态是newHead指向null,currentHead指向的是第一个元素,一直往后遍历直到newHead指向最后一个元素为止:
下面展示的是其中某个时间点的指向细节:
注意:考虑空链表和链表里只有一个元素的情况。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# 空链表{}
if not pHead :
return None
#只有一个元素表{1}
if not pHead.next:
return pHead
#currenthead就是从第一个元素往后移动的原链表元素
currenthead,newhead=pHead,None
while currenthead:
#以第一次循环为例子:{1,2,3,4,5}
#(1)temp指向2
temp=currenthead.next
#(2)currenthead的next指向newhead,1指向none
currenthead.next=newhead
#(3)newhead现在指向到第一个元素1
newhead=currenthead
#(4)currenthead指向原来存储好的2的位置
currenthead=temp
return newhead
详细过程循环里图解:
2.合并2个有序链表
将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的,且合并后新链表依然有序。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路1:
新建表头new,用一个指针(初始为new)往后走进行判断链表,判断大小确定当前下一个元素是什么.直到2个链表有1个为空,剩下不空的链表接在后面.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param l1 ListNode类
# @param l2 ListNode类
# @return ListNode类
#
class Solution:
def mergeTwoLists(self , l1 , l2 ):
#若有一个为空的链表,则直接返回另一个有序链表
if not l1:return l2
if not l2:return l1
new=ListNode(-1)
l=new
while l1 and l2:
if l1.val>l2.val:
l.next,l2=l2,l2.next
else:
l.next,l1=l1,l1.next
l=l.next
l.next=l1 if l1 else l2
return new.next
思路2:
或者不新建表头,使用递归,将指针指向当前小的val的链表(假如l1.val<l2.val),再递归合并(l1.netx,l2).
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self , l1 , l2 ):
# write code here
if not l1:return l2
if not l2:return l1
#递归
if l1.val<=l2.val:
l=l1
l.next=self.mergeTwoLists(l1.next,l2)
else:
l=l2
l.next=self.mergeTwoLists(l1, l2.next)
return l
3. 删除链表节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
要考虑删除头节点情况
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
if head.val==val: return head.next
pre,cur=head,head.next
while cur and cur.val!=val:
pre,cur=cur,cur.next
if cur: pre.next=cur.next
return head
4-1.删除链表的倒数第n个节点
给定一个链表,删除链表的倒数第n个节点并返回链表的头指针.
输入{1,2,3,4,5,6},2
返回{1,2,3,4,6}
快慢指针均指向头节点:快指针先走k步,然后两个指针一起走直到快指针走到最后一个节点,此时慢指针位于要删除的节点的前一个节点,直接slow.next=sloe.next.next删除即可,返回头指针head.
且注意,考虑删除头结点(len - n == 0)的特例,直接返回head->next
分如下两种情况:
1 n=0(链表为空)或n=1(链表只有一个元素),直接返回None
2 常规情况,两个游标,一个游标first_cur先走n步,然后两个游标first_cur/last_cur一起移动。需要注意当n=链表的长度时,即需要删除头节点时的情况。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param head ListNode类
# @param n int整型
# @return ListNode类
#
class Solution:
def removeNthFromEnd(self , head , n ):
#0 or 1
cur = head.next if head else head
if not cur:
return None
slow,fast=head,head
for _ in range(n):
fast=fast.next
if not fast:return head.next
while fast.next:
slow,fast=slow.next,fast.next
slow.next=slow.next.next
return head
4-2. 输出该链表中倒数第k个结点
注意:如果是返回从第k个节点开始的链表
比如
输入{1,2,3,4,5,6},2
返回{5,6}
则不需要slow.next=sloe.next.next,且一直快指针跑到为空,慢指针跑到倒数第k个节点处
返回去slow即可
如下题:
输入一个链表,输出该链表中倒数第k个结点。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param pHead ListNode类
# @param k int整型
# @return ListNode类
#
class Solution:
def FindKthToTail(self , pHead , k ):
# write code here
# if not pHead:return pHead
slow,fast=pHead,pHead
for _ in range(k):
#注意:下面一句判断解决链表为{}情况和{1,2,3},k=50情况,均应该返回空链表
if not fast: return fast
fast=fast.next
while fast:
slow,fast=slow.next,fast.next
return slow
5-1.链表是否有环
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
你能给出空间复杂度的解法
根据快慢指针是否相遇判断
快指针每次两步,慢指针每次一步,有环则必相遇。
终止条件:null,单个节点,两个结点,都不可能有环
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
# @return bool布尔型
class Solution:
def hasCycle(self , head ):
if not head:
return False
fast,slow=head,head
while fast and fast.next:
fast,slow=fast.next.next,slow.next
if fast==slow:
return True
return False
5-2.若链表有环找入环点
对于一个给定的链表,返回环的入口节点,如果没有环,返回null
拓展:你能给出不利用额外空间的解法么?
解法:一个快指针,两个慢指针。
1.快慢指针同时出发,如果快指针到达null,说明链表没有环
2.如果快慢指针相遇了,说明链表有环。
(以上和5-1一样)
3.在快慢指针相遇的时候,让第二个慢指针从头结点出发,
4.当两个慢指针相遇的时候,相遇的位置就是环的起点。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
# @param head ListNode类
# @return ListNode类
class Solution:
def detectCycle(self , head ):
if not head:
return head
slow,fast=head,head
while fast and fast.next:
fast,slow=fast.next.next,slow.next
if fast==slow:
slow2=head
while slow!=slow2:
slow,slow2=slow.next,slow2.next
return slow
return None
6.两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
解题思路:
我们使用两个指针 node1
,node2
分别指向两个链表 headA
,headB
的头结点,然后同时分别逐结点遍历,当 node1
到达链表 headA
的末尾时,重新定位到链表 headB
的头结点;当 node2
到达链表 headB
的末尾时,重新定位到链表 headA
的头结点。
这样,当它们相遇时,所指向的结点就是第一个公共结点。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param pHead1 ListNode类
# @param pHead2 ListNode类
# @return ListNode类
#
class Solution:
def FindFirstCommonNode(self , pHead1 , pHead2 ):
node1,node2=pHead1,pHead2
while node1!=node2:
node1=node1.next if node1 else pHead2
node2=node2.next if node2 else pHead1
return node1
7.复制复杂链表(含random)
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
解题思路: 哈希表
考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。
参考ppt理解
:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/jian-zhi-offer-35-fu-za-lian-biao-de-fu-zhi-ha-xi-/
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head: return
dic = {}
# 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
cur = head
while cur:
dic[cur] = Node(cur.val)
cur = cur.next
cur = head
# 4. 构建新节点的 next 和 random 指向
while cur:
dic[cur].next = dic.get(cur.next)
dic[cur].random = dic.get(cur.random)
cur = cur.next
# 5. 返回新链表的头节点
return dic[head]
8.两个链表相加
假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。
链表元素正序
情况1:链表元素正序存放
输入
[9,3,7],[6,3]
返回值
{1,0,0,0}
思路:
因为相加应该从个位开始,同时考虑进位问题
因为两个链表和最后相加后的链表都要求是正序.因此用到三次反转.
首先对于两个链表进行一次翻转,即[7,3,9],[3,6]进行相加
当两个列表均空的时候停止相加.
初始化进位jw=0,新链表表头newhead,cur指向新链表表头.
循环里:
res=jw值
哪个链表非空:res+=head1.val,同时链表往下走一步
cur.next指向新节点,节点的值为res%10
更新进位jw=res//10
同时指针往前一步
当两个链表均为空的时候跳出循环,此时还得看可能还有进位,则如果此时仍有进位的值,cur.next指向val为该进位值的新节点.
注意,此时新的链表头节点值为-1,因此从newhead.next开始,反转后即为相加后链表的正序.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param head1 ListNode类
# @param head2 ListNode类
# @return ListNode类
#
class Solution:
def reverse(self,head):
pre,cur=None,head
while cur:
temp=cur.next
cur.next=pre
pre=cur
cur=temp
return pre
def addInList(self , head1 , head2 ):
head1,head2=self.reverse(head1),self.reverse(head2)
newhead=ListNode(-1)
cur=newhead
jw=0
while head1 or head2:
res=jw
if head1:
res+=head1.val
head1=head1.next
if head2:
res+=head2.val
head2=head2.next
cur.next=ListNode(res%10)
jw=res//10
cur=cur.next
if jw>0:
cur.next=ListNode(jw)
return self.reverse(newhead.next)
注意:也可以不用反转链表,可以用三个栈,head1,head2分别入栈,相加后的元素也入栈,再按照出栈顺序构造新的链表.
链表元素倒序
情况2:链表元素倒序存放
如输入:(7 -> 1 -> 6) ,(5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912
此时不需要reverse操作,其他的与情况1代码一致即可
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
newhead=ListNode(-1)
cur,jw=newhead,0
while l1 or l2:
res=jw
if l1:
res+=l1.val
l1=l1.next
if l2:
res+=l2.val
l2=l2.next
cur.next=ListNode(res%10)
jw=res//10
cur=cur.next
if jw>0:
cur.next=ListNode(jw)
return newhead.next
9.删除链表的重复元素
未排序链表
情况1:移除未排序链表中的重复节点。保留最开始出现的节点
输入:[1, 2, 3, 3, 2, 1]
输出:[1, 2, 3]
思路:
新建一个节点cur指向头节点,利用一个set().
当cur.next非空时遍历(从头节点到最后一个节点):
若cur.next的值不在visited里,则加入,同时cur往前走一步
否则,删除cur.next的节点.
最后输出head
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param head ListNode类
# @return ListNode类
#
class Solution:
def deleteDuplicates(self , head ):
if not head or not head.next:
return head
node=ListNode(-1)
node.next=head
visited=set()
while node.next:
if node.next.val not in visited:
visited.add(node.next.val)
node=node.next
else:
node.next=node.next.next
return head
排序链表
情况2:移除排序链表中的重复节点。保留最开始出现的节点
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次.
例如:
输入
{1,1,2}
返回
{1,2}
思路:
直接cur指向头指针
在cur.next非空时遍历,因为已经排序,如果重复,只可能相邻的值重复,因此若cur的值和cur.next值相等,则删除cur.next,否则,cur正常往后走.
最后返回head
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param head ListNode类
# @return ListNode类
#
class Solution:
def deleteDuplicates(self , head ):
# write code here
if not head or not head.next:return head
cur=head
while cur.next:
if cur.next.val==cur.val:
cur.next=cur.next.next
else:
cur=cur.next
return head
排序链表,且不把重复元素保留一次,全部删除
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
例如:
输入
{1,2,2}
返回值
{1}
思路:
建立两个一样的链表new和pre,选择遍历head,当head.val==head,next.val时,进入while循环,head往前走,循环结束,依然head往前走一步,因为只要是重复的都不要,然后pre选择新的next,但不更新pre,因为此时的head尽管以及跳过一段重复了,但可能也是下一个重复数字的开始,若还没更新pre,head已经到最后一个数字了,那么刚好pre.next就是head。否则head和pre同时更新。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param head ListNode类
# @return ListNode类
#
class Solution:
def deleteDuplicates(self , head ):
new=ListNode(-1)
new.next=head
pre=new
while head and head.next:
if head.val==head.next.val:
while head and head.next and head.val==head.next.val:
head=head.next
head=head.next
pre.next=head
else:
pre,head=pre.next,head.next
return new.next
10. 判断链表是否回文
这个在leetcode上可以AC但是在牛客网上总超时QAQ
那理解方法为主吧
题目:
编写一个函数,检查输入的链表是否是回文的。
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
思路
如果空列表,直接True
(1)找到链表中心位置:
设置fast,slow初始化=head,即均指向第一个节点:
fast每走2步,slow走1步
当fast为空或fast.next为空,则跳出.
-
当链表长度为奇数的时候
跳出循环时候,slow指向最中间的数字(不是后半部分链表的开始,不过不影响后面比对)
-
当链表长度为偶数的时候
如图再往下一步即fast为空,此时slow指向后半段链表的第一个节点(图中未画出)
(2)反转后半部分链表,前后两部分链表进行比较
类似反转链表的写法:此时fast指向前半段链表的头节点,reverse为后半段链表反转后的头节点.
当二者都不为空时,比较对应节点值,如果不一样,立刻返回False
否则,fast,reverse均向前走[注意:当链表长度为奇数的时候,此时reverse链表比fast链表长,但是不要紧,fast链表走完即跳出,不会到那一步比较]
跳出返回True
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
# l=[]
# while head:
# l.append(head.val)
# head=head.next
# return l[:]==l[::-1]
if not head :return True
fast,slow=head,head
#找到中心点
while fast and fast.next:
fast=fast.next.next
slow=slow.next
#反转后半部分
fast=head
reverse=None
while slow:
temp=slow.next
slow.next=reverse
reverse=slow
slow=temp
#比较两部分是否相等
while fast and reverse:
if fast.val!=reverse.val:
return False
fast,reverse=fast.next,reverse.next
return True
注释部分代码是将链表push进列表,根据python列表的比较,看翻转后和原来是否一致比较.list[:]==list[::-1]
11.重排链表
题目描述:
要求使用原地算法,不能改变节点内部的值,需要对实际的节点进行交换。
例如:
对于给定的单链表{10,20,30,40},将其重新排序为{10,40,20,30}.
思路:
(1)利用快慢指针找到中间节点,链表分为两部分
(2)对后半部分的链表进行反转操作
(3)交叉取节点合并成新链表
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
#
# @param head ListNode类
# @return void
#
class Solution:
def reorderList(self , head ):
#当空链表或者只有1个或2个节点时候不用重排
if not head or not head.next or not head.next.next:
return head
# 1.找到中间节点,链表分两个部分
slow,fast=head,head
while fast.next and fast.next.next:
fast,slow=fast.next.next,slow.next
right=slow.next
slow.next=None
#2.反转后半部分链表
pre=None
while right:
temp=right.next
right.next=pre
pre=right
right=temp
left=head
right=pre
#3合并两部分链表,left和pre(right)
while left and right:
next1,next2=left.next, right.next
left.next= right
left=next1
right.next=left
right=next2
return left
注意分开两个链表时的一个操作:图解一下!
right=slow.next
slow.next=None
--------2021.03----updating-------------------