一文搞定常见的链表问题

链表定义

// 链表的定义
// Definition for singly-linked list.
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};

常见问题

无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。

然而面试的时候经常碰见诸如获取倒数第 k 个元素获取中间位置的元素判断链表是否存在环判断环的长度等和长度与位置有关的问题。这些问题都可以通过灵活运用双指针来解决。

倒数第 k 个元素

设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1 个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 q 即指向倒数第 k 个结点。可以参考下图来理解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WVPdk0Pu-1677758676523)(一文搞定常见的链表问题.assets/8fc9ef022554d2a062db6a70d5199dbbb2a154ba1e64f0f697319bb0ef9ac680.png)]

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *p = head, *q = head; //初始化
        while(k--) {   //将 p指针移动 k 次
            p = p->next;
        }
        while(p != nullptr) {//同时移动,直到 p == nullptr
            p = p->next;
            q = q->next;
        }
        return q;
    }
};

获取中间元素

设有两个指针 fast 和 slow,初始时指向头节点。

每次移动时,fast 向后走两次,slow 向后走一次,直到 fast 无法向后走两次。

这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设

链表有 n 个元素,那么最多移动 n/2 轮。

当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠前一个 (可以考虑下如何使其指向后一个结点呢?)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-reR2pJj5-1677758676524)(一文搞定常见的链表问题.assets/7984572cea859a3d569932934fe6580abf3f59221ec95911f662f1f19b6822aa.png)]
下述代码实现了 n 为偶数时慢指针指向靠后结点

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode *p = head, *q = head;
        while(q != nullptr && q->next != nullptr) {
            p = p->next;
            q = q->next->next;
        }
        return p;
    } 
};

是否存在环

如果将尾结点的 next 指针指向其他任意一个结点,那么链表就存在了一个环。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDqdD7br-1677758676524)(一文搞定常见的链表问题.assets/ea4aff71116d6c483b2b34519497d39d640435cb3ce12ea1b40dd453c45b59fd.png)]
当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。

想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。

当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0Slb2km-1677758676524)(一文搞定常见的链表问题.assets/d1ac82780e5189d7d58406504c3b7b56c35165997bfbb4c325677af92ee2d483.gif)]
根据上述表述得出,如果一个链表存在环,那么快慢指针必然会相遇。实现代码如下:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *slow = head;
        ListNode *fast = head;
        while (fast != nullptr) {
            slow = slow->next;
            if (fast->next == nullptr) return false;
            fast = fast->next->next;
            if (fast == slow) return true;
        }
        return false;
    }
};

最后一个问题,如果存在环,如何判断环的长度呢?

方法是,快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。

反转链表

有迭代和递归两种写法。

// 迭代法
class Solution {
public:
    ListNode *reverseList(ListNode *head) {
        ListNode *prev = nullptr, *next;
        while (head) {
            next = head->next;
            head->next = prev;
            prev = head;
            head = next;
        }
        return prev;
    }
};

递归版本稍微复杂一些,假设链表为:
n 1 → … → n k − 1 → n k → n k + 1 → … → n m → ∅ n_1 \rightarrow \ldots \rightarrow n_{k-1} \rightarrow n_k \rightarrow n_{k+1} \rightarrow \ldots \rightarrow n_m \rightarrow \varnothing n1nk1nknk+1nm
若从节点 n k + 1 n_{k+1} nk+1 n m n_m nm 已经被反转,而我们正处于 n k n_k nk
n 1 → … → n k − 1 → n k → n k + 1 ← … ← n m n_1 \rightarrow \ldots \rightarrow n_{k-1} \rightarrow n_k \rightarrow n_{k+1} \leftarrow \ldots \leftarrow n_m n1nk1nknk+1nm
我们希望 n k + 1 n_{k+1} nk+1 的下一个节点指向 n k n_k nk
所以, n k . n e x t . n e x t = n k n_k.next.next=n_k nk.next.next=nk

需要注意的是 n 1 n_1 n1 的下一个节点必须指向 ∅ \varnothing 。如果忽略了这一点,链表中可能会产生环。

// 递归法
class Solution {
public:
    ListNode *reverseList(ListNode *head) {
        if (head == nullptr || head->next == nullptr) return head;
        ListNode *p = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return p;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值