leetcode203
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
问题分析
- 链表中可能包含多个节点,删除节点需要调整指针的指向。
- 当需要删除的节点是头节点时,直接操作
head
会很麻烦,因此引入哨兵节点简化逻辑。哨兵节点的优点:统一逻辑处理,即使删除的是头节点,也不需要额外的特判。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
public ListNode removeElements(ListNode head, int val) {
// 虚拟头节点
ListNode dummyNode = new ListNode(0);
dummyNode.next = head;
ListNode current = dummyNode;
while (current.next != null) {
if (current.next.val == val) {
// 跳过目标节点
current.next = current.next.next;
} else {
// 移动到下一个节点
current = current.next;
}
}
return dummyNode.next; // 返回新的头节点
}
时间复杂度:O(n):遍历链表一次,链表长度为 n。
空间复杂度:O(1):使用了常量级的额外空间,没有引入额外的辅助数据结构。
leetcode206
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
思路一:递归
1.如果链表为空或只有一个节点,直接返回 head
。
2.假设 head.next
及其后的部分已经反转,将 head
插入到反转后的链表末尾。
3.返回新的头节点。
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode newHead = reverseList(head.next);
head.next.next = head; // 让下一个节点指向当前节点,head.next就是newHead的最后一个
head.next = null; // 当前节点的 next 置空
return newHead; // 返回反转后的头节点
}
}
思路二:虚拟头节点 头插法
1.遍历链表:使用一个 head
指针遍历链表。
2.节点插入到新链表的头部(虚拟节点后面)。
public ListNode reverseList(ListNode head) {
ListNode dummy = new ListNode(0); // 虚拟头节点
while (head != null) {
ListNode temp = head; // 暂存当前节点
head = head.next; // 更新 head
temp.next = dummy.next; // 插入到新链表头部
dummy.next = temp; // 更新 dummy.next
}
return dummy.next; // 返回反转后的链表头节点
}
两种方法的时间复杂度差不多,后一种更加容易理解。