剑指 Offer 18. 删除链表的节点
题目描述
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
说明:
- 题目保证链表中节点的值互不相同
- 若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
模拟1
思路
- 题目保证链表中节点的值互不相同,所以只有一个 node 值为
val
。 - 即,只用从前向后遍历找到值为
val
的 前驱节点,即可。
Note:被删除的节点可能是头结点,所以这里**引入 “虚拟头结点”(统一删除操作)**。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode ansList = new ListNode(); // 虚拟头结点
ansList.next = head;
ListNode cnt = ansList;
// 题目中说了链表中所有节点指互不相同,所以只有一个node值为val
// 这里找val的前驱节点
while (cnt != null && cnt.next != null && cnt.next.val != val) {
cnt = cnt.next;
}
cnt.next = cnt.next.next; // 删除val
return ansList.next;
}
}
- 时间复杂度: O ( n ) O(n) O(n)
- 时间复杂度: O ( 1 ) O(1) O(1)
模拟2 ⭐️
但是,如果链表中可以包含重复元素时,以上方法失效(“模拟1”解法中,只能删除一个 val
节点)。
若链表中可以包含重复节点时,可以采用如下写法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode res = new ListNode(); // 虚拟头节点
res.next = head;
ListNode cnt = res;
// 空表不用管
while (cnt != null && cnt.next != null) {
if (cnt.next.val == val) { // 下一个节点时val,则删除之
cnt.next = cnt.next.next;
} else { // 下一个节点不是 val,则指针后移
cnt = cnt.next;
}
}
return res.next;
}
}
- 时间复杂度: O ( n ) O(n) O(n)
- 时间复杂度: O ( 1 ) O(1) O(1)
剑指 Offer 22. 链表中倒数第k个节点
题目描述
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
暴力 (两遍遍历)
思路
- 求链表长度;
- 从前向后遍历找倒数第 k k k 个,即正数第 l e n − k + 1 len-k+1 len−k+1 节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
// 求长度
int len = 0;
ListNode cnt = head;
while (cnt != null) {
len++;
cnt = cnt.next;
}
System.out.println("len = " + len);
// 找到倒数第k个(即,正数第len-k+1)节点
cnt = head;
int i = 0;
while (cnt != null && i < len - k) {
i++;
cnt = cnt.next;
}
System.out.println("cnt = " + cnt.val);
return cnt;
}
}
- 时间复杂度: O ( n ) O(n) O(n)
- 时间复杂度:
O
(
1
)
O(1)
O(1)
一遍遍历 + 数组存储
思路
- 同上面 “两遍遍历” 写法类似,只不过在第一次遍历的时候使用
list
将链表节点存放起来; - 然后,直接返回
list
中正数 l e n − k + 1 len - k + 1 len−k+1 个节点,即为 倒数第 k k k 个节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode cnt = head;
List<ListNode> list = new ArrayList<>();
// 将链表存到数组中
while (cnt != null) {
list.add(cnt);
cnt = cnt.next;
}
// 倒数第k个节点,即正数 len - k + 1 个节点
return list.get(list.size() - k);
}
}
- 时间复杂度: O ( n ) O(n) O(n)
- 时间复杂度:
O
(
n
)
O(n)
O(n)
双指针 ⭐️
思路 🤔
- 先将
fast
向后移动k
个节点;此时,
fast
指向链表中第 k + 1 k + 1 k+1 个节点,从1 开始计数 - 将
slow
从 h e a d head head 开始 和fast
同时向后移动,直至fast
为 n u l l null null,此时slow
便为倒数第 k k k 个节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast= head;
// fast向后走k个节点( 此时,`fast` 指向链表中第 k + 1 个节点,从1 开始计数)
while (k-- > 0) {
fast= fast.next;
}
// 同时后移
ListNode slow = head;
while (fast != null) {
fast= fast.next;
slow = slow .next;
}
return slow ;
}
}
- 时间复杂度: O ( n ) O(n) O(n)
- 时间复杂度:
O
(
1
)
O(1)
O(1)