LeetCode链表

LeetCode链表

Leetcode链表刷题记录

链表性质

  • 每个节点包含部分:数据域存储数据;指针域指向下一个节点的地址
  • 内存不连续
  • 链表的入口节点:head
  • 复杂度:增删复杂度为O(1),索引/查询1 元素复杂度为O(n)

构造链表

struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

如果不写构造函数,就无法直接在创建节点的时候赋值

class ListNode:
    def __init__(self, val, next=None): #定义构造函数
        self.val = val
        self.next = next

删除

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # 先处理head
        while head != None and head.val == val:
            head = head.next
        if head == None:
            return head
        pre = head
        while pre.next != None:
            if pre.next.val == val:
                pre.next = pre.next.next
                # pre.next.next 有可能是None
            else:
                pre = pre.next
        return head

最开始写的版本,太麻烦了,其实可以建一个虚拟节点,这样处理head的问题和处理后面节点的问题可以统一起来

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        if head == None:
            return head
        virtualhead = ListNode(0, next = head)
        pre = virtualhead
        while pre.next != None:
            if pre.next.val == val:
                pre.next = pre.next.next
            else:
                pre = pre.next
        return virtualhead.next

注意点:

  • if pre.next.val == val: 之后pre不直接更新为pre.next,需要经过判断pre.next != None
  • pre一直是需要更改的前一个节点
  • 删除要找的一定是前一个节点,失去对前一个节点的记录,就不能删除
  • 遍历的时候要定义一个临时指针,不能改变头节点

递归的修改

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        if head == None:
            return head
        head.next = self.removeElements(head.next, val)
        if head.val == val:
            head = head.next
        return head

注意:

  • head.next = self.removeElements(head.next, val) 是在head.val == val:之前的,因为head.val == val:可能会删除当前节点,如果在此之前不计算好当前节点的next,删除了之后再计算next就可能会解析空指针;而且这样先删除再计算next也不符合程序逻辑

自定义链表

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


class MyLinkedList:

    def __init__(self):
        self.head = None

    def get(self, index: int) -> int:
        if self.head == None:
            return -1
        count = 0
        tmp = self.head 
        while tmp != None:
            if count == index:
                return tmp.val
            tmp = tmp.next
            count += 1
        return -1
        

    def addAtHead(self, val: int) -> None:
        newhead = ListNode(val, next = self.head)
        self.head = newhead
        
        

    def addAtTail(self, val: int) -> None:
        newtail = ListNode(val)
        if self.head == None:
            self.head = newtail
        else:
            tmp = self.head
            while tmp.next != None:
                tmp = tmp.next
            tmp.next = newtail
        

    def addAtIndex(self, index: int, val: int) -> None:
        if index == 0:
            self.addAtHead(val)
            return
        if self.head == None:
            return
        newnode = ListNode(val)
        tmp = self.head
        count = 0
        while tmp != None:
            if count  == index - 1: #找index前一个节点
                newnode.next = tmp.next
                tmp.next = newnode
                return
            tmp = tmp.next
            count += 1
        if count == index:
            self.addAtTail(val)

        

    def deleteAtIndex(self, index: int) -> None:
        virtualhead = ListNode(next = self.head)
        pre = virtualhead
        count = 0
        while pre.next !=  None:
            if count == index:
                pre.next = pre.next.next
                break
            count += 1
            pre = pre.next
        self.head = virtualhead.next

注意:

  • 插入节点时一定是先改newnode.next 再修改tmp.next 指向node
  • 插入/删除 节点时,tmp指向的一定是index的前一个节点

反转列表

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head == None:
            return head
        pre = None #初始化为空指针, 因为头变成尾
        cur = head
        while cur != None:
            nex = cur.next
            cur.next = pre
            pre = cur
            cur = nex
            # 交换顺序
        return pre

递归的写法

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head == None or head.next == None:
            return head
        newhead = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return newhead

注意边界条件的的写法:if head == None or head.next == None:因为当链表为空或者链表只有一个元素的时候都不需要反转处理

节点交换

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head == None or head.next == None:
            return head
        pre = head
        cur = head.next
        newhead = head.next
        while cur != None:
            pre.next = cur.next
            cur.next = pre
            if pre.next != None and pre.next.next != None: 
                # 存在下一对节点
                cur = pre.next
                pre.next = pre.next.next
                pre = cur
                cur = cur.next
            else:
                break
        return newhead

更清晰的思路,每次指针都指向要处理的那对的前驱节点;加入虚拟头节点,这样就不用单独处理头

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        virtualhead = ListNode(next = head)
        pre = virtualhead
        while pre.next != None and pre.next.next != None:
            # 注意判断顺序,利用and的短路性,避免解引用空指针
            ptr1 = pre.next
            ptr2 = pre.next.next
            	# 节点1和节点2为互相要交换的节点
            ptr1.next = ptr2.next
            ptr2.next = ptr1
            pre.next = ptr2
            pre = ptr1
        return virtualhead.next

递归解法

def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head == None or head.next == None:
            # 无节点/只有1个节点不需要交换
            return head
        nex = head.next.next
        nex = self.swapPairs(nex)
        # 交换好后面
        newhead = head.next
        head.next.next = head
        head.next = nex
        return newhead

删除倒数第N个节点

双指针解法运用:本题的关键在于找到什么时候是倒数第N个节点,第一种直白的想法是我先遍历一遍链表,算出总长度,这样我就知道倒数第N个节点的索引,然后再遍历一遍,但这样显然太浪费了。由此想到双指针的解法,块指针比慢指针块n个节点,这样当快指针走到链表的尾部的时候,慢指针刚好指向要被删除的节点

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        virtualhead = ListNode(next = head)
        slow = virtualhead
        fast = virtualhead
        n += 1 # 找前驱,方便删除
        while fast != None:
            if n > 0:
                n -= 1
            else:
                slow = slow.next
            fast = fast.next
        if n > 0:
            return virtualhead.next
        slow.next = slow.next.next
        return virtualhead.next

寻找交点

通过求链表长度的差值来对齐两个链表

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        if headA == None or headB == None:
            return None
        ptrA = headA
        ptrB = headB
        lenA = 0
        lenB = 0
        while ptrA != None:
            lenA += 1
            ptrA = ptrA.next
        while ptrB != None:
            lenB += 1
            ptrB = ptrB.next
        ptrA = headA
        ptrB = headB
        print(lenA, lenB)
        if lenA < lenB :
            for _ in range(lenB - lenA):
                ptrB = ptrB.next
        else:
            bias = lenA - lenB
            for _ in range(lenA - lenB):
                ptrA = ptrA.next
        while ptrA != ptrB:
            # 不用担心链表尾部,A B会同时到达链表尾部
            ptrA = ptrA.next
            ptrB = ptrB.next
        return ptrA

补充一种解法,因为两个相交的链表有个性质,就是交点之后的路径是完全一样的(等长),交点之前的路径不同,在第一种解法里,我们做的无非是把两个链表对齐,让两个指针对齐,然后寻找交点,我们也可以用相交链表本身的性质来保证这一点

如果指针A先走A链表,再走B链表,指针B先走B链表,再走A链表,它们走路的速度一样,如果链表相交,走到交点处它们刚好相遇,如果不想交,它们不相等,各自走到链表末尾,最后都等于None,与我们要返回的值一样

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        ptrA = headA
        ptrB = headB
        while ptrA != ptrB:
            if ptrA == None:
                #走到A得末尾,从B的开头重新开始走
                ptrA = headB
            else:
                ptrA = ptrA.next
            if ptrB == None:
                ptrB = headA
            else:
                ptrB = ptrB.next
        return ptrA

环形链表

  • 定义快慢指针fast和slow slow走一步,fast走两步
  • 如果有环,fast和slow一定会相遇,因为fast和slow总会进入环中,一旦进入环,fast相当于比slow快一步,在追赶slow(操场上赛跑,只要是环形跑道,两个人有速度差,总有相遇的一天)
  • 如何寻找入口
    1. 快指针与慢指针相遇的时候慢指针一定没有走完环的第一圈
      直观理解:A的跑步速度是B的一倍,B绕操场跑一圈A可以跑两圈,所以如果A B同时出发,那么A必然在B刚好到达终点的时候追上B,但现在A抢跑(快指针比慢指针先入环),所以A一定在B第一圈到达终点前追上B
      数学证明:假设当慢指针到达环入口的时候,快指针离环入口有 K K K个节点的距离,环的长度为 c c c,所以快指针为了追上慢指针还需要走 c − k c-k ck步,设两个指针经过 t t t步(可以理解为单位时间)相遇, 2 t − t = c − k 2t - t = c - k 2tt=ck 得到 t = c − k t = c-k t=ck,所以此时慢指针距离环的入口为 c − k ≤ c c-k \leq c ckc慢指针走过的距离小于一个环长,慢指针在重新到达入口循环之前就和快指针相遇了
    2. 假设起点到环入口的距离为 x x x 我们要求解的就是 x x x 假设两者相遇时到环入口的距离为 y y y(由于1得 y = c − k y = c-k y=ck) 根据1,慢指针走过的距离为 x + y x+y x+y,快指针走过的距离为 x + y + n c x+y+nc x+y+nc 根据快慢指针速度关系,得到 2 × ( x + y ) = x + y + n c 2 \times (x + y) = x + y + nc 2×(x+y)=x+y+nc 改写成x的表达式 x = n c − y = n c − ( c − k ) = ( n − 1 ) c + k x = nc - y = nc - (c-k) = (n-1)c + k x=ncy=nc(ck)=(n1)c+k 这个表达式的含义是什么?先假设n=1,那么 x = k x=k x=k,就是说快慢指针的相遇点到入环点的距离和头节点到入环点的距离是一样的,如果 n ≠ 1 n \neq 1 n=1,那么头节点到入环点的距离,是快慢节点相遇点到入环点的距离加上环长的 n − 1 n-1 n1倍,所以如果有一个指针在快慢指针相遇的时候从头节点出发,快慢指针和新指针都以每次一个节点的速度移动,最终它们必定在环的入口相遇
class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        fast = head
        slow = head
        while fast != None and fast.next != None:
            slow = slow.next
            fast = fast.next.next #前进两步
            if slow == fast:
                break
        if slow != fast or fast == None or fast.next == None:
            return None
        new = head
        while new != slow:
            new = new.next
            slow = slow.next
        return new

总结

链表总结


  1. 我把查询定义为查找一个元素在不在链表里,索引定义为知道在第几个,得到这个元素的内容 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值