算法入门很简单:链表题套路及精选题目

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 2 天,点击查看活动详情

1 链表介绍

链表(Linked List) :一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。

简单来说,「链表」 是实现线性表的链式存储结构的基础。存储模式如下:

在链表中,数据元素之间的逻辑关系是通过指针来间接反映的。逻辑上相邻的数据元素在物理地址上可能相邻,可也能不相邻。其在物理地址上的表现是随机的。

我们先来简单介绍一下链表结构的优缺点:

  • 优点:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比数组高(插入、移动、删除元素等)。
  • 缺点:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。

2 套路总结

链表操作套路:链表不仅仅是穿针引线,还有双指针,虚拟节点,迭代和递归。

3 精选题目

3.1 反转链表

Python 版本:

def reverseList(self, head):
    cur, prev = head, None
    while cur:
        cur.next, prev, cur = prev, cur, cur.next
    return prev

Go 语言版本:

func reverseList(head *ListNode) *ListNode {

    if head == nil {
        return nil
    }

    cur := head
    var pre *ListNode

    for cur != nil {

        tmp := cur.Next
        cur.Next = pre
        pre = cur
        cur = tmp
    }
    return pre
}

3.2 反转链表2

leetcode-cn.com/problems/re…

思路 1:迭代

  1. 使用两个指针 cur 和 pre 进行迭代。pre 指向 cur 前一个节点位置。初始时,pre 指向 Nonecur 指向 head

  2. 将 pre 和 cur 的前后指针进行交换,指针更替顺序为:

    使用 next 指针保存当前节点 cur 的后一个节点,即 next = cur.next

    断开当前节点 cur 的后一节点链接,将 cur 的 next 指针指向前一节点 pre,即 cur.next = pre

    pre 向前移动一步,移动到 cur 位置,即 pre = cur

    cur 向前移动一步,移动到之前 next 指针保存的位置,即 cur = next

  3. 继续执行第 2 步中的 1、2、3、4。

  4. 最后等到 cur 遍历到链表末尾,即 cur == None,时,pre 所在位置就是反转后链表的头节点,返回新的头节点 pre

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        pre = None
        cur = head
        while cur != None:
            next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        return pre

思路 2:递归

具体做法如下:

  • 首先定义递归函数含义为:将链表反转,并返回反转后的头节点。
  • 然后从 head.next 的位置开始调用递归函数,即将 head.next 为头节点的链表进行反转,并返回该链表的头节点。
  • 递归到链表的最后一个节点,将其作为最终的头节点,即为 new_head
  • 在每次递归函数返回的过程中,改变 head 和 head.next 的指向关系。也就是将 head.next 的next 指针先指向当前节点 head,即 head.next.next = head 
  • 然后让当前节点 head 的 next 指针指向 None,从而实现从链表尾部开始的局部反转。
  • 当递归从末尾开始顺着递归栈的退出,从而将整个链表进行反转。
  • 最后返回反转后的链表头节点 new_head
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if head == None or head.next == None:
            return head
        new_head = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return new_head

3.3 两数相加

def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:

    dummy = tail = ListNode(None)
    sum = 0
    while l1 or l2 or sum:
        sum += (l1.val if l1 else 0) + (l2.val if l2 else 0)
        tail.next = ListNode(sum % 10)
        tail = tail.next
        sum // 10
        l1 = l1.next if l1 else None
        l2 = l2.next if l2 else None
    return dummy.next

3.4 两两交换链表中的节点

Python 代码:

def swapPairs(self, head: ListNode) -> ListNode:
    # 申请一个虚节点头
    pre, pre.next = self, head

    # 空 或者 只剩下一个节点
    while pre.next and pre.next.next:
        a = pre.next
        b = a.next

        pre.next, b.next, a.next = b, a, b.next
        pre = a

    return pre.next

Go 代码:

func swapPairs(head *ListNode) *ListNode {
    if head == nil {
        return nil
    }
    if head.Next == nil {
        return head
    }

    var (
        dummy *ListNode = &ListNode{0, head}
        temp = dummy
        cur = dummy.Next
    )

    for cur != nil && cur.Next != nil {
        temp.Next = cur.Next
        cur.Next = cur.Next.Next
        temp.Next.Next = cur
        
        temp = cur
        cur = cur.Next
    }
    return dummy.Next
}

3.5 环形链表--判断是否有环

Go 代码:

func hasCycle(head *ListNode) bool {
    first, second := head, head
    for first != nil && first.Next != nil {
        first = first.Next.Next
        second = second.Next
        if first == second {
            return true
        }
    }
    return false
}
  1. 硬做
  2. Set
  3. 快慢指针

Python 代码:

def hasCycle(self, head):
    fast = slow = head
    while slow and fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            return True
    return False

3. 6. 环型链表2

3.7 K 个一组翻转链表

优先队列

class Solution:
    # 翻转一个子链表,并且返回新的头与尾
    def reverse(self, head: ListNode, tail: ListNode):
        prev = tail.next
        p = head
        while prev != tail:
            nex = p.next
            p.next = prev
            prev = p
            p = nex
        return tail, head

    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        hair = ListNode(0)
        hair.next = head
        pre = hair

        while head:
            tail = pre
            # 查看剩余部分长度是否大于等于 k
            for i in range(k):
                tail = tail.next
                if not tail:
                    return hair.next
            nex = tail.next
            head, tail = self.reverse(head, tail)
            # 把子链表重新接回原链表
            pre.next = head
            tail.next = nex
            pre = tail
            head = tail.next
        
        return hair.next

Go 代码:

func reverseKGroup(head *ListNode, k int) *ListNode {
    hair := &ListNode{Next: head}
    pre := hair

    for head != nil {
        tail := pre
        for i := 0; i < k; i++ {
            tail = tail.Next
            if tail == nil {
                return hair.Next
            }
        }
        nex := tail.Next
        head, tail = myReverse(head, tail)
        pre.Next = head
        tail.Next = nex
        pre = tail
        head = tail.Next
    }
    return hair.Next
}

func myReverse(head, tail *ListNode) (*ListNode, *ListNode) {
    prev := tail.Next
    p := head
    for prev != tail {
        nex := p.Next
        p.Next = prev
        prev = p
        p = nex
    }
    return tail, head
}

链表中删除重复项

从链表中移除一个重复的值,链表是有序的。在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

Go 语言实现如下:

func(list *List) RemoveDuplicate() {
  curr := list.head
  for curr != nil {
    if curr.next != nil && curr.value == curr.next.value {
      curr.next = curr.next.next
    } else {
      curr = curr.next
    } 
  }
}

算法分析:

该算法只用了一层循环,for 循环用于遍历列表。 每当有一个节点的值等于下一个节点的值时,该当前节点下一个将指向下一个节点的下一个。 这将从列表中删除下一个节点,算法复杂度为 O(n)

链表反转

将链表的内容以相反的顺序复制到另一个链表中。 如果原始链表包含顺序为 1,2,3,4 的元素,则新链表应包含顺序为 4,3,2,1 的元素。

func(list *List) CopyListReversed() *List {
  var tempNode, tempNode2 * Node
  curr := list.head
  for curr != nil {
    tempNode2 = &Node{curr.value, tempNode}
    curr = curr.next
    tempNode = tempNode2
  }
  
  ll2 := new(List)
  ll2.head = tempNode
  return ll2
}

遍历列表并将节点的值添加到新列表中。 由于列表是正向遍历的,并且每个节点的值都被添加到另一个列表中,所以形成的列表与给定列表相反.

链表对比

比较给定头指针的两个链表的值。

func (list *List) CompareList(ll *List) bool {
  return list.compareListUtil(list.head, ll.head)
}

func(list *List) compareListUtil(head1 *Node, head2 *Node) bool {
  if head1 == nil && head2 == nil {
    return true
  } else if (head1 == nil) || (head2 == nil) || (head1.value != head2.value) {
    return false
  } else {
    return list.compareListUtil(head1.next, head2.next)
  }
}
  • 列表是递归比较的。 此外,如果我们到达列表的末尾并且两个列表都是空的。 然后两个列表相等,因此返回 true
  • 列表是递归比较的。 如果列表中的任何一个为空或对应节点的值不相等,则此函数将返回 false。
  • 递归调用当前节点的下一个节点的比较列表函数。

猜你喜欢

转载自juejin.im/post/7126562443374886948
今日推荐