总结了常考的十一种操作:判断是否有环
找环的入口
找到中点
找到倒数第K个结点
逆序链表
删除倒数第K个结点
将有序链表转成搜索二叉树
合并两个有序链表
判断是否回文
旋转链表
相交链表
其中找中间结点,倒数第k个结点,逆序链表这三个操作最常用,通过组合可以解决链表的绝大部分问题,并利用哑结点这个小技巧避免空链表与一个结点的链表。
1. 判断是否有环,leetcode141
思路:快慢指针,fast走两步,slow走一步。如果无环快指针最终会遇到null,若有环快指针最终会超慢指针一圈,和慢指针相遇。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
slow, fast = head, head
while(fast!=None and fast.next!=None):
slow = slow.next
fast = fast.next.next
if (slow == fast):
return True
return False
2. 找环的入口,leetcode142
思路:快慢指针,快的两步,慢的一步,当有环时他俩会相遇,此时快指针走的步数2k是慢指针k的两倍,设相遇点距离入口为m,则入口距离起点为k-m,巧的是环的长度为k,因为快指针比慢指针多绕了一环,所以多出来的就是环的长度,所以从相遇点继续前进k-m步,也恰好到环起点。所以只需把其中一个指针重置为起点,然后同步向前,再次相遇的时候就是入口。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast, slow = head, head
while(fast!=None and fast.next!=None):
fast = fast.next.next
slow = slow.next
if slow == fast:
break
if fast == None or fast.next == None:
return
slow = head
while(slow!=fast):
slow = slow.next
fast = fast.next
return slow
3. 找中点(常用技巧),leetcode876
思路:快慢指针,fast走两步,slow走一步,当fast走到链表最后一个结点是,slow为中间结点。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
slow, fast = head, head
while(fast!=None and fast.next!=None):
fast = fast.next.next
slow = slow.next
return slow
4. 倒数第k个结点,剑指offer22
思路:快慢指针同时指向头结点,fast先走k步,此时fast和slow距离即为k,然后一直遍历节点到fast指向null,因为俩指针距离k,所以此时慢指针的位置即为倒数第k个结点。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
slow, fast = head, head
while(k):
fast = fast.next
k-=1
while(fast):
fast = fast.next
slow = slow.next
return slow
5. 逆序链表,leetcode206
思路一:迭代,搞一个辅助结点先指向null,类似于空瓶子倒水,注意每一步保存原始的head.next,不然将head.next改变指向后会找不到原来的下个结点。
思路二:递归,因为逆序所以要递归到最深再操作,相当于后序,后序操作为改变当前结点下一个结点的指向,并将当前结点指向null;终止条件是递归到最后一个结点,然后返回最后一个结点。最终逆序结束的返回为最后一个结点。
切记,python中递归调用自身要加self.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
# 迭代
# pre = None
# while(head):
# temp = head.next
# head.next = pre
# pre = head
# head = temp
# return pre
# 递归
if not head or not head.next:
return head
p = self.reverseList(head.next)
head.next.next = head
head.next = None
return p
6. 删除倒数第k个结点,leetcode19
思路:利用快慢指针找到倒数第k个结点的前一个结点,即k+1,然后将其next指针指向下下个结点即可。按照找倒数第K个的方法,将判别条件改成fast.next!=null,找到的就是倒数第k+1个结点,不用再在slow前创建一个pre指针了,但当输入链表只有一个时容易报错,fast已经是null,没有next属性会报错,所以需要创建哑结点。
Note:这里用到了一个常用技巧,创建哑结点指向head,即dummy.next = head,可以有效防止链表为空或者链表仅有一个值的特殊情况。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
slow = dummy
fast = dummy
while(n):
fast = fast.next
n -= 1
while(fast.next): # 判断条件变了,找到的是倒数第k个结点的前一个
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next
7. 将有序链表转成搜索二叉树,leetcode109
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。一个高度平衡二叉树是指一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1。
思路:因为有序链表,且搜索二叉树特点就是中序遍历完是个有序列表。因为高度平衡,所以左右子树不能差1,所以每次重建的时候选取链表的中间结点作为根节点,前半部分为左子树,后半部分为右子树,依次递归即可。
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def sortedListToBST(self, head: ListNode) -> TreeNode:
if not head: return
if not head.next: return TreeNode(head.val)
fast = head
slow = head
preslow = head
while(fast and fast.next):
preslow = slow
fast = fast.next.next
slow = slow.next
root = TreeNode(slow.val)
if preslow:
preslow.next = None
root.left = self.sortedListToBST(head)
root.right = self.sortedListToBST(slow.next)
return root
8. 合并两个有序链表,leetcode21
思路1:暴力法,遍历比较l1和l2两个链表的每个结点,因为是有序的,所以当一个比较完时,剩的那个链表的所有结点一定大。直接链到后边即可。
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
head = ListNode(0)
prehead = head
while(l1 and l2):
if l1.val <= l2.val:
head.next = l1
l1 = l1.next
elif l1.val > l2.val:
head.next = l2
l2 = l2.next
head = head.next
if l1:
head.next = l1
elif l2:
head.next = l2
return prehead.next
思路2:递归,见等价子方程,结束条件为递归到null
list1[0]+merge(list1[1:],list2) list1[0]
list2[0]+merge(list1,list2[1:]) otherwise
class Solution:
def mergeTwoLists(self, l1, l2):
if l1 is None:
return l2
elif l2 is None:
return l1
elif l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
9. 判断是否回文,leetcode234
思路:先用快慢指针找到中间结点,注意偶数的话找后边的那个结点,然后把中间以后的后半部分链表反转,然后遍历后半部分的链表,判断每个结点的值是否相等。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
fast, slow = head, head
while(fast and fast.next):
fast = fast.next.next
slow = slow.next
post = self.reverseList(slow)
while(post):
if not post.val == head.val:
return False
post = post.next
head = head.next
return True
def reverseList(self,head):
if not head or not head.next:
return head
p = self.reverseList(head.next)
head.next.next = head
head.next = None
return p
10. 旋转链表,leetcode61
给定一个链表,旋转链表,将链表每个节点向右移动 k个位置,其中 k是非负数。
输入:1->2->3->4->5->NULL, k = 2
输出:4->5->1->2->3->NULL
思路:因为k可能会超出链表长度,所以计算出链表长度len,然后k对len求余得到真实旋转的长度。观察可以发现,右移k个位置其实就是把倒数第k个结点作为头结点,倒数k+1个结点作为尾结点并指向null,为了找到倒数第k+1个结点,使用fast.next作为结束条件。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def rotateRight(self, head: ListNode, k: int) -> ListNode:
if not head:
return head
length = 0
fast = head
slow = head
aux = head
while(aux):
aux = aux.next
length += 1
k %= length
while(k):
fast = fast.next
k -= 1
while(fast.next):
fast = fast.next
slow = slow.next
fast.next = head
head = slow.next
slow.next = None
return head
11. 相交链表,leetcode160
思路1:遍历其中一个链表,将其每个结点存入字典中,然后遍历另一个当其结点存在于字典中返回该结点,如果都不存在返回None。
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
dict = {}
while(headA):
dict[headA] = 1
headA = headA.next
while(headB):
if headB in dict:
return headB
headB = headB.next
return None
思路2:让两个链表一起走,当其中一个A走到尾时,另一个链表B走的路径正好为链表A的长度。然后将A的指针pA置为B的头结点,此时pB指针和pA指针的距离差即为A的长度,然后继续同步遍历俩个指针,当pB达到B尾部时,将其置为A的头结点,此时pA到B的尾部的距离即为A的长度,pB到A的尾部的距离也为A的长度。然后再同步走两个指针,当指向的两个结点的值相等时,即为交点。
class Solution(object):
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
ha, hb = headA, headB
while ha != hb:
ha = ha.next if ha else headB
hb = hb.next if hb else headA
return ha