题目:
链表中倒数最后k个结点
题解:链表无法逆序访问,也不能通过下标直接访问。故不能逆序遍历链表得到最后k个结点。
解法一:遍历链表用ArrayList存储每一个节点,然后通过下标访问。
当k大于链表长度或者k=0时返回null
倒数第k个节点就是正序list.size()-k个节点。
public ListNode FindKthToTail (ListNode pHead, int k) {
List<ListNode> list = new ArrayList<>();
while(pHead!=null){
list.add(pHead);
pHead=pHead.next;
}
if(k>list.size()||k==0){
return null;
}
return list.get(list.size()-k);
}
复杂度分析:
时间复杂度:O(n),总共遍历n个链表元素
空间复杂度:O(n),list存储n个链表元素
解法二:快慢双指针
双指针指的是在遍历的过程中,使用两个指针遍历(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针)。
构造两个快慢指针,使其之间的距离一直为k,那么当快指针到达链表尾部的时候,慢指针刚好处于倒数第k个位置。
public ListNode FindKthToTail (ListNode pHead, int k) {
ListNode fast = pHead;
ListNode slow = pHead;
while (k > 0 && fast != null) {
fast = fast.next;
k--;
}
if (k > 0) {
return null;
}
while (fast != null) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
第一个while循环,正常情况下k=0,fast!=null,但是如果k大于链表的长度,则fast=null,k>0,属于异常数据,所以在while循环外做了一次k是否大于0的判断。
复杂度分析:
时间复杂度:O(n),总共遍历2n-k个链表元素,O(n)+O(n-k)
空间复杂度:O(1),没有借助额外的内存空间
解法三:先获取到链表长度n,倒数第k个节点就是正向第n-k个节点。解法三与解法一思想有点类似,都是将倒数第k个节点转换成正向第n-k个节点。区别在于解法一用到了额外的存储空间,解法三多遍历了n-k个节点
public ListNode FindKthToTail (ListNode pHead, int k) {
int size = 0;
ListNode node = pHead;
while (node != null) {
size++;
node = node.next;
}
if (k > size) {
return null;
}
int num = size - k;
while (num > 0) {
pHead = pHead.next;
num--;
}
return pHead;
}
复杂度分析:
时间复杂度:O(n),总共遍历2n-k个链表元素,O(n)+O(n-k)
空间复杂度:O(1),没有借助额外的内存空间
总结:
涉及数据结构:链表
涉及算法:快慢双指针