【Leetcode】链表问题整理笔记



链表定义

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

节点删除

83. Remove Duplicates from Sorted List

n/a

Solution:

class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        if not head:
            return head
        curr = head
        while curr.next:
            cv = curr.val
            nv = curr.next.val
            if cv == nv:
                _next = curr.next
                nn_of_c = _next.next
                curr.next = nn_of_c
                del _next
            else:
                if not curr.next:  # [..., 5, 9, 9, 9] case
                    break
                curr = curr.next

        return head


82. Remove Duplicates from Sorted List II

n/a

Solution:

class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        if not head:
            return None
        dummy_head = ListNode(None)
        dummy_head.next = head
        curr = dummy_head
        dup_v = None
        while curr:
            # cv = curr.val
            _next = curr.next
            if _next and _next.next:
                nv = _next.val
                nnv = _next.next.val
            else:
                break

            dup_v = nv if nv == nnv else None

            # remove duplicate node
            if dup_v is not None:  # [0, 0, 0, 0]
                while _next:
                    if _next.val == dup_v:
                        curr.next = _next.next
                        del _next
                        _next = curr.next
                    else:
                        break
            else:
                curr = _next
            
        return dummy_head.next


203. Remove Linked List Elements - 删除符合指定值的所有节点

n/a

Solution:

使用 dummy node 的 solution 只要正确编写都不需要判断 head 是否为空。

class Solution:
    def rm_elems(self, head, key):
        dummy_head = ListNode(key-1)
        dummy_head.next = head
        
        curr = dummy_head
        while curr:  # make sure value of current node != key
            if curr.next:
                if curr.next.val == key:
                    rm_node = curr.next
                    curr.next = rm_node.next
                    del rm_node
                else:
                    curr = curr.next
            else:
                break
        return dummy_head.next

    def removeElements(self, head: ListNode, val: int) -> ListNode:
        return self.rm_elems(head, val)

灵活使用 dummy head 的思想,确保当前节点位置不用删除!
这一点应该是这个 solution 比大部分人都快的原因!

n/a


237. Delete Node in a Linked List - 删除给定的链表节点

n/a
n/a

Solution:

这道题在我看过之前还在面试的时候被问过。当时觉得很懵逼 ?。
后来没过几天就刚好在《剑指 Offer》看到这个问题,才搞懂面试官的意思。

或许是太过奇淫技巧,所以这个问题被踩的次数远远大于点赞的次数,思路还是很不错的。
可以学学扩展扩展思维,但是实际上不太可能这么操作 – 毕竟尾节点你都没办法删掉

class Solution:
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void Do not return anything, modify node in-place instead.
        """
        if node is None:
            return
        
        if node.next:  # not tail node
            _next = node.next
            node.val = _next.val
            node.next = _next.next
            del _next
            return
        
        return

n/a



链表翻转

206. Reverse Linked List – 翻转整个链表

n/a

Solution:

分析实现过程见注释。

class Solution:
    def recursive_reverse_list(self, dst, src):
        # 8->None => 8->None
        # 3->7->None => 7->3->None
        #    3 \ 7->None
        #        7->3 \ None
        #            None? tail->None, return head
        # 1->2->3->4->None
        #   1 \ 2->3->4->None
        #      2->1 \ 3->4->None
        #            3->2->1 \ 4->None
        #                      4->3->2->1 \ None
        #                                  None? tail->None, return head
        if src is None:  # head
            return dst
        else:
            n_src = src.next
            src.next = dst
            return self.recursive_reverse_list(src, n_src)
            

    def iterative_reverse_list(self, node):
        pass

    def reverseList(self, head: ListNode) -> ListNode:
        if not head:
            return None
        if not head.next:
            return head

        if True:
            tail = head
            new_head = self.recursive_reverse_list(head, head.next)
            tail.next = None
        else:
            new_head = self.iterative_reverse_list(head)

        return new_head

92. Reverse Linked List II – 翻转链表内部分连续节点

n/a

Solution:

这道题在上一道题之后做的。而且上一道题昨天做的,竟然给忘记了。
原本还是一开始就想用递归实现,但是因为忘了昨天实现过了整个链表翻转,倒是一时没想出来,最后只能使用其它方案实现。
如果早点发现原来实现过的链表翻转,那么在原来的基础上修改应该是可以比较快实现的。

不过总的来说,在纸上画画两下之后,还是满足了 Do it in one-pass.

class Solution:
    def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
        new_head = None
        new_tail = None
        dummy_head = ListNode(None)
        dummy_head.next = head
        curr = dummy_head

        # find new_head by use m
        for _ in range(m-1):
            curr = curr.next
        tail_of_head = curr
        new_head = curr.next
        
        curr = curr.next  # move to 'n'-place
        # find new_tail by use n
        for _ in range(n-m):  # take care about border
            curr = curr.next
        new_tail = curr.next  # curr at 'm'-place
        curr.next = None

        # reverse
        while new_head:
            head = new_head
            new_head = new_head.next
            head.next = new_tail
            new_tail = head
            
        tail_of_head.next = new_tail
        return dummy_head.next

n/a



判断链表环路

141. Linked List Cycle

n/a
n/a
n/a

Solution:

Approach 2: Two Pointers
Intuition

Imagine two runners running on a track at different speed. What happens when the track is actually a circle?

Algorithm

The space complexity can be reduced to O(1)O(1) by considering two pointers at different speed - a slow pointer and a fast pointer. The slow pointer moves one step at a time while the fast pointer moves two steps at a time.

If there is no cycle in the list, the fast pointer will eventually reach the end and we can return false in this case.

Now consider a cyclic list and imagine the slow and fast pointers are two runners racing around a circle track. The fast runner will eventually meet the slow runner. Why? Consider this case (we name it case A) - The fast runner is just one step behind the slow runner. In the next iteration, they both increment one and two steps respectively and meet each other.

How about other cases? For example, we have not considered cases where the fast runner is two or three steps behind the slow runner yet. This is simple, because in the next or next’s next iteration, this case will be reduced to case A mentioned above.

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: 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

Complexity analysis

  • Time complexity : O ( n ) O(n) O(n). Let us denote nn as the total number of nodes in the linked list. To analyze its time complexity, we consider the following two cases separately.

    • List has no cycle:
      The fast pointer reaches the end first and the run time depends on the list’s length, which is O ( n ) O(n) O(n).

    • List has a cycle:
      We break down the movement of the slow pointer into two steps, the non-cyclic part and the cyclic part:

      1. The slow pointer takes “non-cyclic length” steps to enter the cycle. At this point, the fast pointer has already reached the cycle. Number of iterations \text{Number of iterations} Number of iterations = non-cyclic length \text{non-cyclic length} non-cyclic length = N N N
      2. Both pointers are now in the cycle. Consider two runners running in a cycle - the fast runner moves 2 steps while the slow runner moves 1 steps at a time. Since the speed difference is 1, it takes distance between the 2 runners difference of speed \dfrac{\text{distance between the 2 runners}}{\text{difference of speed}} difference of speeddistance between the 2 runners loops for the fast runner to catch up with the slow runner. As the distance is at most “ cyclic length K \text{cyclic length K} cyclic length K” and the speed difference is 1, we conclude that
        Number of iterations = almost " cyclic length K " \text{Number of iterations} = \text{almost}"\text{cyclic length K}" Number of iterations=almost"cyclic length K".

    Therefore, the worst case time complexity is O ( N + K ) O(N+K) O(N+K), which is O ( n ) O(n) O(n).

  • Space complexity : O ( 1 ) O(1) O(1). We only use two nodes (slow and fast) so the space complexity is O ( 1 ) O(1) O(1).



61. Rotate List

n/a

Solution:

先做过 189. Rotate Array 的话,对于比如 k > 链表长度 k > \text{链表长度} k>链表长度 这样的情况会提前考虑到!所以首先遍历一遍链表获取长度。

因为链表不需要像数组那样一个一个移动值,所以在这个问题上,链表的操作理论上会更快。

class Solution:
    def get_len(self, head):
        _len = 0
        curr = head
        while curr:
            _len += 1
            curr = curr.next
        return _len

    def rot_k(self, head, k):
        if k == 0:
            return head

        p_slow = head
        p_fast = head
        for _ in range(k):
            p_fast = p_fast.next

        while p_fast.next:
            p_fast = p_fast.next
            p_slow = p_slow.next
        p_fast.next = head
        new_head = p_slow.next
        p_slow.next = None
        return new_head

    def rotateRight(self, head: ListNode, k: int) -> ListNode:
        if head == None:
            return None

        k = k % self.get_len(head)
        return self.rot_k(head, k)



876. Middle of the Linked List - 获取链表中点

n/a
Given a non-empty, singly linked list with head node head, return a middle node of linked list.

If there are two middle nodes, return the second middle node.

Example 1:

Input: [1,2,3,4,5]
Output: Node 3 from this list (Serialization: [3,4,5])
The returned node has value 3. (The judge’s serialization of this node is [3,4,5]).
Note that we returned a ListNode object ans, such that:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, and ans.next.next.next = NULL.

Example 2:

Input: [1,2,3,4,5,6]
Output: Node 4 from this list (Serialization: [4,5,6])
Since the list has two middle nodes with values 3 and 4, we return the second one.

n/a

Solution:

class Solution:
    def middleNode(self, head: ListNode) -> ListNode:
        if head.next is None:
            return head
        
        # O-head-one-> O-two-> None
        fast = head.next.next
        slow = head.next
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow

n/a



Reference



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值