设计链表

题目

力扣题目链接

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性: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

这题有点特殊,与其说是题不如说是熟悉如何实现操控链表的常见方法

参考https://www.cnblogs.com/DAYceng/p/17047323.html中的实例代码以及题题目给的模板得知

我们需要先自行定义节点类ListNode并初始化链表,这里使用的是单链表,定义如下:

class ListNode{
int val;
ListNode next; //定义三个构造器
public ListNode(){
} public ListNode(int val){
this.val = val;
} public ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
} class MyLinkedList {
int size;//链表节点个数
ListNode dummy;//定义虚拟头节点 //用于初始化一个链表
public MyLinkedList() {
//初始化链表的size和虚拟头节点
size = 0;
dummy = new ListNode(0); } public int get(int index) { } public void addAtHead(int val) { } public void addAtTail(int val) { } public void addAtIndex(int index, int val) { } public void deleteAtIndex(int index) { }
}

题目需要实现五个函数,下面逐一介绍

首先需要明确几点:

1、题目中,index是从0开始的,也就是说头节点的值也应该能够获取

2、统一使用虚拟节点,方便进行CRUD

获取第n个节点的值

思路

获取链表的值不能带入其他数据结构的思维

获取链表的值的方式就是遍历链表(这也是链表相对于数组的一大缺陷,慢)

要哪个节点就要从头节点遍历到那个才行

基于此,我们便可以开始设计这个方法了

不过还有几个点需要注意:

  • 需要考虑n的范围(小于0不行,大于链表size-1也不行)
  • 不能直接操作头节点,要不然找不回来原有的链表了
代码实现
class ListNode{
int val;
ListNode next; //定义三个构造器
public ListNode(){
} public ListNode(int val){
this.val = val;
} public ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
} class MyLinkedList {
int size;//链表节点个数
ListNode dummy;//定义虚拟头节点 //用于初始化一个链表
public MyLinkedList() {
//初始化链表的size和虚拟头节点
size = 0;
dummy = new ListNode(0); } public int get(int index) {
//排除非法范围的n
if(index < 0 || index > size - 1){
return -1;
}
//定义当前节点cur
ListNode cur = dummy;
//遍历链表直到找到第index个节点
for(int i = 0; i <= index; i++){
cur = cur.next;
}
return cur.val; //返回该节点的值
}
...
}

ps:

在C++中可以用"while+运算式"来遍历,如下:

while(index){
cur = cur.next;
index--;
}

在Java中好像不能这么写,Java中的while需要的是一个条件式

头部插入节点

(按解题的思路顺序,应该先解决“第n个节点前插入节点”)

思路

如果搞清楚怎么在第n个节点前插入节点,这里的做法其实是一样的

代码实现
public void addAtHead(int val) {
//写法1:直接调用addAtIndex
//addAtIndex(0, val); //写法2:
ListNode pre = dummy;//虚拟节点
ListNode cur = dummy.next;//这个才是真正的“头节点”所在的位置 //新建一个节点
ListNode node4add = new ListNode(val);
node4add.next = pre.next;
pre.next = node4add;
size++; }

尾部插入节点

(按解题的思路顺序,应该先解决“第n个节点前插入节点”)

思路

同理

代码实现
    public void addAtTail(int val) {
//写法1:直接调用addAtIndex
//addAtIndex(index, val); //写法2:直接遍历链表,然后在末尾加
// ListNode pre = dummy;//虚拟节点
// ListNode cur = dummy.next;//这个才是真正的“头节点”所在的位置
ListNode cur = dummy;
while(cur.next != null){//遍历结束条件是遇到next为空的节点
cur = cur.next;
}
ListNode node4add = new ListNode(val);
cur.next = node4add;
size++; }

ps:寻找末尾节点的遍历结束条件是遍历到.next为null的节点

第n个节点前插入节点

//在指定位置前插入新的节点
//要求
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
思路

0、判定index是否合法

1、先找到第n个节点

2、然后将新创建的节点的next指向pre节点(指向dummy)的next(如果是只定义cur,cur指向dummy,那就是指向cur.next)

或者

代码实现
class ListNode{
int val;
ListNode next; //定义三个构造器
public ListNode(){
} public ListNode(int val){
this.val = val;
} public ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
} class MyLinkedList {
int size;//链表节点个数
ListNode dummy;//定义虚拟头节点 //用于初始化一个链表
public MyLinkedList() {
//初始化链表的size和虚拟头节点
size = 0;
dummy = new ListNode(0); }
...
//在指定位置前插入新的节点
//要求
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
//先判定index是否合法
if(index > size){
return;
}else if(index < 0){
index = 0;
}
//有两种写法:使用临时节点变量pre指代dummyhead+cur指代当前节点、直接使用一个cur
// //写法1:
// ListNode pre = dummy;
// ListNode cur = dummy.next;
// //遍历寻找待修改的节点
// for(int i = 0; i < index; i++){
// pre = cur;
// cur = cur.next;
// }
// //找到了就开始插
// //新建用来插入的节点
// ListNode node4add = new ListNode(val);
// //按步骤插入
// node4add.next = pre.next;
// pre.next = node4add;
// size++; // //写法2:
ListNode cur = dummy;
//遍历找点
for(int i = 0; i < index; i++){
cur = cur.next;
}
//新建用来插入的节点
ListNode node4add = new ListNode(val);
node4add.next = cur.next;
cur.next = node4add;
size ++;
}
...
}

ps:别忘了size ++;

删除第n个节点

思路

cur指向A节点(dummy节点),记住,当前节点应该是cur的下一个节点,即B节点(cur.next)

删除当前第n个节点就是删除cur.next

那么只需要把cur的下一个节点的地址指向B的下一个节点,即C节点(cur.next.next)

代码实现
 public void deleteAtIndex(int index) {
if(index < 0 || index >= size){
return;
}
size--; //还是先找到要删除的节点
// ListNode pre = dummy;
// ListNode cur = dummy.next;
ListNode cur = dummy;
// while(index){
// cur = cur.next;
// }
for(int i = 0; i < index; i++){
cur = cur.next;
}
//找到后开始删除
// pre.next = cur.next;
// cur.next = null;
cur.next = cur.next.next;
}

完整代码

class ListNode{
int val;
ListNode next; //定义三个构造器
public ListNode(){
} public ListNode(int val){
this.val = val;
} public ListNode(int val, ListNode next){
this.val = val;
this.next = next;
}
} class MyLinkedList {
int size;//链表节点个数
ListNode dummy;//定义虚拟头节点 //用于初始化一个链表
public MyLinkedList() {
//初始化链表的size和虚拟头节点
size = 0;
dummy = new ListNode(0); } public int get(int index) {
if(index < 0 || index > size - 1){
return -1;
}
//定义当前节点cur
ListNode cur = dummy;
// while(index){
// cur = cur.next;
// index--;
// }
for(int i = 0; i <= index; i++){
cur = cur.next;
}
return cur.val;
} public void addAtHead(int val) {
//写法1:直接调用addAtIndex
//addAtIndex(0, val); //写法2:
ListNode pre = dummy;//虚拟节点
ListNode cur = dummy.next;//这个才是真正的“头节点”所在的位置 //新建一个节点
ListNode node4add = new ListNode(val);
node4add.next = pre.next;
pre.next = node4add;
size++; } public void addAtTail(int val) {
//写法1:直接调用addAtIndex
//addAtIndex(index, val); //写法2:直接遍历链表,然后在末尾加
// ListNode pre = dummy;//虚拟节点
// ListNode cur = dummy.next;//这个才是真正的“头节点”所在的位置
ListNode cur = dummy;
while(cur.next != null){//遍历结束条件是遇到next为空的节点
cur = cur.next;
}
ListNode node4add = new ListNode(val);
cur.next = node4add;
size++; } //在指定位置前插入新的节点
//要求
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
//先判定index是否合法
if(index > size){
return;
}else if(index < 0){
index = 0;
}
//有两种写法:使用临时节点变量pre指代dummyhead+cur指代当前节点、直接使用一个cur
// //写法1:
// ListNode pre = dummy;
// ListNode cur = dummy.next;
// //遍历寻找待修改的节点
// // while(index){
// // cur = cur.next;
// // index--;
// // }
// for(int i = 0; i < index; i++){
// pre = cur;
// cur = cur.next;
// }
// //找到了就开始插
// //新建用来插入的节点
// ListNode node4add = new ListNode(val);
// //按步骤插入
// node4add.next = pre.next;
// pre.next = node4add;
// size++; // //写法2:
ListNode cur = dummy;
//遍历找点
// while(index){
// cur.next = cur.next.next;//cur = cur.next也行,就多遍历一个dummy节点的区别
// index --;
// }
for(int i = 0; i < index; i++){
cur = cur.next;
}
//新建用来插入的节点
ListNode node4add = new ListNode(val);
node4add.next = cur.next;
cur.next = node4add;
size ++;
} public void deleteAtIndex(int index) {
if(index < 0 || index >= size){
return;
}
size--; //还是先找到要删除的节点
// ListNode pre = dummy;
// ListNode cur = dummy.next;
ListNode cur = dummy;
// while(index){
// cur = cur.next;
// }
for(int i = 0; i < index; i++){
cur = cur.next;
}
//找到后开始删除
// pre.next = cur.next;
// cur.next = null;
cur.next = cur.next.next;
}
}

【LeetCode链表#7】设计一个链表并实现常见的操作方法的更多相关文章

  1. 6.5 k个已排好序链表合并为一个排序链表

    1 建立链表(带哨兵位的)2 建立最小堆方法3 合并已排好序的k个链表 typedef int DataType; //建立链表 class Link { private: struct Node { ...

  2. 牛客网:将两个单调递增的链表合并为一个单调递增的链表-Python实现-两种方法讲解

    方法一和方法二的执行效率,可以大致的计算时间复杂度加以对比,方法一优于方法二   1. 方法一: 思路: 1. 新创建一个链表节点头,假设这里就叫 head3: 2. 因为另外两个链表都为单调递增,所 ...

  3. 面试题6:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList

    题目 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. 思路 使用栈依次存放输入的链表顺序的值,然后依次出栈便是链表的逆序. 代码 import java.util.ArrayList ...

  4. 链表操作----将单链表向右旋转 K 个位置

    给定一个单链表,设计一个算法实现链表向右旋转 K 个位置. 举例: 给定 1->2->3->4->5->6->NULL, K=3 则     4->5-> ...

  5. 设单链表中存放n个字符,试设计一个算法,使用栈推断该字符串是否中心对称

    转载请注明出处:http://blog.csdn.net/u012860063 问题:设单链表中存放n个字符.试设计一个算法,使用栈推断该字符串是否中心对称,如xyzzyx即为中心对称字符串. 代码例 ...

  6. 笔试题&amp;面试题:设计一个复杂度为n的算法找到单向链表倒数第m个元素

    设计一个复杂度为n的算法找到单向链表倒数第m个元素.最后一个元素假定是倒数第0个. 提示:双指针查找 相对于双向链表来说,单向链表仅仅能从头到尾依次訪问链表的各个节点,所以假设要找链表的倒数第m个元素 ...

  7. leetcode菜鸡斗智斗勇系列(1)---把一个链表中的二进制数字转换为一个整型数(int)

    Convert Binary Number in a Linked List to Integer这道题在leetcode上面算作是“easy”,然而小生我还是不会做,于是根据大佬的回答来整理一下思路 ...

  8. 链表习题(1)-设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点

    /*设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点*/ /* 算法思想:设f(L,x)的功能是删除以L为首结点指针的单链表中所有值等于x的结点, 则显然有f(L->next,x)的 ...

  9. [链表]LeetCode 25 K组一个翻转链表

    LeetCode 25 k组一个翻转链表 TITLE 示例 1: 输入:head = [1,2,3,4,5], k = 2 输出:[2,1,4,3,5] 示例 2: 输入:head = [1,2,3, ...

  10. 有一个线性表,采用带头结点的单链表L来存储,设计一个算法将其逆置,且不能建立新节点,只能通过表中已有的节点的重新组合来完成。

    有一个线性表,采用带头结点的单链表L来存储,设计一个算法将其逆置,且不能建立新节点,只能通过表中已有的节点的重新组合来完成. 分析:线性表中关于逆序的问题,就是用建立链表的头插法.而本题要求不能建立新 ...

随机推荐

  1. [转帖]linux--Segfault详解

    linux--Segfault详解 1 简介 1.1 段错误的定义 1.2 痛点 2 知识点 2.1 报错内容 2.2 error number 3 排除步骤(借助汇编) 3.1 日志确定错误类型 3 ...

  2. Docker镜像的基本操作总结

    摘要 容器化是上个十年比较火的技术. 现在看起来在进行总计有点晚了. 不过linux是三十年前的,我依旧没有总结好 道理是一样的. 技术不在于新旧, 重要的是学习到原理. Docker的重要概念 Re ...

  3. 一个Redis dump文件的简要分析过程

    摘要 遇到一个老大难的问题. 让帮忙分析一下一个Redis的dump文件. 虽然之前写过了rdb和rdr的文档 但是感觉大家都喜欢拿来主义. 没办法. 今天继续进行深入一点的分析. 原理其实还是基于r ...

  4. Linux平台下面部署node npm 等工具软件

    公司这边用到了运行时定制, 用的是angular 开发的. 所以需要在linux 里面安装 angular的相关工具. 需要在服务器上面有angular nodejs 还有jit的工具 然后 运行时定 ...

  5. typeof的用法和注意点

    基本数据类型和查看数据类型 1==>js有六种基本数据类型. String Boolean Number null underfined Symbol [6种] 但是<你不知道的javas ...

  6. 去除 i 标签的倾斜样式;如何引入本地的阿里字体图标

    去除 i 标签的倾斜样式 i{ font-style:normal; } 如何引入本地的阿里字体图标 将代码下载下来 当然你将下载下载来的资源有用的放在静态资源中 然后在 main.js 引入: ma ...

  7. elementui中表格表头设置背景色

    参考的地址: https://www.cnblogs.com/lljun/p/11551128.html 今天在设置表格的表头的时候,我通过类的时候 发现无法设置表格的表头设置不了颜色: 经过查找:原 ...

  8. 压缩软件 WinRAR 去广告

    别去中国的那个代理网站下载 去国外的官网下载英文版或者湾湾版的, 这样用网上的rarreg.key文件方式就没有广告了, 不然中国的就是有广告. 这里是湾湾版的链接: https://pan.baid ...

  9. 大语言模型的预训练[1]:基本概念原理、神经网络的语言模型、Transformer模型原理详解、Bert模型原理介绍

    大语言模型的预训练[1]:基本概念原理.神经网络的语言模型.Transformer模型原理详解.Bert模型原理介绍 1.大语言模型的预训练 1.LLM预训练的基本概念 预训练属于迁移学习的范畴.现有 ...

  10. HanLP — 词性标注

    词性(Part-Of-Speech,POS)指的是单词的语法分类,也称为词类.同一个类别的词语具有相似的语法性质 所有词性的集合称为词性标注集. 词性的用处 当下游应用遇到OOV时,可以通过OOV的词 ...