上一篇博客中,对链表的原理、以及一些基础的方法进行了简单的探讨,这边博客我们继续玩转链表~
寻找链表的倒数第N个节点
问题描述
普通算法的思路
思路1:
- 将指针移动到链表尾部null
- 将指针往前回退N位
但是,单链表没有pre指针!显然,这种思路是行不通的。
于是,我们换个思路。
思路2:
假设链表的长度为M,寻找倒数第N个节点,相当于寻找链表的第M-N+1个节点。
M = 6,N = 4,那么M-N+1 = 3。
- 遍历链表,取得链表的长度M
- 再次遍历链表,找到第M-N+1个节点
注意:第二次遍历,指针向后移动M-N次。
普通算法的实现
/**
*取得链表的长度
*/
public int lengthOfList(ListNode head) {
int m = 0;
ListNode p = head;
while (p != null) {
m++;
p = p.next;
}
return m;
}
/**
* 方法1
*/
public ListNode find01(ListNode head, int n) {
if (head == null) {
return head;
} else {
int m = lengthOfList(head);
ListNode p = head;
for (int i = 1; i <= m - n; i++) {
p = p.next;
}
return p;
}
}
这种方法,时间复杂度为O(M),M表示链表长度,空间复杂度为O(1)
OnePass算法的思路
额外要求:
- 只允许遍历一次链表,也就是OnePass
- 允许存在多个指针
onePass算法:
- 定义指针p1、p2
- 先将指针p2往后移动N位
- 同时将p1、p2往后移动,直到p2遇到null
OnePass算法的实现
/**
* 方法2,双指针,遍历一次
*/
public ListNode find02(ListNode head, int n) {
if (head == null) {
return head;
} else {
ListNode p1 = head;
ListNode p2 = head;
for (int i = 1; i <= n; i++) {
p2 = p2.next;
}
while (p2 != null) {
p2 = p2.next;
p1 = p1.next;
}
return p1;
}
}