一、链表介绍
关于链表这块,其实个人我感觉如果是单纯从“面向对象”的思维去考虑的话,会容易把自己搞混(也有可能是因为当年学链表的时候看的是C的代码),所以其实这块可以去看一下C语言关于链表的介绍:
![](https://img-blog.csdnimg.cn/direct/7af1c0295f8b479f9c728f25308e2516.png)
简单来讲,一个节点其实分为两个部分:data和next。对于Java来说,我们将一个节点(Node)看作为一个对象,这个对象包括两个属性,一个属性是节点值(val),另一个属性是下一个节点(在Java中,称作“引用”,引用的数据类型也是Node(next))。
综上,其实我们可以将节点,定义为一个类,如下:
private static class Node {
int value;
Node next;
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
而无论是单链表,还是双链表,我们可以将“链表(LinkedList)”视作一个对象,每一个链表的对象其实是由节点对象(Node)组成的。为了方便链表的操作,我们一般会给链表设置一个头节点,头节点对于链表来说就是一个成员变量。
综上,我们可以定义出链表(单向链表)对象:
public class LinkedList {
private Node head;
}
这样或许会对 LeetCode707.设计链表 理解更加深刻。
其实我自己对于Java面向对象这块理解也不是很透彻。
二、LeetCode203.移除链表元素
阅题思路:思路来自 《2022年王道数据结构考研复习指导》。
步骤拆解:
代码如下:
public static ListNode removeElements(ListNode head, int val) {
// 构造虚拟头节点,将虚拟节点的下一个节点指向头节点
ListNode dummy = new ListNode(-1, head);
// 判断极限情况
if (head == null) {
return head;
}
// 定义节点指向当前的链表里的节点
ListNode curr = head;
// 定义当前节点的前驱节点
ListNode pre = dummy;
while (curr != null) {
if (curr.val != val) {
// 当前节点指向的节点值不等与目标值,向后移动
curr = curr.next;
pre = pre.next;
} else {
// 相等就移除当前cur指向的元素
pre.next = curr.next;
curr = curr.next;
}
}
return dummy.next;
}
三、LeetCode707.设计链表
阅题思路:这道题的关键在于对整个的Java面向对象的考察,怎么定义一个MyLinkedList对象,并为这个对象设置一些方法。其中最取巧的在于addAtHead和addAtTail这两个方法,相当于直接在指定的索引位置插入节点。(源于《代码随想录》介绍)
代码如下:
class MyLinkedList {
// 初始化长度
int size;
// 初始化头节点
ListNode head;
/**
* 初始化链表对象
*/
public MyLinkedList() {
size = 0;
head = new ListNode(-1);
}
/**
* 获取链表中指定索引的节点的值
*
* @param index
* @return
*/
public int get(int index) {
// 校验索引合法性
if (index < 0 || index >= size) {
return -1;
}
// 定义一个移动节点,初始值为head(保障head永远指向构造出来的链表)
ListNode pre = head;
for (int i = -1; i < index; i++) {
pre = pre.next;
}
return pre.val;
}
public void addAtHead(int val) {
// 在头插入相当于在索引为0的位置插入
addAtIndex(0, val);
}
public void addAtTail(int val) {
// 在尾插入相当于在索引为size的位置插入
addAtIndex(size, val);
}
/**
* 在指定的索引位置插入节点
* @param index
* @param val
*/
public void addAtIndex(int index, int val) {
// 校验索引合法性
if (index > size) {
// 大于长度不插入
return;
}
if (index < 0) {
// 索引位置小于0,也不插入
return;
}
size++;
// 定义一个前驱节点
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;
}
/**
* 删除指定索引的节点
*
* @param index
*/
public void deleteAtIndex(int index) {
// 校验索引合法性
if (index < 0 || index >= size) {
return;
}
size--;
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
}
}
四、LeetCode206.反转链表
阅题思路:根据链表的特性,翻转一个链表其实是改变每一个节点后继节点的指向;
步骤拆解:
代码如下:
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
// 开始翻转
// 保存当前节点的后继
ListNode temp = cur.next;
// 翻转步骤
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}