链表问题
总结常出现的几种链表问题
前言
无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。然而面试的时候经常碰见诸如获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环,判断环的长度等和长度与位置有关的问题。这些问题都可以通过灵活运用双指针来解决。
参考链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/yi-wen-gao-ding-chang-jian-de-lian-biao-wen-ti-h-2/
问题一:倒数第k个元素
大体思路是,设置两个指针q,p,初始时刻同时指向头节点,首先p指针移动k次,这时p指针指向第k+1个结点,之后两个指针同时移动,当p指针指向空时,q指针指向倒数第k个结点。
代码如下(示例):
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int kthToLast(ListNode head, int k) {
ListNode p = head;
ListNode q = head;
for(int i=0;i<k;i++)
{
p = p.next;
}
while(p!=null)
{
p=p.next;
q=q.next;
}
return q.val;
}
}
问题二:中间位置的结点`
使用快慢指针,fast移动两部每次,slow每次移动一步,当fast.next为空时,如果链表有单数个结点,那么指向的就是中间节点,如果是偶数个结点,那么指向的就是中间位置考前的那一个结点,代码参考上一个。
问题三:环形链表
会询问是否有环,环的长度,环的位置等
是否有环:
1.可以用hash表中元素唯一性来实现
2.另外可以使用快慢指针,如果有环那么快慢指针一定会相遇
环的长度:
基于是否有环的基础上,当快慢指针第一次相遇之后再次移动,使用计数器,下一次相遇时移动的次数就是环的长度。
环的位置:
使用快慢指针,下图来自leetcode。
可以进行推导,核心为快指针移动步数始终为慢指针的二倍。
2*(a+b) = a+(n-1)b+n*c
继而得到 a=c+(n-1)(b+c)
//hash 实现
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode p = head;
Set<ListNode> _hash = new HashSet<ListNode>();
while(p!=null)
{
if(_hash.contains(p))
{
return p;
}
else
{
_hash.add(p);
p=p.next;
}
}
return null;
}
}
时间复杂度和空间复杂度都是O(n),但是使用hash表比较直观
//快慢指针实现
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast!=null)
{
slow = slow.next;
if(fast.next!=null)
{
fast = fast.next.next;
}
else
{
return null;
}
if(fast == slow)
{
ListNode cur = head;
while(cur!=slow)
{
cur=cur.next;
slow=slow.next;
}
return cur;
}
}
return null;
}
}
时间复杂度是O(N),空间复杂度为O(1)。