题目:面试题 02.02. 返回倒数第 k 个节点
题目描述
代码实现
方法一:暴力破解
这道题给了一个ListNode
头结点代表一条单链表,因此我们不能直接得出这条链表的长度n
,而需要先遍历一遍链表算出n
的值,然后再遍历链表计算第n-k
个节点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def kthToLast(self, head: ListNode, k: int) -> int:
# 遍历两次:
# 第一次找出链表长度n
# 第二次返回n-k的值
if not head: return float('-inf')
cur = node = head
n = 0
while node.next:
n += 1
node = node.next
n += 1
i = 0
while cur.next:
if i == n-k:
return cur.val
i += 1
cur = cur.next
return cur.val
方法二:双指针
能不能只遍历一次链表,就算出倒数第k
个节点?可以做到的,如果是面试问到这道题,面试官肯定也是希望我们给出只需遍历一次链表的解法。思路如下:
首先,我们先让一个指针p1
指向链表的头节点head
,然后走k
步;
然后,此时p2指针和p1指针一起走,直到p1指针走到尾巴的时候,p2指针刚好指向倒数k的位置。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def kthToLast(self, head: ListNode, k: int) -> int:
"""双指针"""
p1 = p2 = head
# p1先走k步
while k>0:
p1 = p1.next
k -= 1
# p1和p2同时走n-k步
while p1:
p2 = p2.next
p1 = p1.next
# p2 现在指向第n-k个节点
return p2.val
题目:19. 删除链表的倒数第 N 个结点
题目描述
代码实现
方法:双指针
要删除倒数第n
个节点,就得获得倒数第n+1
个节点的引用,可以先写一个find函数
来找节点。
# 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:
# 要删除倒数第n个节点,就得获得倒数第n+1个节点
def find(head: ListNode, k: int) -> ListNode:
p1 = p2 = head
# p1先走k步
while k>0:
p1 = p1.next
k -= 1
# p1和p2同时走n-k步
while p1:
p2 = p2.next
p1 = p1.next
# p2 现在指向第n-k个节点
return p2
dummy = ListNode(0)
dummy.next = head
# 删除倒数第n个,要先找倒数第n+1个节点
node = find(dummy, n+1)
# 删掉倒数第n个节点
node.next = node.next.next
return dummy.next
题目:876. 链表的中间结点
题目描述
代码实现
方法:快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
"""
快慢指针:两个指针slow和fast分别指向链表头结点head
(1)快指针走两步
(2)慢指针走一步
每当慢指针slow前进一步,快指针fast就前进两步,
这样,当fast走到链表末尾时,slow就指向了链表中点。
"""
slow = fast = head
while fast and fast.next:
# 慢指针走一步,快指针走两步
slow = slow.next
fast = fast.next.next
# 慢指针指向中点
return slow
题目:141. 环形链表
题目描述
代码实现
方法一:set()
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
"""set()"""
if not head: return False
tmp = set()
p1 = head
# tmp.add(p1)
while p1:
if p1 not in tmp:
tmp.add(p1)
else:
return True
p1 = p1.next
return False
方法二:快慢指针
每当慢指针slow
前进一步,快指针fast
就前进两步。
如果fast
最终遇到空指针,说明链表中没有环;如果fast
最终和slow
相遇,那肯定是fast
超过了slow
一圈,说明链表中含有环。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
"""快慢指针"""
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if fast==slow:
return True
return False
题目:142. 环形链表 II
题目描述
代码实现
方法一:set集合
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
"""set()"""
tmp = []
p1 = head
while p1:
if p1 not in tmp:
tmp.append(p1)
else:
return p1
p1 = p1.next
return None
方法二:快慢指针
假设快慢指针相遇时,慢指针slow
走了k
步,那么快指针fast
一定走了2k
步:
fast
一定比slow
多走了k
步,这多走的k
步其实就是fast
指针在环里转圈圈,所以k
的值就是环长度的「整数倍」。
假设相遇点距环的起点的距离为m
,那么结合上图的slow指针,环的起点距头结点head
的距离为k-m
,也就是说如果从head
前进k-m
步就能到达环起点。
如果从相遇点继续前进k-m
步,也恰好到达环起点。因为结合上图的 fast 指针,从相遇点开始走k
步可以转回到相遇点,那走k-m
步肯定就走到环起点了:
所以,只要我们把快慢指针中的任一个重新指向head
,然后两个指针同速前进,k-m
步后一定会相遇,相遇之处就是环的起点了。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
if head is None or head.next is None:
return None
p = head
slow = head.next
fast = head.next.next
while fast and fast.next:
if slow == fast:
while(p != slow):
p = p.next
slow = slow.next
return slow
slow = slow.next
fast = fast.next.next
return None
另一种写法
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow==fast:
break
if fast is None or fast.next is None:
return None
# 重新指向头节点
slow = head
while slow != fast:
# 快慢指针同步前进,相交点就是环起点
slow = slow.next
fast = fast.next
return slow
题目:160. 相交链表
题目描述
代码实现
方法一:set()
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
"""set()"""
if not headA or not headB:
return None
p1 = headA
p2 = headB
tmp = set()
while p1:
if p1 not in tmp:
tmp.add(p1)
p1 = p1.next
while p2:
if p2 not in tmp:
tmp.add(p2)
else:
return p2
p2 = p2.next
return None
方法二:双指针
如果用两个指针p1
和p2
分别在两条链表上前进,并不能同时走到公共节点,也就无法得到相交节点c1
。
所以,解决这个问题的关键是,通过某些方式,让p1
和p2
能够同时到达相交节点c1
。
所以,可以让p1
遍历完链表A
之后开始遍历链表B
,让p2
遍历完链表B
之后开始遍历链表A
,这样相当于「逻辑上」两条链表接在了一起。
如果这样进行拼接,就可以让p1
和p2
同时进入公共部分,也就是同时到达相交节点c1
:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
"""set()"""
if not headA or not headB:
return None
p1 = headA
p2 = headB
while p1 != p2:
if p1 == None:
p1 = headB
else:
p1 = p1.next
if p2 == None:
p2 = headA
else:
p2 = p2.next
return p1