数据结构之链表实现

  本文基于Leetcode上Top Interview Questions、Top 100 Liked Questions中的链表部分和剑指offer上的链表算法题进行总结,同时大家也可以参考这篇博客,整理的很不错。

          • 1. Delete Node in a Linked List
          • 2. Linked List Cycle
          • 3. Linked List Cycle 2
          • 4. Merge Two Sorted Lists
          • 5. Sort List
          • 6. Merge K Sorted Linked List
          • 7. Odd Even Linked List
          • 8. Palindrome Linked List
          • 9. Remove Kth Node From End of List
          • 10. Reverse Linked List
          • 11. Reverse Nodes in k-Group

1. Delete Node in a Linked List

  问题描述:删除链表中的结点(已经定位到该结点),该结点不在尾结点,要求in-place操作。
  思路:一般的删除链表结点是先定位到该结点的前一个结点,通过指针删除,这里既然已经定位到该结点,且不是尾结点,就可以将下一结点的信息赋值给当前结点,然后删除下一个结点。

node.val = node.next.val
node.next = node.next.next
2. Linked List Cycle

  问题描述:判断链表是否有环
  思路:方法1.直接利用python中的list,将结点循环添加到list中,如果存在重复则有环,不过这种方法需要额外的O(n)空间,且容易出现超时;方法2. 利用快慢指针,一个在前一个在后,如果两者相遇则链表有环。

def hasCycle(head):
    if head is None:
        return False

    fast = head
    slow = head
    while fast and fast.next:   # 利用前面的指针判断 fast.next 和 fast.next.next
        fast = fast.next.next
        slow = slow.next
        if fast == slow:
            return True
    return False
3. Linked List Cycle 2

  问题描述:判断链表是否有环,有环则找出环的起点
  思路:方法1.直接利用python中的list,循环将结点添加到list中,如果存在重复则有环,第一个重复的元素则为环的起点,需要额外的O(n)空间,且容易出现超时;方法2.快慢指针,具体分析如下:
  假设链表head到环的起点距离为L1,环的长度为C,快慢指针在环中M点相遇,M点距离环起点L2距离。
  利用快慢指针,一个在前一个在后,快指针此时前进了L1 + L2 + n * C,慢指针前进了L1 + L2,根据相遇条件,2 * (L1 + L2) = L1 + L2 + n * C,解得 L1 = (n - 1) * C + (C - L2),如果忽略(n-1)* C这一项,则L1 = C- L2,即L1的长度为环的长度减去环的起点到M点的距离。再利用一个慢指针从头结点开始出发,相遇点处的指针也继续向前走,则一定会在环的起点相遇。

def detectCycle(head):
    if head is None:
        return None

    slow1 = head
    fast = head
    slow2 = head

    while fast and fast.next:     # 利用快慢指针向前
        fast = fast.next.next
        slow1 = slow1.next
        if fast == slow1:
            break
    else:               # 注意这里的写法,当不相等的时候,直接返回None
        return None

    while fast != slow2:    # 从头开始的慢指针,与之前的指针相遇时,就到达环开始的地方
        slow2 = slow2.next
        fast = fast.next
    return fast
4. Merge Two Sorted Lists

  问题描述:将两个排好序的列表进行合并
  思路:利用一个新链表和两个指针,根据已排好序的链表中首结点的值大小将链表头结点结点添加到新的链表中,并向后移动指针,注意存在两个链表长度不同的情况。

def mergeTwoLists(l1, l2):
    p = ListNode(0)
    dummy = p

    while l1 and l2:
        if l1.val > l2.val:
            p.next = l2
            l2= l2.next
        else:
            p.next = l1
            l1 = l1.next
        p = p.next

    if l1:            # 当第一个链表较长时
        p.next = l1
    if l2:
        p.next = l2    
    return dummy.next
5. Sort List

  问题描述:链表排序,要求时间复杂度O(n log n)和O(1)空间复杂度
  思路:归并排序,1.先找到列表的中间节点;2.再对左右节点进行分治;3.将左右节点进行合并排序,其中第三步的方法就是上一个题目。

def sortList(head):
    if head is None or head.next is None:
        return head

    # split the Linklist at point p
    p = None
    slow = head
    fast = head
    while fast and fast.next:
        p = slow
        slow = slow.next
        fast = fast.next.next

    left = head
    right = p.next
    p.next = None

    # iteration
    left = self.sortList(left)
    right = self.sortList(right)

    # MergeSort
    return self.merge(left,right)
6. Merge K Sorted Linked List

  问题描述:将K个链表进行合并
  思路:方法1. 利用第4个题目中的方式,与K个链表进行循环合并。

def mergeKLists(lists):
    if len(lists) == 0:
        return None
    if len(lists) == 1:
        return lists[0]

    mergelist = None
    for i in range(len(lists)):
        mergelist = mergeTwoLists(mergelist, lists[i])
    return mergelist
7. Odd Even Linked List

  问题描述:将链表中偶数打印到奇数后面
  思路:方法1. 直接利用python中的list,将结点加入list并排序,然后再根据排序的结果新建链表;方法2. 利用两个指针,不断向前移动,时间复杂度为O(n)。

def oddEvenList(head):

    if head is None:
        return None
    #  借助了两个变量,其中 oddhead 和 evenhead 是分别作为奇数和偶数结点的起点一直保持不动,在最后的时候append
    oddhead = odd = head
    evenhead = even = head.next   # 这里 head.next is None 的情况会在下面的循环语句判断
    while even and even.next:
        odd.next = even.next
        odd = odd.next
        even.next = odd.next
        even = even.next
    odd.next = evenhead   # 将偶数结点添加到奇数结点后面
    return oddhead
8. Palindrome Linked List

  问题描述:判断链表是否对称(回文链表)
  思路:1. 利用快慢指针,找到中间节点;2. 将后面的链表进行链表反转;3. 将反转的链表与链表的前半部分进行比较,如果相同则是对称链表。

def isPalindrome(head):

    if head is None:
        return True

    fast = head
    slow = head

    while fast and fast.next:   # 找到中间点位置
        fast = fast.next.next
        slow = slow.next

    p_head = slow    # 偶数个节点时,中间位置就是下一段的开始节点,并进行链表反转
    p = None
    q = None
    while p_head:
        q = p_head.next
        p_head.next = p
        p = p_head
        p_head = q

    while p:   # 将反转得到的新链表和原来的链表前半部分进行比较
        if head.val != p.val:
            return False
        head = head.next
        p = p.next
    return True
9. Remove Kth Node From End of List

  问题描述:移出链表中倒数第N个结点
  思路:利用两个指针,一个快一个慢,快指针向前移动到倒数第K个结点的位置,然后再和慢指针一起往前移动,直到找到倒数第(K+1)个结点。

def removeNthFromEnd(head, n):

    if head is None or n <= 0:    # 边界条件的判断
        return None

    fast = head
    slow = head  
    for _ in range(n):
        fast = fast.next
        if fast is None:
            return head.next

    while fast.next:
        fast = fast.next
        slow = slow.next

    slow.next = slow.next.next     # 移出结点
    return head    
10. Reverse Linked List

  问题描述:列表反转
  思路:创建新的链表,利用指针不断将链表的首节点添加到新的链表中。

def ReverseList(pHead):
    preNode = None
    nextNode = None

    while pHead:
        nextNode = pHead.next    # 链表的下一个结点
        pHead.next = preNode     # 将首节点逐渐添加到新的链表中
        preNode = pHead            
        pHead = nextNode
    return preNode
11. Reverse Nodes in k-Group

  问题描述:K个节点一组翻转链表
  思路:与上述题目不同,此题要求不能使用额外的空间,因此不能重建新的链表。一种思路是在遍历一次链表的时候,每K个节点做反转操作。具体的,如果得到链表反转的头结点位置和尾结点位置,则可以在这个区间进行类似“插入”的操作进行翻转。

def ReverseKnodes(phead, k):

    h = ListNode(-1)
    h.next = phead
    pre = h
    cur = phead

    while cur is not None:
        t = cur
        count = 1
        # find the start position and end position
        while count < k and t is not None:
            t = t.next
            count += 1
        if count == k and t is not None:
            for _ in range(k-1):
                lst = cur.next
                cur.next = lst.next
                lst.next = pre.next
                pre.next = lst

            # 借助于pre结点,进行类似“插入”的操作,完成翻转
            # 如 pre->1->2->3
            # 第一步: pre->2->1->3(将2 “插入” 到 pre 和 1 之间)
            # 第二步: pre->3->2->1(将3 “插入” 到 pre 和 2 之间)
            # 更新 pre 和 标记位 cur
            pre = cur
            cur = pre.next
        else:
            break

        return h.next

你可能感兴趣的:(链表,leetcode,剑指offer,面试,python,数据结构与算法(python))