面试题15:输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点。
Basic
头指针和中间指针向前遍历法(自创词汇方便记忆)
设置头指针和中间指针,保持相同间隔,朝着同一个方向向前遍历链表。
思路分析
需要注意 k <= 0 的场景和k大于链表长度的场景。
代码实现
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(k <= 0 || head == null) {
return null;
}
ListNode front = head;
ListNode back = head;
for(int i=0; i< k-1; i++) {
//这里需要通过在纸上画出临界点的场景,比如只有5个节点,求倒数第5个节点和倒数第6个节点的场景
//倒数第5个:k-1 = 4, i 从0~3,总共循环4次,最终遍历到头结点的后序4个节点,即尾节点
//倒数第6个:k-1 = 5, i 从0~4,总共循环5次,最终遍历到出界,如果出现back.next == null,说明出界了
if(back.next == null){
return null;
}
back = back.next;
}
while(back.next != null){
front = front.next;
back = back.next;
}
return front;
}
}

扩展
题目1:求链表的中间节点
如果链表中的总数为奇数,返回中间节点;如果为偶数,返回中间两个节点的任意一个。
思路分析
定义两个指针,同时从链表头结点出发,一个指针一次走一步,另一个指针一次走两步。
当走得快的指针走到链表末尾时,走得慢的指针正好在链表中间。
可以在纸上模拟。
代码实现
public static Node findMiddleNode(Node head) {
if (head == null) {
return null;
}
if (head.next == null) {
return head;
}
Node slow = head;
Node fast = head;
//1、当链表有偶数个节点时,可以遍历到倒数第 2 个节点,由于可以返回中间两个节点的任意一个,
// 此时慢指针刚好就是其中两个中间节点的前一个节点,此时定位结束。
//2、当链表有奇数个节点时,可以遍历到最后一个节点,此时慢指针刚好到正中间的节点,定位结束
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}

题目2:判断一个单向链表是否形成了环结构。
思路分析
定义两个指针,同时从链表头结点出发,一个指针一次走一步,另一个指针一次走两步。
如果走得快的指针追上了走得慢的指针,那么链表就存在环形结。
如果走得快的指针走到了链表末尾,即 next 为 null,则表明链表不存在环形结构。
可以在纸上模拟。
代码实现
public static boolean isLoop(Node head) {
if (head == null || head.next == null) {
return false;
}
Node oneStepNode = head;
Node twoStepNode = head;
while (twoStepNode.next != null && twoStepNode.next.next != null) {
oneStepNode = oneStepNode.next;
twoStepNode = twoStepNode.next.next;
//注意避免死循环
if (oneStepNode == twoStepNode) {
return true;
}
}
return false;
}
