【刷题笔记day3】链表的基本操作

链表的基础知识

不是连续的内存空间,而是随机存储的。单链表每个节点包括:value存储内容,next指针指向下一个节点。双链表在单链表的基础上多了一个prev指针指向上一个节点。

class ListNode:
	def __init__(self, val=0, nxt = None):
		self.val = val
		self.next = nxt
		# self.prev = None
  • 访问:从head开始遍历直到找到目标元素。O(N)
  • 搜索:同样,遍历链表看是否存在目标。O(N)
  • 插入:将A节点的next指针(原来指向下一个节点,断开)指向B,B节点的next指向C节点。仅讨论插入这个操作的复杂度:O(1)
  • 删除:将A和B,B和C的next指针断开,将A的next指向C。O(1)

适合写入数据,读少写多的场合。

相关练习题

链表的存在意义在于各节点通过next指针相互联系,所以一旦节点没有被next指向,且自己的next也没有指向别人,它就被“删除”了。在C++中,需要我们手动释放内存,但在Java和Python中,我们只需要断开指针,剩下的交给程序完成就好了。

LC203 移除链表元素

删除链表中等于给定值 val 的所有节点。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6。 输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1。输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7。输出:[]

本题涉及的一个小技巧就是虚拟头节点,这样就可以让原头节点可以像处理链表中其他节点一样的方式来处理。

此外,我经常在while循环遍历链表中出错,纠结于是while cur还是while cur.next,现发现:在保证循环主体正确的情况下,观察第一次调用cur指针等号左边是cur还是cur.next又或者是cur.next.val,前两种情况循环条件设置为while cur,第三种为while cur.next。这是因为我们需要保证进入循环的时候循环变量不能是NoneType,否则就会报错:“NoneType doesn't have attribute val

class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        if not head:
            return 
        dummy = ListNode(next=head)
        cur = dummy
        while cur.next:
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
            
        return dummy.next

LC707 设计链表

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点

本题是一道设计数据结构类型的题目,需要完成链表类的构造以及一些成员方法。

遇到链表+索引的题目时,需要注意索引是0-index还是1-index,尤其是还要引入虚拟头节点的情况下,遍历链表时对于索引的计算需要格外细心。例如本题的关键在于addAtIndex, deleteAtIndex还有get这三个函数中的遍历范围。

本题题解来自于代码随想录,其使用了一种较为冷门的for ... else ...结构。意思就是只有for循环被正常、完整地执行,才会继续运行else里面的代码,否则不会运行。

# 单链表
class Node:
    
    def __init__(self, val):
        self.val = val
        self.next = None


class MyLinkedList:

    def __init__(self):
        self._head = Node(0)  # 虚拟头部节点
        self._count = 0  # 添加的节点数

    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. If the index is invalid, return -1.
        """
        if 0 <= index < self._count:
            node = self._head
            for _ in range(index + 1):
                node = node.next
            return node.val
        else:
            return -1

        
    def addAtHead(self, val: int) -> None:
        """
        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.
        """
        self.addAtIndex(0, val)

        
    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        self.addAtIndex(self._count, val)

        
    def addAtIndex(self, index: int, val: int) -> None:
        """
        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 < 0:
            index = 0
        elif index > self._count:
            return

        # 计数累加
        self._count += 1

        add_node = Node(val)
        prev_node, current_node = None, self._head
        for _ in range(index + 1):
            prev_node, current_node = current_node, current_node.next
        else:
            prev_node.next, add_node.next = add_node, current_node

            
    def deleteAtIndex(self, index: int) -> None:
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        if 0 <= index < self._count:
            # 计数-1
            self._count -= 1
            prev_node, current_node = None, self._head
            for _ in range(index + 1):
                prev_node, current_node = current_node, current_node.next
            else:
                prev_node.next, current_node.next = current_node.next, None

LC206 反转链表

反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

本题既可以通过递归来做,也可以用迭代法做

递归法

摘录于labuladong的递归魔法:反转单链表,其对于递归函数的理解讲解十分到位。

我们在敲递归的时候不要陷入这个误区:用大脑递归,否则很快就会真·绞尽脑汁也想不出来了。我们可以把递归函数看成黑盒子,只需要知道它实现了什么功能,返回了什么东西,就会好受很多。

class Solution:
	def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
		# base case
		if head == None or head.next == None:
			return head
			
		# 当链表递归反转之后,新的头结点是 last,而之前的 head 变成了最后一个节点,别忘了链表的末尾要指向 null:
		last = self.reverseList(head.next)
		head.next.next = head
		head.next = None
		return last

复杂度分析:挺显然的,对每个节点都要操作,所以时间复杂度是 O ( N ) O(N) O(N),但由于需要堆栈,所以空间复杂度是 O ( N ) O(N) O(N),而下文要提到的迭代法要更高效:相同的时间复杂度,空间复杂度仅为 O ( 1 ) O(1) O(1)

迭代法

在刚接触这道题时,由于不熟悉链表的特性,这个解法理解起来十分痛苦,好在经过打草稿以及复习之后还是能理解。

代码实际上很简洁,我们需要注意的是在对多个变量的指针进行操作的时候,变量的先后顺序,总而言之就是,要把某变量进行改变之前,必须确保该变量的价值已经被利用完。

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

拓展题 LC92 反转链表II

Given the head of a singly linked list and two integers left and right where left <= right, reverse the nodes of the list from position left to position right, and return the reversed list.

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def __init__(self):
        self.successor = None
    
    def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
        if left == 1:
            # base case
            # 反转从 head 节点开始到 right 的节点
            return self.reverseN(head, right)
        
        # 通过移动 head 指针,同时修改 left 和 right 的值,保证相对位置不变,直到base case
        head.next = self.reverseBetween(head.next, left - 1, right - 1)
        return head
    
    def reverseN(self, head, n):
    	# LC206 递归法模板稍加修改
    	# 需要记录后驱节点,因为 head 在反转后不一定是最后一个节点了
        if n == 1:
            self.successor = head.next
            return head
        
        last = self.reverseN(head.next, n - 1)
        head.next.next = head
        head.next = self.successor
        return last

你可能感兴趣的:(LeetCode刷题笔记)