刷题 链表

链表

理论基础

1.基础

链表是一种通过指针串联在一起的线性结构,每一个节点是又两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。数组需要一块连续的内存空间来存储,对内存的要求比较高。而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用。
链接的入口点称为列表的头结点也就是head

2. 链表类型

  1. 单链表
    上图
  2. 双链表
    单链表中的节点只能指向节点的下一个节点。
    双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
    双链表 既可以向前查询也可以向后查询。
    如图所示:
  3. 循环链表
    它跟单链表唯一的区别就在尾结点。我们知道,单链表的尾结点指针指向空地址,表示这就是最后的结点了。而循环链表的尾结点指针是指向链表的头结点。循环链表可以用来解决约瑟夫环问题。

3. 链表的定义

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

4. 链表的操作

删除节点

删除D节点,如图所示:

只要将C节点的next指针 指向E节点就可以了。
D节点依然存留在内存里, 只不过是没有在这个链表里而已.在C++里最好是再手动释放这个D节点,释放这块内存。其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

添加节点

可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)

5. 性能分析

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

刷题

JZ24/LC206 反转链表

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
  1. 正规法

    首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
    然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
    为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
    接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
    最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param head ListNode类 
# @return ListNode类
#
class Solution:
    def ReverseList(self , head: ListNode) -> ListNode:
        # write code here
        if head is None or head.next is None:
            return head
        
        pre=None # 上一个节点
        while head:
            # 先用tmp保存phead下一个节点信息,保证单链表不会因为失去head的next
            # 就此断裂
            tmp=head.next
            # 保存完next就可以让head 的next指向pre
            head.next=pre
            # 让pre和head以此向后
            pre=head
            head=tmp
        return pre
  1. 递归法

    可以把问题划分为头结点与剩余结点


    地址: https://leetcode-cn.com/problems/reverse-linked-list/solution/shi-pin-jiang-jie-die-dai-he-di-gui-hen-hswxy/
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param head ListNode类 
# @return ListNode类
#
class Solution:
    def ReverseList(self , head: ListNode) -> ListNode:
        # write code here
        # 递归终止条件
        
        if head is None or head.next is None:
            return head
        #这个是递
        p=self.ReverseList(head.next)
        head.next.next=head
        head.next=None
        return p

JZ52 两个链表的第一个公共结点

使用两个指针N1,N2,一个从链表1的头节点开始遍历,我们记为N1,一个从链表2的头节点开始遍历,我们记为N2。
让N1和N2一起遍历,当N1先走完链表1的尽头(为null)的时候,则从链表2的头节点继续遍历,同样,如果N2先走完了链表2的尽头,则从链表1的头节点继续遍历,也就是说,N1和N2都会遍历链表1和链表2。
因为两个指针,同样的速度,走完同样长度(链表1+链表2),不管两条链表有无相同节点,都能够到达同时到达终点。
(N1最后肯定能到达链表2的终点,N2肯定能到达链表1的终点)。
所以,如何得到公共节点:
有公共节点的时候,N1和N2必会相遇,因为长度一样嘛,速度也一定,必会走到相同的地方的,所以当两者相等的时候,则会第一个公共的节点
无公共节点的时候,此时N1和N2则都会走到终点,那么他们此时都是null,所以也算是相等了。
下面看个动态图,可以更形象的表示这个过程~
在这里插入图片描述

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

#
# 
# @param pHead1 ListNode类 
# @param pHead2 ListNode类 
# @return ListNode类
#
class Solution:
    def FindFirstCommonNode(self , pHead1 , pHead2 ):
        # write code here
        if pHead1 is None or pHead2 is None:
            return None
        
        l1=pHead1
        l2=pHead2
        while l1!=l2:
            if l1 is None:
                l1=pHead2
            else:
                l1=l1.next
            if l2 is None:
                l2=pHead1
            else:
                l2=l2.next
        # 如果不存在的话最后同时为None  也跳出了循环
        return l2  

JZ23/LC142 链表中环的入口结点/环形链表II

题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
  1. 普通法
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        listnode=[]
        if pHead is None or pHead.next is None:
            return None
        while pHead:
            if pHead in listnode:
                return pHead
            else:
                listnode.append(pHead)
                pHead=pHead.next
        return None
  1. 快慢指针

    (1)初始化:快指针fast指向头结点, 慢指针slow指向头结点
    (2)让fast一次走两步, slow一次走一步,第一次相遇在C处,停止
    (3)然后让fast指向头结点,slow原地不动,让后fast,slow每次走一步,当再次相遇,就是入口结点。
    如上解释:

// cpp
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        ListNode *fast = pHead;
        ListNode *slow = pHead;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) break;
        }
        if (!fast || !fast->next) return nullptr;
        fast = pHead;
        while (fast != slow) {
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
    }
};
class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        slow, fast = head, head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            # 如果相遇
            if slow == fast:
                p = head
                q = slow
                while p!=q:
                    p = p.next
                    q = q.next
                #你也可以return q
                return p

        return None

JZ14/LC19 删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 k 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
  1. 快慢指针
    双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。思路是这样的,但要注意一些细节。分为如下几步:
  • 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑。
  • 定义fast指针和slow指针,初始值为虚拟头结点,如图:
  • fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
  • fast和slow同时移动,直到fast指向末尾
  • 删除slow指向的下一个节点,如图:
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        dummy=ListNode()
        dummy.next=head

        slow= dummy
        fast=dummy
        while (n!=0): #fast先往前走n步
            fast=fast.next
            n-=1
        
        while fast.next!=None:
            slow=slow.next
            fast=fast.next

        slow.next=slow.next.next #fast 走到结尾后,slow的下一个节点为倒数第N个节点
        return dummy.next
//cpp
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummyHead->next;
    }
};

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
输出:[]
  1. 迭代法
    如果不添加虚拟头结点,删除头结点就需要另做考虑。
# Python
# 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: ListNode, val: int) -> ListNode:
        dummy=ListNode()
        dummy.next=head
        p=dummy
        while (p.next!=None):
            # 如果不加p.next这个判定 则会报错 因为p.next可能为None,none没用val这个attribute
            if p.next.val==val:
                #  跳过 p.next 节点
                p.next=p.next.next
            else:
                p=p.next
        return dummy.next
// c++
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        struct ListNode* dummyHead = new ListNode(0, head);
        struct ListNode* temp = dummyHead;
        while (temp->next != NULL) {
            if (temp->next->val == val) {
                temp->next = temp->next->next;
            } else {
                temp = temp->next;
            }
        }
        return dummyHead->next;
    }
};
  1. 递归法
    链表的定义具有递归的性质,因此链表题目常可以用递归的方法求解。这道题要求删除链表中所有节点值等于特定值的节点,可以用递归实现。
    对于给定的链表,首先对除了头节点 head以外的节点进行删除操作,然后判断 head 的节点值是否等于给定的val。如果 head 的节点值等于 val,则 head 需要被删除,因此删除操作后的头节点为head.next;如果head 的节点值不等于val,则head 保留,因此删除操作后的头节点还是head。上述过程是一个递归的过程。
    递归的终止条件是head 为空,此时直接返回 head。当head 不为空时,递归地进行删除操作,然后判断head 的节点值是否等于val 并决定是否要删除head。
# Python
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        if head is None:
            return head
        
        # removeElement方法会返回下一个Node节点
        head.next = self.removeElements(head.next, val)
        if head.val == val:
            next_node = head.next 
        else:
            next_node = head
        return next_node
//c++
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if (head == nullptr) {
            return head;
        }
        head->next = removeElements(head->next, val);
        return head->val == val ? head->next : head;
    }
};

LC707 设计链表

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

链表操作的两种方式:直接使用原来的链表来进行操作。设置一个虚拟头结点在进行操作。
下面采用的设置一个虚拟头结点,这样更方便一些。

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

class MyLinkedList:

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

      # -> 是返回函数注释
    def get(self, index: int) -> int:
        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:
        self.addAtIndex(0,val)



    def addAtTail(self, val: int) -> None:
        self.addAtIndex(self._count,val)


    def addAtIndex(self, index: int, val: int) -> None:
        if index<0:# 如果index小于0,则在头部插入节点。
            index=0
        elif index>self._count: #如果 index 大于链表长度,则不会插入节点。
            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: 
            # for 正常运行完再运行else for如果跳出 else 也会跳出
            prev_node.next=add_node
            add_node.next=current_node





    def deleteAtIndex(self, index: int) -> None:
        if 0<=index<self._count:
            self._count-=1
            prev_node = None
            current_node = 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



# 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)

LC24 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
输入: head=[1,2,3,4]
输出: [2,1,4,3]
输入: head=[]
输出 = []

建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序
初始时,cur指向虚拟头结点,然后进行如下三步:

操作之后,链表如下

代码的顺序先从步骤3,再从步骤2,再从步骤1

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummy=ListNode()
        dummy.next=head
        pre=dummy
        # 必须有pre的下一个和下下一个才能交换 否则说明交换已经结束
        while pre.next and pre.next.next:
            cur=pre.next
            post=pre.next.next

            # pre cur 和post分别对应最左中间和最右边的节点
            cur.next=post.next # 步骤三
            post.next=cur # 步骤二
            pre.next=post #步骤一

            pre=pre.next.next

        return dummy.next

LC141 环形链表

Reference

  1. 代码随想录 https://www.programmercarl.com/
  2. 数据结构与算法之美
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值