【力扣探索】链表——双指针技巧

本文介绍了链表中双指针技巧的应用,包括判断链表是否有环的经典环绕问题,提供了链表双指针解题模板,并详细讲解了环形链表、相交链表及删除链表倒数第N个节点的解题思路,涉及快慢指针、哈希表等方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链表的双指针技巧

经典环绕问题

问题:给定一个链表,判断其中有无环

链表中使用两个速度不同的指针时会遇到的情况:

  1. 如果没有环,快指针将停在链表的末尾。
  2. 如果有环,快指针最终将与慢指针相遇

两个指针的速度应该如何设置?
3. 安全的选择是每次移动慢指针一步,而移动快指针两步。每一次迭代,快速指针将额外移动一步。如果环的长度为 M,经过 M 次迭代后,快指针肯定会多绕环一周,并赶上慢指针。
4. 其他选择?有用吗? 会更高效么?

链表双指针解题模板

在这里,我们为你提供了一个模板,用于解决链表中的双指针问题。

// Initialize slow & fast pointers
ListNode* slow = head;
ListNode* fast = head;
/**
 * Change this condition to fit specific problem.
 * Attention: remember to avoid null-pointer error
 **/
while (slow && fast && fast->next) {
   
    slow = slow->next;          // move slow pointer one step each time
    fast = fast->next->next;    // move fast pointer two steps each time
    if (slow == fast) {
            // change this condition to fit specific problem
        return true;
    }
}
return false;   // change return value to fit specific problem

Tips

1. 在调用 next 字段之前,始终检查节点是否为空
获取空节点的下一个节点将导致空指针错误。例如,在我们运行 fast = fast.next.next 之前,需要检查 fast 和 fast.next 不为空。
2. 仔细定义循环的结束条件。
运行几个示例,确保结束条件不会导致无限循环。在定义结束条件时,你必须考虑我们的第一点提示。
3. 复杂度分析
空间复杂度分析容易。如果只使用指针,而不使用任何其他额外的空间,那么空间复杂度将是 O(1)。但是,时间复杂度的分析比较困难,我们需要分析运行循环的次数。

练习

1. 环形链表

题目链接: 141. 环形链表.
解题思路

  1. 快慢指针,原则上设置快指针速度为M,慢指针速度为N,当M-N=1且存在环时,两者必然会在环内相遇,更概括的,当相遇的时候,快指针多跑了若干个环的长度(设环长为L),只要两者的速度差满足L%(M-N) = 0或者(M-N) = k*L(k为正整数)即可。
  2. 逐个删除,一个链表从头节点开始一个个删除,所谓删除就是让他的next指针指向他自己。如果没有环,从头结点一个个删除,最后肯定会删完;如果是环形的,那么有两种情况,一种是o型的,一种是6型的,删到最后,肯定会出现head=head.next,详情见逐个删除思路解析
  3. 反转比较,关于链表的反转可以看下432,剑指 Offer-反转链表的3种方式。如果有环,那么链表反转之后,原来的头结点和反转之后的头结点一定是同一个.
  4. 存放集合,把节点存放到集合set中,每次存放的时候判断当前节点是否存在,如果存在,说明有环
  5. 哈希表,每次遍历到一个节点时,判断该节点此前是否被访问过,用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。

代码

//快慢指针版本1,时间O(N) 额外空间O(1)
class Solution {
   
public:
    bool hasCycle(ListNode *head) {
   
        if(head == NULL) return false;
        ListNode* fast = head;
        ListNode* slow = head;
  		while(slow != NULL && fast != NULL){
   
  			slow = slow->next;
  			if(fast->next != NULL){
   
  				fast = fast->next->next;
  			}
  			else break;
  			if(slow == fast) return true;	
  		} 
  		return false;     
    }
};

//快慢指针版本2,时间O(N) 额外空间O(1)
class Solution {
   
public:
    bool hasCycle(ListNode* head) {
   
        if (head == nullptr || head->
### 力扣第82题 删除链表中的重复元素 力扣第82题要求从已排序的链表中删除所有具有重复值的节点,仅保留原始列表中不重复的数字。此问题可以通过遍历链表并维护前驱节点来解决。 以下是该问题的一种常见解法: #### 解决方案 通过创建一个虚拟头结点 `dummy` 来简化边界条件处理。定义两个指针:一个是用于记录当前正在检查的节点的前驱节点 (`prev`);另一个是指向当前节点 (`curr`) 的指针。如果发现连续多个节点的值相同,则跳过这些节点直到找到不同的值为止。 ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next class Solution: def deleteDuplicates(self, head: ListNode) -> ListNode: dummy = ListNode(0) # 创建哑节点作为新的头部 dummy.next = head # 将原链表连接到新头部之后 prev = dummy # 前置指针初始化为哑节点 curr = head # 当前指针初始化为实际头部 while curr is not None: flag = False # 标志位判断是否有重复 # 如果下一个节点存在且其值等于当前节点值,则继续移动current直至不同值出现 while curr.next is not None and curr.val == curr.next.val: curr = curr.next flag = True # 设置标志表示遇到了重复项 if not flag: # 若无重复则正常更新前置指针指向当前节点 prev.next = curr prev = prev.next curr = curr.next # 移动至下一位 prev.next = None # 断开可能存在的多余部分链接关系 return dummy.next ``` 以上方法利用了双循环结构,在外层控制整体流程的同时内部实现局部去重逻辑[^1]。 #### 复杂度分析 时间复杂度O(n),其中n代表输入链表长度。因为每个节点最多被访问两次——一次由外部while loop完成初次扫描,另一次可能是由于内部判定过程造成的额外操作。 空间复杂度O(1),除了几个辅助变量之外并未消耗更多存储资源[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值