链表是比较重要的数据结构(也是我掌握得很差的)
在剑指offer上刷了几道题,在这里做个总结:
.
链表中倒数第k个节点
这是leetcode上的一道简单题。
第一时间的想法:
- 用一个while循环,计算出链表长度n
- 再用一个while循环,对头节点进行n-k次next操作,即可求解
但是看过题解之后,发现了一种更简洁的方法,该方法可以不用计算出链表长度,求解如下:
- 定义两个结点former和latter。former指向头节点,latter指向former后K个元素(例如在示例中,former指向1, latter指向3)
- 令former和latter同时向后移,当latter指向链表尾元素时,此时former即为所求的结点。
这种方法在我理解就像是用长度为k的绳子把两个结点系住了,当后面的结点为链表尾部结点时,前面的结点自然是倒数第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:
former, latter = head, head
for i in range(k):
latter = latter.next
while latter:
latter = latter.next
former = former.next
return former
.
反转链表
这题重点是想清楚对每一个节点是怎么操作的。只要弄懂了每一个节点上的操作,那么对整个链表就一目了然了。
假设我们正在对1->2->3中的2进行操作,操作有两步
- 将2的next赋值为1(即1<-2)
- 将指针移动到3(下一步再对3进行操作)
因此我们需要用到两个指针:
- cur:指向正在操作的元素(假设中的2)
- pre:指向上一个元素(假设中的1)
- T:指向下一个要操作的元素(假设中的3)
值得注意的是,因为cur的next指针经过了修改,所以T不能直接由cur的next得到,需要提前保存
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while cur:
t = cur.next
cur.next = pre
pre = cur
cur = t
return pre
.
合并两个排序的链表
这依然是一道…简单题。
不过这题体现了链表类题目中一个较通用的办法:哨兵节点。
这一题的思想很简单:挨个比较两个链表中的元素,将较小的元素插入到目标链表中,直到两个链表均访问到末尾为止。
但是有一点需要注意:我们该如何建立目标链表?
最直接的想法是:可以在原有的链表基础上进行构建。
例如对A,B链表,指定A为目标链表,在A上进行修改。但这样有个问题,当B中的节点插入到A中时(也就是将A中某节点的next指向B中某节点),此时原A中节点的next指向的节点便丢失了。因此需要预先保存。这其实是不必要的开销。
嘿嘿,这时我们的哨兵节点就要出场了。
虽然名字叫哨兵,但它其实就是一个空节点。之后的插入节点,都是在它的屁股后面插。
这避免了什么问题?因为空节点跟原来的A,B链表都没有关系,所以A,B中的节点可以放心大胆地往里面插——反正不会破坏A,B的内部结构。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1, l2):
# maintain an unchanging reference to node ahead of the return node.
prehead = ListNode(-1) ## 哨兵节点
prev = prehead
while l1 and l2:
if l1.val <= l2.val:
prev.next = l1
l1 = l1.next
else:
prev.next = l2
l2 = l2.next
prev = prev.next
# exactly one of l1 and l2 can be non-null at this point, so connect
# the non-null list to the end of the merged list.
prev.next = l1 if l1 is not None else l2
return prehead.next