剑指offer 22 链表中倒数第K个节点
输入一个链表,输出该链表中倒数第k
个节点。为了符合大多数人的习惯,本题从1
开始计数,即链表的尾节点是倒数第1
个节点。
例如,一个链表有 6
个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6
。这个链表的倒数第 3
个节点是值为 4
的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 $k = 2.$
返回链表 4->5.
解题思路:
用快慢指针,让快指针先走k
步,随后让快慢指针一起走,当快指针走到尾部的后一节点null
的时候,慢指针刚好走了m-k
步,也就是刚好到倒数第k
个节点的位置。
Java代码
/**
* 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) {
if(head == null || k < 0) return head;
ListNode fast = head;
ListNode slow = head;
for(int i = 0;i < k;i++){//快指针先走k步
fast = fast.next;
}
//现在快慢指针一起走,快指针指向null时,慢指针刚好指向倒数第k个节点
while(fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
扩展题一:LeetCode 19 删除链表的倒数第N个节点
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例 1:
输入: head = [1,2,3,4,5], n = 2
输出: [1,2,3,5]
示例 2:
输入: head = [1], n = 1
输出: []
示例 3:
输入: head = [1,2], n = 1
输出: [1]
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
解题思路:
由于有可能要删除的是倒数最后一个结点,也即第一个结点,故我们可以建立一个虚拟头结点dummy
,然后设定快慢指针,fast
和slow
,让fast
先走n
步,slow
不动,然后fast
和slow
同时走,直到fast
走到尽头,假设共有m
个结点,则fast
走n
步后,还剩下m-n
个结点,此时fast
和slow
再都走m-n
步,fast
到达尽头,而slow
还差n
步才到尽头,而这个位置刚好是我们所要删除结点的前一位置。如图所示:
现在两个指针同时走,fast
走到尾结点走了m-n
步,故慢指针也是走了m-n
步,还差n
步才到尾结点,因此慢指针刚好指着我们需要的倒数第n
个结点的前一结点
Java代码
/**
* 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; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//由于可能要删除的是头结点,故建立虚拟头结点,用于统一操作
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode fast = dummy,slow = dummy;
//快指针先走n步
for(int i = 0;i < n;i++){
fast = fast.next;
}
//现在快慢指针一起走
while(fast.next != null){
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
扩展题二:LeetCode 876 求链表的中间节点
876. 链表的中间结点
给定一个头结点为 head
的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出: 此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入: [1,2,3,4,5,6]
输出: 此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
提示:
给定链表的结点数介于 1
和 100
之间。
解题思路:
同样是用快慢指针,这里由于是求中间节点,所以可以让快指针每次走两步,慢指针每次走一步
,当链表长度是奇数
时,快指针走到尾结点时,慢指针刚好指着中间节点。当链表长度是偶数
时,快指针指着链表尾结点下一节点的null
时,慢指针指着中间节点的第二个节点。
Java代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode middleNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode fast = head;
ListNode slow = head;
//由于要用到fast.next.next,所以一定要判断一下fast.next!=null,否则出现空指针异常
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
举一反三
当我们用一个指针遍历链表不能解决问题的时候,可以尝试用两个指针来遍历链表。可以让其中一个指针遍历的速度快一些,比如一次在链表中走两步,或者让它先在链表上走若干步。
双指针、快慢指针两个思想很重要,数组和链表问题经常用到。