链表
   
    
    
    141. 环形链表!
    
     判断是否有环
    
   
判断是否有环
    
     面试问的比较多的一道题
    
    
    https://leetcode.cn/problems/linked-list-cycle/
   
给你一个链表的头节点 head ,判断链表中是否有环。
哈希表 链表 双指针
思路:利用快慢指针判断
我的思路:不对,这样不叫快慢指针,只是快指针在慢指针前面一步,但是前进的速度一样,这样永远追不上。
class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow = head
        fast = head.next
        while fast.next != None:
            if slow != fast:
                fast = fast.next
                slow = slow.next
            else:
                return True
        return False
    
    
    方法一:快慢指针
   
    
     注
    
    ,边界条件:如果链表是否为空或只有一个元素,肯定不存在环。
   
需要判断slow != fast,所以不能把slow和fast都设为head,而是slow设为head,fast设为head.next。
class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if not head or not head.next:
            return False
        # 快慢指针
        slow = head
        fast = head.next
        while fast and fast.next:
            if slow != fast:
                fast = fast.next.next
                slow = slow.next
            else:
                return True
        return False
答案形式:
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if not head or not head.next:
            return False
        slow = head
        fast = head.next
        while slow != fast:
            if not fast or not fast.next:
                return False
            slow = slow.next
            fast = fast.next.next
        
        return True
- 
     时间复杂度:O(N),其中 N 是链表中的节点数。
 
 当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 N 轮。
- 空间复杂度:O(1)。我们只使用了两个指针的额外空间。
    
    
    方法二:哈希表(更好理解)
   
使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        # 哈希表
        record = set()
        cur = head
        while cur != None:
            if cur in record:  # 直接判断节点
                return True
            record.add(cur)
            cur = cur.next
        return False
- 
时间复杂度:O(N),其中 N 是链表中的节点数。最坏情况下我们需要遍历每个节点一次。 
- 
空间复杂度:O(N),其中 N 是链表中的节点数。主要为哈希表的开销,最坏情况下我们需要将每个节点插入到哈希表中一次。 
    
    
    补充:142.环形链表II
    
     返回入环节点
    
   
返回入环节点
https://leetcode.cn/problems/linked-list-cycle-ii/
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
    
    
    方法一:快慢指针
   
前提:
快指针和慢指针都指向头节点
。!!! 计算快指针和慢指针走过的距离,任意时刻,
fast 指针走过的距离都为 slow 指针的 2 倍
。建立等式。得出结论:
从相遇点到入环点的距离加上 n-1 圈的环长,恰好等于从链表头部到入环点的距离
。
因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。(起始点指针和相遇点指针)
# 代码随想录
# 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:
                p = head   # 起始点
                q = slow   # 相遇点
                while p != q:
                    p = p.next
                    q = q.next
                return p
        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:
        # 哈希表
        record = set()
        cur = head
        while cur:
            if cur in record:
                return cur
            record.add(cur)
            cur = cur.next
        return None
    
    
    怎么计算环内有几个节点?
   
    
    
    方法一:
   
若链表中存在环,则快指针与慢指针必定相遇于环中任一结点,因此
相遇时我们开始计数直到下次相遇
,慢指针所移动步数即为环中结点个数(设环中结点数为r,另设从第一次相遇开始到第二次相遇时慢指针移动S步,那么快指针移动步数为2S,且有2S=S+r,因此r=S)。
我的写法:
class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        slow, fast = head, head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                slow = slow.next
                fast = fast.next.next
                n = 1 # 统计环内节点数
                while slow != fast:
                    slow = slow.next
                    fast = fast.next.next
                    n += 1 
                # print(n)
                return n
        return 0
    
     我的思路:起始点指针和相遇点指针到达入口节点前走过的节点数 + 从入口节点处到相遇点处的节点数 = 环内的节点数。
    
    
    但是代码这样写,当链表是一个环时,记录的节点数为0。
   
class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        # # 快慢指针,起始点指针,相遇点指针
        slow = head
        fast = head
        n = 0  # 统计环内节点数
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                p = head   # 起始点
                q = slow   # 相遇点
                while p != q:
                    p = p.next
                    q = q.next
                    n += 1
                while q != fast:
                    q = q.next
                    n += 1
                print(n)
                return p
        return None
    
    
    21. 合并两个有序链表
   
    https://leetcode.cn/problems/merge-two-sorted-lists/
    
    
     递归 链表
    
    
    
    方法一:递归(不太能理解)
   
https://leetcode.cn/problems/merge-two-sorted-lists/solution/he-bing-liang-ge-you-xu-lian-biao-by-leetcode-solu/
class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        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
    
    
    方法二:迭代(更好理解)
   
    定义一个虚拟节点
    
    https://leetcode.cn/problems/merge-two-sorted-lists/solution/he-bing-liang-ge-you-xu-lian-biao-by-leetcode-solu/
   
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        dummyHead = ListNode(-1)
        cur = dummyHead
        while l1 and l2:
            if l1.val <= l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next
        # l1和l2中至少有一个不为None, 将链表末尾指向未合并完的链表
        cur.next = l1 if l1 else l2
        return dummyHead.next
- 时间复杂度:O(n + m),其中 n 和 m 分别为两个链表的长度。因为每次循环迭代中,l1 和 l2 只有一个元素会被放进合并链表中, 因此 while 循环的次数不会超过两个链表的长度之和。所有其他操作的时间复杂度都是常数级别的,因此总的时间复杂度为 O(n+m)。
- 空间复杂度:O(1)。我们只需要常数的空间存放若干变量。
    
    
    203. 移除链表元素
   
https://leetcode.cn/problems/remove-linked-list-elements/
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
# 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: Optional[ListNode], val: int) -> Optional[ListNode]:
        dummyHead = ListNode(-1)
        cur = dummyHead
        dummyHead.next = head
        while cur.next != None:
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dummyHead.next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        dummyHead = ListNode(next = head)
        curNode = dummyHead
        while(curNode.next != None):
            if(curNode.next.val == val):
                curNode.next = curNode.next.next
            else:
                curNode = curNode.next
        return dummyHead.next
    
    
    206. 反转链表
   
https://leetcode.cn/problems/reverse-linked-list/?envType=study-plan&id=shu-ju-jie-gou-ru-men
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        pre = None
        while cur != None:
            temp = cur.next
            cur.next = pre
            pre = cur
            cur = temp
        return pre
    
    
    83. 删除排序链表中的重复元素
   
https://leetcode.cn/problems/remove-duplicates-from-sorted-list/
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
我的题解:
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head:
            return head
        cur = head
        while cur.next != None:
            if cur.val == cur.next.val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return head
答案:
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head:
            return head
        cur = head
        while cur.next:
            if cur.val == cur.next.val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return head
    
    
    补充(算法学习计划_链表_双指针):
   
    
    
    876. 链表的中间结点
   
https://leetcode.cn/problems/middle-of-the-linked-list/
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
    
    
    方法一:快慢指针法
   
用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。
class Solution:
    def middleNode(self, head: ListNode) -> ListNode:
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow
- 
时间复杂度:O(N),其中 NN 是给定链表的结点数目。 
- 
空间复杂度:O(1),只需要常数空间存放 slow 和 fast 两个指针。 
    
    
    方法二:单指针法
   
可以对链表进行两次遍历。第一次遍历时,我们统计链表中的元素个数 N;第二次遍历时,我们遍历到第 N/2 个元素(链表的首节点为第 0 个元素)时,将该元素返回即可。
class Solution:
    def middleNode(self, head: ListNode) -> ListNode:
        n, cur = 0, head
        while cur:
            n += 1
            cur = cur.next
        k, cur = 0, head
        while k < n // 2:
            k += 1
            cur = cur.next
        return cur
    
    
    24. 两两交换链表中的节点
   
https://leetcode.cn/problems/swap-nodes-in-pairs/
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
    
    
    方法一:直接交换
   
定义一个虚拟头节点
class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next: return head
        dummyNode = ListNode(next=head)
        cur = dummyNode
        while cur.next != None and cur.next.next != None:
            # 暂存,有点绕
            tmp = cur.next
            tmp1 = cur.next.next
            tmp.next = tmp1.next
            tmp1.next = tmp
            cur.next = tmp1
            # 循环,准备下一轮交换
            cur = cur.next.next
        return dummyNode.next
    
    
    19. 删除链表的倒数第 N 个节点
   
https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
给你一个链表,删除链表的倒数第 n 个节点,并且返回链表的头节点。
    思路:定义一个
    
     虚拟头节点
    
    ,利用
    
     快慢指针
    
    。首先,快慢指针都指向虚拟头节点,然后
    
     快指针先走n步
    
    ,之后快慢指针同时向后移动,当快指针走到结尾时,
    
     slow指针的下一个节点
    
    为倒数第n个节点。
   
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        # 定义一个虚拟头节点
        dummyNode = ListNode()
        dummyNode.next = head
        slow = fast = dummyNode
        # 快指针先走n步
        for i in range(n):
            fast = fast.next
        
        while fast.next != None:
            slow = slow.next
            fast = fast.next
        # fast走到结尾后,slow的下一个节点为倒数第n个节点
        slow.next = slow.next.next
        return dummyNode.next
另一种写法:思路是一样的
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummyNode = ListNode(next=head)
        slow = fast = dummyNode
        for i in range(n):
            fast = fast.next
        while fast.next != None:
            slow = slow.next
            fast = fast.next
        
        slow.next = slow.next.next
        return dummyNode.next
    试了一下
    
     return head
    
    ,在下面的情况下会报错。返回的是
    
     
      
       [ 
1
]
        [1]
      
      
       
        
        
        
         [
        
        
         1
        
        
         ]
        
       
      
     
    
    
    输入:head = [1], n = 1
    
    输出:[]
   
    
    
    20221020 链表排序
   
    
    
    148.排序链表&剑指 Offer II 077. 链表排序
   
- 排序链表 https://leetcode.cn/problems/sort-list/
给你链表的头结点
head
,请将其按
升序
排列并返回
排序后的链表
。
归并排序
    
    
    147. 对链表进行插入排序
   
https://leetcode.cn/problems/insertion-sort-list/
给定单个链表的头 head ,使用
插入排序
对链表进行排序,并返回
排序后链表的头
。
插入排序
算法的步骤:
1.插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
2.每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
3.重复直到所有输入数据插入完为止。
 
