707.设计链表(单链表)
你可以选择使用单链表或者双链表,设计并实现自己的链表。val和next。val是当前节点的值,next是指向下一个节点的指针/引用。如果是双向链表,则还需要属性prev以指示链表中的上一个节点。假设链表中的所有节点下标从开始。实现index-1valvalvalindexindexindexindex// 链表变为 1->2->3// 返回 2// 现在,链表变为 1->3// 返回 3getad
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:
val和next。val是当前节点的值,next是指向下一个节点的指针/引用。如果是双向链表,则还需要属性
prev以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。实现
MyLinkedList类:
MyLinkedList()初始化MyLinkedList对象。int get(int index)获取链表中下标为index的节点的值。如果下标无效,则返回-1。void addAtHead(int val)将一个值为val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)将一个值为val的节点插入到链表中下标为index的节点之前。如果index等于链表的长度,那么该节点会被追加到链表的末尾。如果index比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)如果下标有效,则删除链表中下标为index的节点。示例:
输入 ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"] [[], [1], [3], [1, 2], [1], [1], [1]] 输出 [null, null, null, null, 2, null, 3] 解释 MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addAtHead(1); myLinkedList.addAtTail(3); myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3 myLinkedList.get(1); // 返回 2 myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3 myLinkedList.get(1); // 返回 3提示:
0 <= index, val <= 1000- 请不要使用内置的 LinkedList 库。
- 调用
get、addAtHead、addAtTail、addAtIndex和deleteAtIndex的次数不超过2000。
题目解读:本题需要我们对创建链表并进行以下操作
1.获取第n个节点的值
2.在头部插入节点
3.在尾部插入节点
4.在第n个节点前插入节点
5.删除第n个节点
解题思路:建议使用虚拟表头,这样2至4操作基本一样只需要找准指针位置即可。
下面我将分成8个部分解析:
1.内部节点类 ListNode
class ListNode {
int val; // 节点存储的数值
ListNode next; // 指向下一个节点的引用
ListNode(int val) {
this.val=val; // 构造方法初始化节点值
}
}
这是链表的基本单元,每个节点包含数值和指向下一个节点的指针。
2.链表的成员变量
// size存储链表元素的个数(不包含虚拟头节点)
private int size;
// 虚拟头结点(固定存在,不存储有效数据)
private ListNode head;
size:记录链表中有效元素的数量,避免每次统计都遍历链表,提升效率。
head:指向虚拟头节点,虚拟头节点的 val 无意义(代码中初始化为 0),核心作用是简化操作。
3.构造方法 MyLinkedList()
public MyLinkedList() {
this.size = 0; // 初始链表为空,有效元素数为0
this.head = new ListNode(0); // 创建虚拟头节点
}
初始化时,链表只有一个虚拟头节点,size 为 0,虚拟头节点的 next 为 null。
4.获取指定位置元素 get(int index)
public int get(int index) {
// 1. 边界校验:index非法(负数 / 大于等于有效元素数),返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
// 2. 遍历找到目标节点:虚拟头节点是第0个,目标是第index+1个
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val; // 3. 返回目标节点的数值
}
index 从 0 开始,且仅对有效元素生效(比如 size=3 时,合法 index 是 0/1/2)。
遍历逻辑:从虚拟头节点出发,循环 index+1 次,最终 cur 指向第 index 个有效节点。
5.头部插入 addAtHead(int val)
public void addAtHead(int val) {
ListNode newNode = new ListNode(val); // 1. 创建新节点
newNode.next = head.next; // 2. 新节点指向原链表的第一个有效节点
head.next = newNode; // 3. 虚拟头节点指向新节点
size++; // 4. 有效元素数+1
}
因为有虚拟头节点,头部插入无需特殊处理,直接修改虚拟头节点的 next 即可。
6.尾部插入 addAtTail(int val)
public void addAtTail(int val) {
ListNode newNode = new ListNode(val); // 1. 创建新节点
ListNode cur = head;
// 2. 遍历到链表最后一个节点(cur.next = null 时停止)
while (cur.next != null) {
cur = cur.next;
}
cur.next = newNode; // 3. 最后一个节点指向新节点
size++; // 4. 有效元素数+1
}
遍历逻辑:从虚拟头节点出发,找到 next 为 null 的节点(即最后一个有效节点)。
7.指定位置插入 addAtIndex(int index, int val)
public void addAtIndex(int index, int val) {
// 1. 边界校验:index < 0 或 > size 时不插入
if (index < 0 || index > size) {
return;
}
// 2. 找到插入位置的前驱节点(第index个节点的前一个节点)
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
// 3. 插入新节点(经典链表插入逻辑:先连后断)
ListNode newNode = new ListNode(val);
newNode.next = pre.next; // 新节点指向前驱的下一个节点
pre.next = newNode; // 前驱指向新节点
size++; // 有效元素数+1
}
核心规则:
index=0:插入到头部(等价于 addAtHead);
index=size:插入到尾部(等价于 addAtTail);
index>size:非法,不插入。
插入逻辑:先让新节点指向前驱的下一个节点,再让前驱指向新节点(避免链表断裂)。
8.删除指定位置元素 deleteAtIndex(int index)
public void deleteAtIndex(int index) {
// 1. 边界校验:index非法时不删除
if (index < 0 || index >= size) {
return;
}
// 2. 找到待删除节点的前驱节点
ListNode pre = head;
for (int i = 0; i < index ; i++) {
pre = pre.next;
}
// 3. 跳过待删除节点(链表删除核心逻辑)
pre.next = pre.next.next;
size--; // 4. 有效元素数-1
}
因为有虚拟头节点,删除第 0 个节点(原头节点)无需特殊处理,和删除其他节点逻辑一致。
完整版
class MyLinkedList {
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val=val;
}
}
//size存储链表元素的个数
private int size;
//注意这里记录的是虚拟头结点
private ListNode head;
//初始化链表
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(0);
}
//获取第index个节点的数值,注意index是从0开始的,第0个节点就是虚拟头结点
public int get(int index) {
//如果index非法,返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
//第0个节点是虚拟头节点,所以查找第 index+1 个节点
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head.next;
head.next = newNode;
size++;
// 在链表最前面插入一个节点,等价于在第0个元素前添加
// addAtIndex(0, val);
}
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = newNode;
size++;
// 在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
// addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index < 0 || index > size) {
return;
}
//找到要插入节点的前驱
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
ListNode newNode = new ListNode(val);
newNode.next = pre.next;
pre.next = newNode;
size++;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
//因为有虚拟头节点,所以不用对index=0的情况进行特殊处理
ListNode pre = head;
for (int i = 0; i < index ; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
size--;
}
}
更多推荐


所有评论(0)