1. 链表的构造
虽然违背面向对象的设计要求,但是不用setter和getter, 直接操作两个public变量,更方便。
public class ListNode {
int val; // 用于存储节点的值
ListNode next; // 指向下一个节点的指针
// 构造函数
ListNode(int x) {
val = x;
next = null;
}
}
2.链表插入节点
为什么要单独处理position==1和head==null
position == 1和其余位置的处理逻辑不同。
目标节点是position, 循环终止使获取position-1。
操作相邻两个节点(后‘节点’可以为null,因为后节点是被赋值而不会需要后节点.next)。
但是position==1为目标节点时,前面没有节点,所以需要单独处理。
/**
* 链表插入节点
* @param head 链表头节点
* @param nodeInsert 待插入节点
* @param position 插入位置, 从1开始
* @return 插入后的链表头节点
*/
public static Node insertNode(Node head, Node nodeInsert, int position) {
if (head == null) {
// 这里可以认为待插入的节点就是链表的头节点, 也可以说此时不存在合适的节点
return nodeInsert;
}
// 已经有的元素个数
int size = getLength(head);
if (position > size+1 || position < 1) {
System.out.println("位置超出范围");
return head;
}
// 头部插入
if (position == 1) {
nodeInsert.next = head;
// 这里可以认为返回 nodeInsert; 也可以认为是:
head = nodeInsert;
return head;
}
Node pNode = head;
int count = 1;
// 这里position要减去1, 因为我们是在pNode后面插入
while (count < position - 1) {
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
/**
* 链表插入节点 - 使用 dummy node 版本
* @param head 链表头节点
* @param nodeInsert 待插入节点
* @param position 插入位置, 从1开始
* @return 插入后的链表头节点
*/
public static Node insertNode(Node head, Node nodeInsert, int position) {
Node dummy = new Node(-1); // 创建一个 dummy node 作为哑元头节点
dummy.next = head; // 将 dummy node 的 next 指向原始头节点
int size = getLength(head);
if (position > size+1 || position < 1) {
System.out.println("位置超出范围");
return head;
}
Node current = dummy; // 使用 current 指针代替 pNode
int count = 0; // 从 dummy node 开始计数,所以初始为 0
// 由于有了 dummy node,我们可以将头部插入纳入 while 循环中
while (count < position - 1) {
current = current.next;
count++;
}
// 在指定位置插入新节点
nodeInsert.next = current.next;
current.next = nodeInsert;
return dummy.next; // 返回 dummy node 的下一个节点作为新的头节点
}
3.链表删除节点
/**
* 删除链表节点
* @param head 链表头节点
* @param position 删除节点的位置, 按位置从1开始
* @return 删除后的链表头节点
*/
public static Node deleteNode(Node head, int position) {
if (head == null) {
return null;
}
int size = getListLength(head);
// 检查下,这里为什么是size, 而不是size+1
if (position > size || position < 1) {
System.out.println("输入的参数有误");
return head;
}
if (position == 1) {
// curNode就是要删除的head
return head.next;
} else {
Node cur = head;
int count = 1;
while (count < position - 1) {
cur = cur.next;
count++;
}
Node curNode = cur.next;
cur.next = curNode.next;
// 上面两行可以简化为: cur.next = cur.next.next
}
return head;
}
public static Node deleteNodeWithDummy(Node head, int position) {
int size = getListLength(head);
Node dummy = new Node(0); // 创建一个 dummy node
dummy.next = head;
if (position > size || position < 1) {
System.out.println("输入的参数有误");
return head;
}
Node current = dummy;
int count = 0;
// 找到要删除节点的前一个节点
while (count < position - 1) {
current = current.next;
count++;
}
// 如果当前节点的下一个节点不为空,删除当前节点的下一个节点
if (current.next != null) {
current.next = current.next.next;
} else {
// 如果位置超出链表长度,输出错误信息
System.out.println("输入的位置超出链表长度");
}
return dummy.next; // 返回 dummy node 的下一个节点,即更新后的头节点
}
反思:
本质上,链表的增删需要考虑三种处理逻辑(第二个是待删/增的node)
- node node node/null 【所以尾节点不需要特殊处理】
- head指针 node node 【通过position==1】
- head指针 node null 【通过head==null】
如果使用了dummynode,以上第二和第三种情况就被合并到第一种情况了。
todo: if(head==null){}case是否可以去掉,if (position > size || position < 1) {}已经过滤了。去掉会不健壮么。