数据结构与算法--链表(三)

链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
在这里插入图片描述

https://leetcode-cn.com/tag/linked-list/

链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。

题型分类

聊过具体链表原理之后,我们要总结一下leetcode上面的关于链表的算法题,leetcode上边的题主要分两部分,一部分是本身的题,另一部分是来自剑指offer的题。在这里我们就不区分,只是根据题做下归类及解题套路梳理。

1.单链表处理问题

(1)O(1)时间删除指定结点
(2)链表的倒序打印(递归思路)
(3)倒数第n个结点(快慢指针思想)
(4)链表的翻转(全部和局部)

2.有环形链表处理

(1)判断是否有环
(2)判断环的入口
(3)判断环的长度

3.双链表关系问题

(1)两个链表是否有交叉
(2)交叉的具体位置
(3)公共串的长度等

核心技巧

1.添加哑巴节点(dummy node),减少不必要的判断
2.快慢指针及变形

a.固定步伐的两个指针
b.提前行走多少步的指针

3.环的循环利用
4.链条的递归
5.地址指针的妙用

典型例题及代码

1.O(1)时间复杂度删除指定结点
实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。时间复杂度O(1)。leetcode

class Solution(object):
    def deleteNode(self, node):
        """
        :type node: ListNode
        :rtype: void Do not return anything, modify node in-place instead.
        """
        node.val = node.next.val
        node.next = node.next.next

扩展问题
链表的查找?

2.快慢指针链表遍历
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。leetcode

class Solution(object):
    def getKthFromEnd(self, head, k):
        """
        :type head: ListNode
        :type k: int
        :rtype: ListNode
        """
        # 快慢指针 链表遍历
        fast = 0
        fast_step = head
        slow_step = head
        while fast_step:
            fast += 1
            fast_step = fast_step.next
            if fast>k:
                slow_step = slow_step.next
        return slow_step

扩展问题
快慢指针,可以先走多少步,也可以相同步伐;在链表中应用场景非常多。

3.链表的反转
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。leetcode

class Solution(object):
    # 迭代双指针反转
    def _reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        cur = head
        pre = None
        post = None
        while cur:
            post = cur.next
            cur.next = pre
            pre = cur
            cur = post
        return pre
    # 递归反转
    def reverseList(self, head):
        if not head or not head.next:
            return head
        node = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return node

扩展问题
以上两种反转方法的空间复杂度分别是多少?

4.环形链表问题
判断是否是环形链表,并确定环的开始位置。leetcode

class Solution(object):
    def detectCycle(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        slow = head
        fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if not fast:
                return None
            if fast == slow:
                slow = head
                while slow !=fast:
                    slow = slow.next
                    fast = fast.next
                return slow
        return None

扩展问题
获取环的长度?

5.交叉链表或公共链表问题
找到两个单链表相交的起始节点。leetcode

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
        tmpA = headA
        tmpB = headB
        count = 1
        while tmpA!=tmpB and count<=2:
            if tmpA and tmpA.next:
                tmpA = tmpA.next
            else:
                tmpA = headB
                count += 1
            if tmpB and tmpB.next:
                tmpB = tmpB.next
            else:
                tmpB = headA
        if count <=2:
            return tmpA
        else:
            return None

扩展问题
获取公共串的长度?

6.哑结点的使用
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。leetcode

class Solution(object):
    def _swapPairs(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        dummy = ListNode(-1)
        dummy.next = head
        prev_node = dummy

        while head and head.next:
            # 标记交换两节点
            first_node = head;
            second_node = head.next;

            # 交换操作
            prev_node.next = second_node
            first_node.next = second_node.next
            second_node.next = first_node

            # 起始位置重置
            prev_node = first_node
            head = first_node.next

        # 返回头结点
        return dummy.next

    def swapPairs(self, head):
        # 递归返回的标记
        if not head or not head.next:
            return head

        # 待调换的两个节点
        first_node = head
        second_node = head.next

        # 交换
        first_node.next  = self.swapPairs(second_node.next)
        second_node.next = first_node

        # 返回
        return second_node

扩展问题
无哑结点要怎么做?有的面试要求不得引入哑结点。

7.链表的设计
设计链表的实现。leetcode

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next, self.prev = None, None

class MyLinkedList:
    def __init__(self):
        self.size = 0
        # 双向链表
        self.head, self.tail = ListNode(0), ListNode(0) 
        self.head.next = self.tail
        self.tail.prev = self.head
        

    def get(self, index):
        """
        Get the value of the index-th node in the linked list. If the index is invalid, return -1.
        """
        # if index is invalid
        if index < 0 or index >= self.size:
            return -1
        
        # 根据链表大小和index,选择从头开始遍历还是从尾部开始遍历
        if index + 1 < self.size - index:
            curr = self.head
            for _ in range(index + 1):
                curr = curr.next
        else:
            curr = self.tail
            for _ in range(self.size - index):
                curr = curr.prev
                
        return curr.val
            

    def addAtHead(self, val):
        """
        Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
        """
        pred, succ = self.head, self.head.next
        
        self.size += 1
        to_add = ListNode(val)
        to_add.prev = pred
        to_add.next = succ
        pred.next = to_add
        succ.prev = to_add
        

    def addAtTail(self, val):
        """
        Append a node of value val to the last element of the linked list.
        """
        succ, pred = self.tail, self.tail.prev
        
        self.size += 1
        to_add = ListNode(val)
        to_add.prev = pred
        to_add.next = succ
        pred.next = to_add
        succ.prev = to_add
        

    def addAtIndex(self, index, val):
        """
        Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
        """
        # If index is greater than the length, 
        # the node will not be inserted.
        if index > self.size:
            return
        
        # [so weird] If index is negative, 
        # the node will be inserted at the head of the list.
        if index < 0:
            index = 0
        
        # find predecessor and successor of the node to be added
        if index < self.size - index:
            pred = self.head
            for _ in range(index):
                pred = pred.next
            succ = pred.next
        else:
            succ = self.tail
            for _ in range(self.size - index):
                succ = succ.prev
            pred = succ.prev
        
        # insertion itself
        self.size += 1
        to_add = ListNode(val)
        to_add.prev = pred
        to_add.next = succ
        pred.next = to_add
        succ.prev = to_add
        

    def deleteAtIndex(self, index):
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        # if the index is invalid, do nothing
        if index < 0 or index >= self.size:
            return
        
        # find predecessor and successor of the node to be deleted
        if index < self.size - index:
            pred = self.head
            for _ in range(index):
                pred = pred.next
            succ = pred.next.next
        else:
            succ = self.tail
            for _ in range(self.size - index - 1):
                succ = succ.prev
            pred = succ.prev.prev
            
        # delete pred.next 
        self.size -= 1
        pred.next = succ
        succ.prev = pred


# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

扩展问题
单双链表在java或python中哪些容器中有应用?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值