目录
废话不多说,上菜 😎
质朴做法(要考虑特殊情况)
💡分析:
// 朴素做法
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) return head; // 如果链表为空直接返回
// 处理当需要删除头结点的时候,因为正常的删除都是需要先找到要删除结点的上一个结点,所有对这种情况要进行特判
while (head.val == val && head != null) { // 因为删除头结点后,新的头结点可能还需要删除
head = head.next;
if (head == null) return head; // 处理特殊情况,当链表的的最后一个元素需要被删除时
// 直接return就好,此时head为空,如果再进行head.val == key的判断就会发生空指针异常
}
// 创建一个引用cur去完成链表的遍历(头节点是整个链表的灵魂,不能直接使用,避免丢失链表)
// 此时的头结点绝对不是要删除的元素,所有我们可以用正常的方法进行删除
ListNode cur = head;
while (cur != null && cur.next != null) { // 删除一个结点,首先要找到他的上一个结点
if (cur.next.val == val) {
cur.next = cur.next.next;
}
else {
cur = cur.next; // 继续向后走,看接下来还有没有要删除的结点
}
}
return head;
}
}
定义虚拟头节点来简化逻辑的做法
💡分析:
// 设置一个虚拟头结点来进行操作
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 设置一个虚拟头结点,可以简化逻辑,这样就不用为删除头结点单独考虑了
ListNode dummyHead = new ListNode(0, head); // 调用带两个参数的构造方法,对新建的结点进行初始化,并将该结点指向链表的头结点
// 创建一个引用cur去完成链表的遍历(此时的虚拟头节点是整个链表的灵魂,不能直接使用,避免丢失链表)
// 不用单独考虑链表为空的情况,应为当链表为空即cur.next == null,循环进不去,直接就返回了
ListNode cur = dummyHead;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
}
else {
cur = cur.next;
}
}
return dummyHead.next; // 返回真正的头结点
}
}
递归做法
💡首先,链表可以想象成一个头结点加上它后面一个更短的结点。如图所示:
如果头节点对应的数值不等于 val ,原问题的结果就是头节点后面挂接上子问题求得的紫色的链表,否则,结果就是子问题求得的紫色的链表。
🌰看完后你可能不太理解,那么我们来画图来实践一下:
递归的确不好理解,在短短几行代码中,他看似什么都没干(其实什么都干了)
强烈建议大家自动动手画一下递归中递和归的过程,只有你亲身画了,你才知道递归的妙处。