链表理论基础及例题(三)

删除链表的倒数第 N 个结点

力扣19

方法一:暴力解法。先遍历一遍链表得到链表长度 L ,第二遍指针遍历到第 L-n 个节点,下一个节点就是要删除的节点,执行删除操作即可。

# 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: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy_head = ListNode(0, head)
        L = 0
        cur = head
        while cur:
            L += 1
            cur = cur.next
        cur = dummy_head
        for _ in range(L - n):
            cur = cur.next
        cur.next = cur.next.next
        return dummy_head.next

时间复杂度:O(L);空间复杂度:O(1)。

方法二:。先依次入栈,弹出的第 n 个节点就是要删除的节点,此时栈顶元素就是待删除节点的前一个节点,执行删除操作即可。

# 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: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy_head = ListNode(0, head)
        stack = []
        cur = dummy_head
        while cur: #依次入栈,虚拟头结点也要入栈,方便处理头结点
            stack.append(cur)
            cur = cur.next
        for _ in range(n): #弹出n个节点
            stack.pop()
        prev = stack[-1] #得到栈顶元素即待删除节点的前一个节点
        prev.next = prev.next.next
        return dummy_head.next

时间复杂度:O(L);空间复杂度:O(L)(栈的开销)。

方法三:双指针法(使用一趟扫描实现)。让 fast 指向头结点,先移动 n 步,slow 指向虚拟头结点,此时 fast 比 slow 超前 n+1 个节点,然后 fast 和 slow 同时移动,直到 fast 指向链表末尾,此时 slow 指向待删除节点的前一个节点,执行删除操作即可。

# 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: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy_head = ListNode(0, head)
        slow, fast = dummy_head, head
        for _ in range(n): #初始定义就差一步,多走n步即可方便删除操作
            fast = fast.next
        while fast:
            slow = slow.next
            fast = fast.next
        slow.next = slow.next.next
        return dummy_head.next

时间复杂度:O(L);空间复杂度:O(1)。

相交链表

力扣160

求相交起始节点的指针,交点不是数值相等,而是指针相等。链表A长度为m,链表B长度为n。

方法一:暴力解法。把链表A的每个节点都加入哈希集合中,然后遍历链表B,判断节点是否在哈希集合中。

# 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) -> Optional[ListNode]:
        hashtable = set()
        curA, curB = headA, headB
        while curA: #链表 A 全部加入哈希集合
            hashtable.add(curA)
            curA = curA.next
        while curB: #遍历链表 B 判断节点是否在哈希集合中
            if curB in hashtable:
                return curB
            curB = curB.next
        return None

时间复杂度:O(m+n);空间复杂度:O(m)。

方法二:。由于相交后的节点都相同,所以从后往前找不同即可。

# 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) -> Optional[ListNode]:
        la, lb = [], []
        curA, curB = headA, headB
        while curA: #链表 A 全部入栈
            la.append(curA)
            curA = curA.next
        while curB: #链表 B 全部入栈
            lb.append(curB)
            curB = curB.next
        ans = None
        i, j = len(la) - 1, len(lb) - 1
        while i >= 0 and j >= 0 and la[i] == lb[j]: #注意边界条件
            ans = la[i]
            i -= 1
            j -= 1
        return ans

时间复杂度:O(m+n);空间复杂度:O(m+n)。

方法三:双指针法。遍历两个链表得到长度差,将长链表对齐至短链表头结点的位置,同时向后移动,并且在过程中找到第一个相同的节点。

# 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) -> Optional[ListNode]:
        m, n = 0, 0
        curA, curB = headA, headB
        while curA:
            m += 1
            curA = curA.next
        while curB:
            n += 1
            curB = curB.next
        curA, curB = headA, headB
        if m > n: #固定 B 指向较长的链表
            m, n = n, m
            curA, curB = curB, curA
        for _ in range(n - m): #将长链表对齐至短链表
            curB = curB.next
        while curA:
            if curA == curB:
                return curA
            curA = curA.next
            curB = curB.next
        return curA

时间复杂度:O(m+n);空间复杂度:O(1)。

方法四:双指针法。指针A和指针B分别指向两个链表的头结点,同时移动两个指针,如果指针不为空则移到下一个节点,如果指针A为空则指向链表B的头结点,如果指针B为空则指向链表A的头结点,在此过程中,如果两个指针指向同一个节点或者都为空时,返回指向的节点。

证明:记相交后相同的节点长度为 c,链表A相交前节点数为 m-c,链表B相交前节点数为 n-c。

如果 m = n,m-c=n-c,相交时两个指针会同时走到交点,否则同时走到 None。

如果 m ≠ n,指针A先遍历完链表A,再遍历链表B直到交点时走过了 m+(n-c) 个节点,指针B先遍历完链表B,再遍历链表A直到交点时走过了 n+(m-c) 个节点,由于 m+(n-c) = n+(m-c),此时两个指针指向同一个节点,如果相交(c>0)指向要求的交点交点,如果不相交(c=0)指向 None 节点。

# 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) -> Optional[ListNode]:
        if (not headA) or (not headB):
            return None

        curA, curB = headA, headB
        while (curA != curB): #两个指针指向不同节点时继续按规则遍历
            curA = curA.next if curA else headB
            curB = curB.next if curB else headA
        return curA

时间复杂度:O(m+n);空间复杂度:O(1)。

环形链表 II

力扣142

方法一:哈希表暴力解法。遍历每个节点,如果不在哈希表中就添加,如果在就找到了环的入口。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        hashtable = set()
        cur = head
        while cur:
            if cur in hashtable:
                return cur
            hashtable.add(cur)
            cur =cur.next
        return cur

时间复杂度:O(n);空间复杂度:O(n)。

方法二:双指针法。分两步,第一步判断是否有环,第二步如果有环如何找到环的入口。

是否有环?方法:分别定义 fast 和 slow 指针,从头结点出发,fast 指针每次移动两个节点,slow 指针每次移动一个节点,如果 fast 和 slow 指针在途中相遇 ,说明这个链表有环。证明:fast 指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇。fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

如果有环如何找到环的入口?方法:从头结点和相遇节点开始同时遍历,一次一个节点,再次相遇的节点即为入环点。

证明:(包含左不包含右)假设从头结点到环形入口节点的节点数为x,环形入口节点到fast指针与slow指针相遇节点的节点数为y,从相遇节点再到环形入口节点的节点数为 z,也就是说环的长度L=y+z。

当slow指针刚进入环时,要走 L 步才能走完一圈,slow指针刚进入环时fast指针一定已经在环内了,且两个指针最多差L-1步,以slow指针为参考系,fast指针在以每次一节点的速度靠近slow指针,最多需要L-1次操作就能追上slow指针,所以两个指针相遇时slow指针一定在第一圈,一共只走了x+y步。

两个指针相遇时,slow指针走过节点数为x+y,fast指针走过节点数为x+y+nL,时间相同可以得到(x + y) * 2 = x + y + n * (y + z),化简得到x = (n - 1) * (y + z) + z = (n - 1) * L + z,于是可知再额外使用一个指针p指向头结点,与slow指针同时移动,都是每次一个节点,当slow多转了 n-1 圈时,两个指针会在入环节点相遇。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        fast, slow = head, head
        while fast and fast.next:
            # fast 一次移动两个节点,如果有 None,说明无环结构
            fast = fast.next.next
            slow = slow.next
            if fast == slow: #指针相遇,说明存在环结构
                p = head
                while p != slow: #再次寻找相遇节点,此相遇节点即为入环点
                    p = p.next
                    slow = slow.next
                return p
        return None

时间复杂度:O(n+n) → O(n);空间复杂度:O(1)。

学习自【代码随想录】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值