IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【LeetCode刷题】707. 设计链表(使用双链表) -> 正文阅读

[数据结构与算法]【LeetCode刷题】707. 设计链表(使用双链表)

1:题目描述(力扣

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val?和?next。val?是当前节点的值,next?是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性?prev?以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

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

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

示例:

MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2);   //链表变为1-> 2-> 3
linkedList.get(1);            //返回2
linkedList.deleteAtIndex(1);  //现在链表是1-> 3
linkedList.get(1);            //返回3

2:解题思路

使用双链表:

????????相对于单链表,增加了prev属性,指向链表中的上一个节点。双链表比单链表快得多,花费的时间比单链表快了两倍,但是它更加复杂,它包含了?size,记录链表元素个数,和虚拟头节点和虚拟尾节点。

先定义链表:

class ListNode:
    def __init__(self, val):
        self.val = val
        self.prev = None         # 指向上一个节点为None
        self.next = None         # 指向下一个节点为None

初始化链表:

def __init__(self):
        self.dummy_head = ListNode(0)        # 添加一个虚拟头节点
        self.dummy_tail = ListNode(0)        # 添加一个虚拟尾节点
        # 将虚拟头节点的下一个节点指向虚拟尾节点,虚拟尾节点的上一个节点指向虚拟头节点
        self.dummy_head.next, self.dummy_tail.prev = self.dummy_tail, self.dummy_head
        self._count = 0                      # 链表长度

(1)get(index):获取链表中第?index?个节点的值

第一步:先判断index是否在索引内,当index等于链表长度_count,也是不满足条件的,因为链表索引是从0开始的,不在索引范围内,直接返回-1

第二步:因为双链表有两个虚拟节点(虚拟头节点和虚拟尾节点),因此我们可以判断index与中间索引(_count//2)的大小。从两边进行查找,缩短了时间。

  • 当index小于_count//2时,我们从虚拟头节点向后进行查找,使用next从前往后找。定义一个临时指针cur指向虚拟头节点dummy_head,遍历链表,每遍历一次,将cur指向cur的下一个节点,直到遍历到index。
  • 当index大于等于_count//2时,我们从虚拟尾节点向前进行查找,使用prev从后往前找。定义一个临时指针cur指向虚拟尾节点dummy_tail,从后往前遍历链表,每遍历一次,将cur指向cur的上一个节点,直到遍历到index。

第三步:通过第二步,我们找到了第index个节点,即cur当前指向的节点,此时我们需要返回这个节点的值,即cur.val

def get(self, index):
        if index < 0 or index >= self._count:
            return -1
        # 当index<_count//2,从虚拟头节点开始查找更快,反之从虚拟尾节点查找更快
        if index >= self._count//2:
            # 使用prev从后往前找
            cur = self.dummy_tail
            for i in range(self._count-index):
                cur = cur.prev
        elif index < self._count//2:
            # 使用next从前往后找
            cur = self.dummy_head
            for i in range(index+1):
                cur = cur.next
        return cur.val 

(2)addAtHead(val):在链表的第一个元素之前添加一个值为?val?的节点。插入后,新节点将成为链表的第一个节点。

第一步:定义一个新节点new_node,定义一个临时指针cur指向虚拟头节点。

第二步:先将新节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向新节点;再将cur的下一个节点指向新节点,新节点的上一个节点指向cur。即完成了在第一个元素之前插入新节点。

第三步:添加新节点后,链表长度需要加1。

def addAtHead(self, val):
        new_node = ListNode(val)              # 定义一个新节点
        cur = self.dummy_head               # 临时指针指向虚拟头节点
        # 先将新节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向新节点
        new_node.next, cur.next.prev = cur.next, new_node
        # 再将cur的下一个节点指向新节点,新节点的上一个节点指向cur
        cur.next, new_node.prev = new_node, cur
        self._count += 1                    # 添加新节点后,链表长度需要加1

(3)addAtTail(val):将值为?val 的节点追加到链表的最后一个元素。

第一步:定义一个新节点new_node,定义一个临时指针cur指向虚拟尾节点。

第二步:先将cur的上一个节点的下一个节点指向新节点,新节点的上一个节点指向cur的上一个节点;再将新节点的下一个节点指向cur, cur的上一个节点指向新节点。即完成了将新节点追加到链表的最后一个元素。

第三步:添加新节点后,链表长度需要加1。

def addAtTail(self, val):
        new_node = ListNode(val)              # 定义一个新节点
        cur = self.dummy_tail               # 定义一个临时指针指向虚拟尾节点
        # 先将cur的上一个节点的下一个节点指向新节点,新节点的上一个节点指向cur的上一个节点
        cur.prev.next, new_node.prev = new_node, cur.prev
        # 再将新节点的下一个节点指向cur, cur的上一个节点指向新节点
        new_node.next, cur.prev = cur, new_node
        self._count += 1                    # 添加新节点后,链表长度需要加1

(4)addAtIndex(index,val):在链表中的第?index?个节点之前添加值为?val??的节点。如果?index?等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。

第一步:判断index是否小于0,小于0,将index置为0;判断index是否大于链表长度_count,大于,则返回-1。

第二步:定义一个新节点new_node。

第三步:因为双链表有两个虚拟节点(虚拟头节点和虚拟尾节点),因此我们可以判断index与中间索引(_count//2)的大小。从两边进行查找到index并插入新节点,缩短了时间。

  • 当index小于_count//2时,我们从虚拟头节点向后进行查找,使用next从前往后找。定义一个临时指针cur指向虚拟头节点dummy_head,遍历链表,每遍历一次,将cur指向cur的下一个节点,直到遍历到index。先将新节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向新节点;再将cur的下一个节点指向新节点, 新节点的上一个节点指向cur。即完成了在index前添加了新节点。
  • 当index大于等于_count//2时,我们从虚拟尾节点向前进行查找,使用prev从后往前找。定义一个临时指针cur指向虚拟尾节点dummy_tail,从后往前遍历链表,每遍历一次,将cur指向cur的上一个节点,直到遍历到index。先将新节点的上一个节点指向cur的上一个节点,cur的上一个节点的下一个节点指向新节点;再将新节点的下一个节点指向cur, cur的上一个节点指向新节点。即完成了在index前添加了新节点。

第四步:添加新节点后,链表长度需要加1。

def addAtIndex(self, index, val):
        if index < 0:
            index =0
        elif index > self._count:
            return -1
        # 当index<_count//2,从虚拟头节点向后进行添加新节点,反之从虚拟尾节点向前进行添加新节点
        new_node = ListNode(val)              # 定义一个新节点
        if index < self._count//2:
            # 使用next向后查找到index后添加新节点
            cur = self.dummy_head
            for i in range(index):
                cur = cur.next
            # 先将新节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向新节点
            new_node.next, cur.next.prev = cur.next, new_node
            # 再将cur的下一个节点指向新节点, 新节点的上一个节点指向cur
            cur.next, new_node.prev = new_node, cur
        elif index >= self._count//2:
            # 使用prev向前查找到index后,添加新节点
            cur = self.dummy_tail
            for i in range(self._count-index):
                cur = cur.prev
            # 先将新节点的上一个节点指向cur的上一个节点,cur的上一个节点的下一个节点指向新节点
            new_node.prev, cur.prev.next = cur.prev, new_node
            # 再将新节点的下一个节点指向cur, cur的上一个节点指向新节点
            new_node.next, cur.prev = cur, new_node
        self._count += 1                   # 添加新节点后,链表长度需要加1

(5)deleteAtIndex(index):如果索引?index 有效,则删除链表中的第?index 个节点。

第一步:先判断index是否在索引内,当index等于链表长度_count,也是不满足条件的,因为链表索引是从0开始的,不在索引范围内,直接返回-1

第二步:因为双链表有两个虚拟节点(虚拟头节点和虚拟尾节点),因此我们可以判断index与中间索引(_count//2)的大小。从两边进行查找到index并删除,缩短了时间。

  • 当index小于_count//2时,我们从虚拟头节点向后进行查找,使用next从前往后找。定义一个临时指针cur指向虚拟头节点dummy_head,遍历链表,每遍历一次,将cur指向cur的下一个节点,直到遍历到index。将cur的上一个节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向cur的上一个节点。即完成了删除第index个节点。
  • 当index大于等于_count//2时,我们从虚拟尾节点向前进行查找,使用prev从后往前找。定义一个临时指针cur指向虚拟尾节点dummy_tail,从后往前遍历链表,每遍历一次,将cur指向cur的上一个节点,直到遍历到index。将cur的上一个节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向cur的上一个节点。即完成了删除第index个节点。

第三步:删除一个节点,链表长度需要减1.

def deleteAtIndex(self, index):
        # 
        if index < 0 or index >= self._count:
            return -1
        # 当index<_count//2,从虚拟头节点向后查找第index个节点删除,反之从虚拟尾节点向前查找第index个节点删除
        if index < self._count//2:
            # 使用next向后查找到index后,删除节点
            cur = self.dummy_head
            for i in range(index+1):
                cur = cur.next
            # 将cur的上一个节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向cur的上一个节点
            cur.prev.next, cur.next.prev = cur.next, cur.prev
        elif index >= self._count//2:
            cur = self.dummy_tail
            for i in range(self._count-index):
                cur = cur.prev
            # 将cur的上一个节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向cur的上一个节点
            cur.prev.next, cur.next.prev = cur.next, cur.prev
        self._count -= 1

整体代码如下:

# 使用双链表
# 定义双链表,相对于单链表,增加了prev属性,指向链表中的上一个节点
class ListNode:
    def __init__(self, val):
        self.val = val
        self.prev = None         # 指向上一个节点为None
        self.next = None         # 指向下一个节点为None

class MyLinkedList:

    def __init__(self):
        self.dummy_head = ListNode(0)        # 添加一个虚拟头节点
        self.dummy_tail = ListNode(0)        # 添加一个虚拟尾节点
        # 将虚拟头节点的下一个节点指向虚拟尾节点,虚拟尾节点的上一个节点指向虚拟头节点
        self.dummy_head.next, self.dummy_tail.prev = self.dummy_tail, self.dummy_head
        self._count = 0                      # 链表长度

    def get(self, index):
        if index < 0 or index >= self._count:
            return -1
        # 当index<_count//2,从虚拟头节点开始查找更快,反之从虚拟尾节点查找更快
        if index >= self._count//2:
            # 使用prev从后往前找
            cur = self.dummy_tail
            for i in range(self._count-index):
                cur = cur.prev
        elif index < self._count//2:
            # 使用next从前往后找
            cur = self.dummy_head
            for i in range(index+1):
                cur = cur.next
        return cur.val        


    def addAtHead(self, val):
        new_node = ListNode(val)              # 定义一个新节点
        cur = self.dummy_head               # 临时指针指向虚拟头节点
        # 先将新节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向新节点
        new_node.next, cur.next.prev = cur.next, new_node
        # 再将cur的下一个节点指向新节点,新节点的上一个节点指向cur
        cur.next, new_node.prev = new_node, cur
        self._count += 1                    # 添加新节点后,链表长度需要加1

    
    def addAtTail(self, val):
        new_node = ListNode(val)              # 定义一个新节点
        cur = self.dummy_tail               # 定义一个临时指针指向虚拟尾节点
        # 先将cur的上一个节点的下一个节点指向新节点,新节点的上一个节点指向cur的上一个节点
        cur.prev.next, new_node.prev = new_node, cur.prev
        # 再将新节点的下一个节点指向cur, cur的上一个节点指向新节点
        new_node.next, cur.prev = cur, new_node
        self._count += 1                    # 添加新节点后,链表长度需要加1


    def addAtIndex(self, index, val):
        if index < 0:
            index =0
        elif index > self._count:
            return -1
        # 当index<_count//2,从虚拟头节点向后进行添加新节点,反之从虚拟尾节点向前进行添加新节点
        new_node = ListNode(val)              # 定义一个新节点
        if index < self._count//2:
            # 使用next向后查找到index后添加新节点
            cur = self.dummy_head
            for i in range(index):
                cur = cur.next
            # 先将新节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向新节点
            new_node.next, cur.next.prev = cur.next, new_node
            # 再将cur的下一个节点指向新节点, 新节点的上一个节点指向cur
            cur.next, new_node.prev = new_node, cur
        elif index >= self._count//2:
            # 使用prev向前查找到index后,添加新节点
            cur = self.dummy_tail
            for i in range(self._count-index):
                cur = cur.prev
            # 先将新节点的上一个节点指向cur的上一个节点,cur的上一个节点的下一个节点指向新节点
            new_node.prev, cur.prev.next = cur.prev, new_node
            # 再将新节点的下一个节点指向cur, cur的上一个节点指向新节点
            new_node.next, cur.prev = cur, new_node
        self._count += 1                   # 添加新节点后,链表长度需要加1


    def deleteAtIndex(self, index):
        # 
        if index < 0 or index >= self._count:
            return -1
        # 当index<_count//2,从虚拟头节点向后查找第index个节点删除,反之从虚拟尾节点向前查找第index个节点删除
        if index < self._count//2:
            # 使用next向后查找到index后,删除节点
            cur = self.dummy_head
            for i in range(index+1):
                cur = cur.next
            # 将cur的上一个节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向cur的上一个节点
            cur.prev.next, cur.next.prev = cur.next, cur.prev
        elif index >= self._count//2:
            cur = self.dummy_tail
            for i in range(self._count-index):
                cur = cur.prev
            # 将cur的上一个节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向cur的上一个节点
            cur.prev.next, cur.next.prev = cur.next, cur.prev
        self._count -= 1

上述代码可以进行优化,将从链表两端查找index节点的代码可以提取出来,另写一个函数来调用,减少了代码的冗余性。

# 使用双链表
# 定义双链表,相对于单链表,增加了prev属性,指向链表中的上一个节点
class ListNode:
    def __init__(self, val):
        self.val = val
        self.prev = None         # 指向上一个节点为None
        self.next = None         # 指向下一个节点为None
class MyLinkedList:

    def __init__(self):
        self.dummy_head = ListNode(0)        # 添加一个虚拟头节点
        self.dummy_tail = ListNode(0)        # 添加一个虚拟尾节点
        # 将虚拟头节点的下一个节点指向虚拟尾节点,虚拟尾节点的上一个节点指向虚拟头节点
        self.dummy_head.next, self.dummy_tail.prev = self.dummy_tail, self.dummy_head
        self._count = 0                      # 链表长度

    def get_node(self, index):
        # 当index<_count//2,从虚拟头节点开始查找更快,反之从虚拟尾节点查找更快
        if index >= self._count//2:
            # 使用prev从后往前找
            cur = self.dummy_tail
            for i in range(self._count-index):
                cur = cur.prev
        elif index < self._count//2:
            # 使用next从前往后找
            cur = self.dummy_head
            for i in range(index+1):
                cur = cur.next
        return cur

    def get(self, index):
        if 0 <= index < self._count:
            # 调用get_node(),得到第index个节点
            node = self.get_node(index)
            return node.val        # 返回第index个节点的值
        return -1                  # index不满足索引,返回-1


    def addAtHead(self, val):
        new_node = ListNode(val)              # 定义一个新节点
        cur = self.dummy_head               # 临时指针指向虚拟头节点
        # 先将新节点的下一个节点指向cur的下一个节点,cur的下一个节点的上一个节点指向新节点
        new_node.next, cur.next.prev = cur.next, new_node
        # 再将cur的下一个节点指向新节点,新节点的上一个节点指向cur
        cur.next, new_node.prev = new_node, cur
        self._count += 1                    # 添加新节点后,链表长度需要加1

    
    def addAtTail(self, val):
        new_node = ListNode(val)              # 定义一个新节点
        cur = self.dummy_tail               # 定义一个临时指针指向虚拟尾节点
        # 先将cur的上一个节点的下一个节点指向新节点,新节点的上一个节点指向cur的上一个节点
        cur.prev.next, new_node.prev = new_node, cur.prev
        # 再将新节点的下一个节点指向cur, cur的上一个节点指向新节点
        new_node.next, cur.prev = cur, new_node
        self._count += 1                    # 添加新节点后,链表长度需要加1


    def addAtIndex(self, index, val):
        if index < 0:
            index =0
        elif index > self._count:
            return -1
        new_node = ListNode(val)              # 定义一个新节点
        # 调用get_node(),获取第index个节点
        node = self.get_node(index)
        # 第index个节点的上一个节点的下一个节点指向新节点,新节点的上一个节点指向第index个节点的上一个节点
        node.prev.next, new_node.prev = new_node, node.prev
        # 第index个节点的上一个节点指向新节点,新节点的下一个节点指向第index个节点
        node.prev, new_node.next = new_node, node
        self._count += 1                   # 添加新节点后,链表长度需要加1


    def deleteAtIndex(self, index):
        # 
        if 0 <= index < self._count:
            # 调用get_node(),获取第index个节点
            node = self.get_node(index)    # node即为第index个节点
            # 将node的上一个节点的下一个节点指向node的下一个节点,node的下一个节点的上一个节点指向node的上一个节点
            node.prev.next, node.next.prev = node.next, node.prev
            self._count -= 1               # 删除节点后,链表长度需要减-1
        return -1                          # 索引不满足条件,返回-1

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 02:14:52  更:2022-09-15 02:15:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 13:17:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码