单链表结构原理比较简单,主要是编程时指针位置容易搞混。
另:不能像列表一样可以按下标查找,单链表只能顺序遍历查找。
如上图所示,节点由数据域和指针域构成,当前指针的next指向下一个元素的指针域,val(图中的elem)指向当前的元素值,节点定义如下:
# 定义节点
class Node(object):
def __init__(self,val,p=0):
self.val = val
self.next = p
一个个节点连起来,即构成了单链表,而也正是这样的原因,我们只要知道一个链表的首个元素的指针,即可以知道整个链表的元素和指针。
解题时,如下图所示,一般需要知道三个地方的指针,前一个指针prev,当前指针curr,下一个指针Next(curr.next),题目一般是需要改变这三个指针的位置,达到反转链表、删除链表某元素等的目的。
而一般头节点会变动的情况,则可能需要设置虚拟节点,让它指向head节点,虚拟节点没有元素值。
dummy_node = ListNode(-1) # 定义一个虚拟节点
dummy_node.next = head
主要是注意表头的前一节点、当前节点、后一节点的变化关系,一般如果需要前一节点指向头节点,则需要设置一个虚拟节点,即prev.next=head,从而使head前面有一个虚拟节点连接
# 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:
prev = None
curr = head
while curr :
Next = curr.next
curr.next = prev
prev = curr
curr = Next
return prev
这里主要注意的是,由于移除操作没有改变curr指针指向的下一个元素,即没有改变curr.next,因此不需要创建Next变量存储curr.next。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
header = ListNode(-1) # 虚拟节点
header.next=head
prev, curr = header, head
while(curr):
if curr.val==val:
prev.next=curr.next
else:
prev=curr
curr = curr.next
return header.next
用节点画图表示一下当前的prev,curr,left_node,right_node分别指在何处。。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
def revese_link(head:ListNode):
pre = None
cur = head
while cur:
Next = cur.next
cur.next = pre
pre = cur
cur = Next
dummy_node = ListNode(-1)
dummy_node.next = head
prev = dummy_node
for _ in range(left-1):
prev = prev.next
right_node = prev
for _ in range(right-left+1):
right_node = right_node.next
left_node = prev.next
curr = right_node.next # 保存一下当前节点
prev.next = None
right_node.next = None
revese_link(left_node)
prev.next = right_node
left_node.next = curr
return dummy_node.next
(待定)比单链表多了个random节点,因此不好用while遍历的方式进行复制。
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
def dfs(head):
if not head: return None
if head in visit:
return visit[head]
copy = Node(head.val,None,None)
visit[head] = copy
copy.next = dfs(head.next)
copy.random = dfs(head.random)
return copy
visit = {}
return dfs(head)
容易想到的是,先遍历一遍,计算一下链表的长度,第二次则循环长度减k次即可。
Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
if not head: return None
curr = head
count = 0
while curr:
count+=1
curr = curr.next
count = count -k
while count:
count-=1
head = head.next
return head
另一种方法是,快慢指针,快指针先走k步,然后慢指针和快指针一起走,直到快指针结束。
if not head: return None
fast,low = head,head
for _ in range(k):
fast = fast.next
while fast:
fast = fast.next
low = low.next
return low
遍历最短的一个链表。。。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy_node = ListNode(-1)
curr = dummy_node
while l1 and l2: # 取最短的
if l1.val<l2.val: # 迭代至一个空
curr.next,l1 = l1,l1.next
else:
curr.next,l2 = l2,l2.next
curr = curr.next # 往下走
curr.next = l1 if l1 else l2 # 如果一个为空,则直接接入
return dummy_node.next
一个绝妙的思路是,当链表A从头开始,到B的公共节点,走a+b-c步,而同时B从头开始,到A处的公共节点,需要走b+a-c步,因此,他们两一起走,走到公共节点的步数一致(a为A的长度,b为B的长度,c为公共节点的长度)。
# 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:
A,B = headA,headB
while A!=B:
A = A.next if A else headB
B = B.next if B else headA
return A