【Leetcode】代码随想录Day3|链表1.0

链表基础

  • 单链表:data, next pointer
  • 双链表:previous pointer, data, next pointer
  • 循环链表:首尾相连的链表
  • 链表储存在空间上可不连续,使用pointer串联,分配机制取决于操作系统的内存管理
  • 链表节点的定义需要写构建函数以方便initialization时同时赋值data (python不需要担心这一点)
class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next
  • 链表的【添加】与【删除本节点】均通过修改pointer实现,O(1)
  • 链表的【查找】O(n),如需查找到某个节点再删除,则O(n)
  • 与数组对比
插入/删除查询适用
数组O(n) 【长度固定需要重新定义一个】O(1)数据量固定,频繁查询,较少增删
链表O(1)O(n)数据量不固定,频繁增删,较少查询

203 移除链表元素

初始思路
为解决head本身可能需要被移除的情况,在head前加一个dummy new_head,new_head.next = head。从new_head开始,用ptr指向当前查看的ListNode,只要ptr.next还存在ListNode x,就查看x.val值,如果需要移除,则直接将ptr.next指向x.next,删除x;如果不需要移除,则移动ptr,指向ptr.next。

    def removeElements(self, head, val):
        new_head = ListNode(0,head)
        ptr = new_head

        while ptr.next is not None:
            if ptr.next.val == val:
                ptr.next = ptr.next.next
            else:
                ptr = ptr.next
        
        return new_head.next

另一种思路:
单独处理删除head的状况,将head指向head.next。

Complexity
time: O(n)
space: O(1)

707 设计链表

初始思路
增删操作本身或在抬头和末尾处增删都较为容易,改变pointer的指向即可。
而get(), addAtIndex(), deleteAtIndex(),需要先找到指定index。
继续使用dummy_head,但没有使用size,开始想以逐一递减index的while循环来找到位置,可以自然在:

  • index递减到0先于出现None:找到index
  • 出现None先于递减到0: invalid index

问题:
边界需要仔细处理,需要很多+1和-1,容易混乱。并且判定ndex invalid时,也需要遍历整个linked list,计算成本过大。

优解参考:

class MyLinkedList(object):

    def __init__(self):
        self.dummy_head = ListNode()
        self.size = 0  # 更快捷的判断invalid index,并且可以追踪长度
        

    def get(self, index):
        # 注意:这里index只能小于self.size,因为index的范围
        if index < 0 or index >= self.size:
            return -1
       
        ptr = self.dummy_head.next
        for i in range(index):
            ptr = ptr.next
        return ptr.val
        
    def addAtHead(self, val):
        old_head = self.dummy_head.next
        new_head = ListNode(val, old_head)
        self.dummy_head.next = new_head
        self.size += 1
        
    def addAtTail(self, val):
        last = ListNode(val)
        ptr = self.dummy_head
        while ptr.next is not None:
            ptr = ptr.next
        ptr.next = last
        self.size += 1
        
    def addAtIndex(self, index, val):
        # 注意:这里index可以等于size,因为是在添加,可以添加在最后,即为新增的index=size
        if index < 0 or self.size < index:
            return
        
        just_before = self.dummy_head
        for i in range(index):
            if just_before.next is not None:
                just_before = just_before.next
        just_before.next = ListNode(val, just_before.next)
        self.size += 1

    def deleteAtIndex(self, index):
        # 注意:这里index只能小于self.size,因为只是在减少,index只能在原本的范围
        if index < 0 or self.size <= index:
            return
        
        just_before = self.dummy_head
        for i in range(index):
            if just_before.next is not None:
                just_before = just_before.next
        just_before.next = just_before.next.next
        self.size -= 1

反思及注意:

  1. 判断index的validity在get(), addAtIndex(), deleteAtIndex()中有所不同,addAtIndex()因为可能在最后多加一个,所以index可以等于current size,而其他两者还是只能在既有的范围内,必须小于size。
  2. 使用for loop正向循环搭配dummy head要简单清楚很多。
  3. 另有双链表的实现方法:size更有意义,取中点判断index从哪边 (head or tail) 遍历更近,就从哪边开始,节省算力。

Complexity
time: 涉及 index 的相关操作为 O(index), 其余为 O(1)
space: O(n)

206 反转链表

初始思路
遍历中逐个将pointer方向反转,在将ptr的next指向pre时,暂时将真正的ptr.next存储在tmp中。在实现的时候对于循环条件是否涉及.next纠结太久。
没有意识到的是,这俨然也属于双指针方法。

    def reverseList(self, head):
        pre = None
        ptr = head
        while ptr is not None:
            tmp = ptr.next
            ptr.next = pre

            pre = ptr
            ptr = tmp
            
        return pre

Complexity
time: O(n)
space: O(1)

代码随想录:
另一种方法: 递归

  1. 与双指针相同,从前向后递归
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        return self.reverse(head, None)
    def reverse(self, cur: ListNode, pre: ListNode) -> ListNode:
        if cur == None:
            return pre
        temp = cur.next
        cur.next = pre
        return self.reverse(temp, cur)

2.从后向前递归

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 边缘条件判断
        if head is None:
            return None
        if head.next is None:
            return head
        # 递归调用,反转第二个节点开始往后的链表
        last = reverseList(head.next)
        # 反转头节点与第二个节点的指向
        head.next.next = head
        # 此时的head节点为尾节点,next需要指向None
        head.next = None
        return last

Complexity
time: O(n), 要递归处理链表的每个节点
space: O(n), 递归调用了 n 层栈空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值