算法通关村第一关——链表经典问题之双指针笔记

文章介绍了使用双指针解决链表问题的两种常见情况:找到链表的中间结点和倒数第k个节点。对于中间结点,通过快慢指针,快指针每次前进两步,慢指针前进一步,当快指针到达末尾时,慢指针位于中间。对于倒数第k个节点,先让快指针走k步,然后快慢指针同时移动,直至快指针到达末尾,慢指针即在目标位置。这些方法具有较好的时间和空间效率。

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

1. 题目:链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
力扣

1.1 分析

1.1.1 第一想法,使用计数器先统计处所有长度,然后再来个计数器让指针移动到中间位置时停止,此时就可以获取中间链表了。

public ListNode middleNode(ListNode head) {
       ListNode current = head;
       int length = 0;
       while(current!=null)  {
           current = current.next;
           length++;
       }   
       int count = 0;
       while(count < length/2){
           head=head.next;
           count++;
       }
       return head;
    }

运行截图
在这里插入图片描述
时间复杂度:O(n)里面两次遍历使用计数器
空间复杂度: O(1)

总结:思路还是很清晰的,使用两个计数器

1.1.2 很显然,这个专题是双指针,肯定是双指针来的快,那么就要思考如何使用双指针,我可以定义一个快慢指针,快指针走两个,慢指针走一个,最后快指针结束了,那么中间节点位置就是慢指针的位置

在这里插入图片描述

 public ListNode middleNode(ListNode head) {
     ListNode slow =head;
     ListNode fast = head;
     while(fast!=null && fast.next!=null){
         slow=slow.next;
         fast=fast.next.next;
     }
     return slow;
    }

运行截图
在这里插入图片描述
时间复杂度:O(n),遍历一次链表
空间复杂度: O(1)

总结:很明显,快慢指针时间空间上性能都很高,这里不需要考虑是否慢指针需要因为奇数偶数而再向后移动,因为条件里面的快指针就是条件,只要满足,最后慢指针就是结果

2. 题目: 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
力扣

2.1 分析

2.1.1 采用快慢指针,先让快指针走k步,让快慢指针里面相差k步,然后两个指针同时走,直到快指针为null,那么慢指针就走到了倒数第k个节点,那么这个节点就是需要的节点。

public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode slow = head;
        ListNode fast =head;
        // 快指针走k步,与慢指针相差k
        while(fast!=null && k>0){
            fast=fast.next;
            k--;
        }
        // 慢指针开始移动,快指针也继续移动
        while(fast!=null){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }

运行截图:
在这里插入图片描述
时间复杂度:O(n),遍历一次链表
空间复杂度: O(1)

总结:当有这种k个距离的时候,采用快慢指针,速度一般是很快的,一开始想法是将链表反转,然后通过k元素的位置节点,然后再反转一次就是所求结果,但是很显然较为复杂,还是需要好好掌握快慢指针。

2. 旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
力扣

2.2 分析

2.2.1 使用快慢指针找到k的位置,不过先要判断一下k是否为空和节点是否存在,还要判断当前链表长度和k的大小,这里采用取余,因为k可能超过链表长度,如果取余后结果为0,意味着不需要移动,但是如果取余大于0,快指针就需要移动一格直到k为0,然后快慢指针一起移动,等快指针为null的时候,k就在慢指针的位置,然后将快指针的next指向链表的头部,然后慢指针后面断开。

public ListNode rotateRight(ListNode head, int k) {
        if(head == null || k == 0){
            return head;
        }
        ListNode temp = head;
        ListNode slow = head;
        ListNode fast = head;
        int len = 0;
        // 计算链表长度
        while(head!=null){
            len++;
            head=head.next;
        }
        // 当k就是链表的长度
        if(k%len == 0){
            return temp;
        }
        while(k%len >0){
            fast=fast.next;
            k--;
        }
        while(fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }

        ListNode res = slow.next;
        slow.next = null;
        fast.next = temp;
        return res;
        
    }

在这里插入图片描述
时间复杂度: O(n)链表长度
空间复杂度: O(1)

总结:需要注意的是一些临界值,比如k是否为0,或者取余是0,因为k不一定就是链表的长度以内。

2.2.2 看到这个题目初始印象就是先分割链表,然后就是合并链表,但是实际写下来发现和上面快慢指针差不多,也是先找到位置分割,然后将第二个链表的最后一个指向原链表的头部,还是不如双指针简单。

总结

对于这种题目说了第k的元素之类,优先考虑一下双指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flybase

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值