双指针刷题总结

双指针:

双指针是一种常见的算法技巧,用于解决与数组、链表等数据结构相关的问题。它通过使用两个指针在数据结构上进行遍历和操作,从而有效地解决问题。

双指针问题通常可以分为两类:快慢指针和左右指针。

快慢指针:一个指针移动速度快,一个指针移动速度慢,常用于解决链表中的问题,如判断链表是否有环、寻找链表的中点、寻找链表的倒数第n个元素等。

左右指针:两个指针分别从数组的两端或特定位置开始,相向或同向移动,常用于解决数组中的问题,如二分查找、两数之和、最长连续子数组等。

常见的双指针问题包括:
  • 链表中环的检测:使用快慢指针来判断链表中是否存在环。
  • 链表的中点查找:通过快慢指针的移动,找到链表的中点。
  • 数组的二分查找:利用左右指针在有序数组中进行二分查找。
  • 两数之和问题:通过左右指针在数组中寻找两个数的和等于目标值的情况。
  • 最长连续子数组问题:使用双指针来找到数组中最长的连续子数组。
注意点:
  • 指针的初始化:根据问题的要求,正确初始化指针的位置。
  • 循环条件的设置:确保循环能够正确终止,避免死循环。
  • 指针的移动:根据问题的逻辑,合理地移动指针。
  • 边界情况的处理:考虑到数组的边界和特殊情况,进行适当的处理。
使用双指针的一般步骤如下:
  1. 根据问题的特点,选择合适的双指针类型(快慢指针或左右指针)。
  2. 初始化指针的位置。
  3. 根据问题的要求,在循环中移动指针,并进行相应的操作。
  4. 在循环过程中,根据条件判断是否找到了问题的解。
  5. 对找到的解进行处理或返回。
常见的应用场景:
  • 两数之和:在一个有序数组中,使用双指针可以找到两个数的和等于目标值的情况。
  • 链表反转:通过快慢指针可以方便地实现链表的反转。
  • 字符串操作:例如反转字符串、判断回文等。
  • 数组去重:可以使用双指针来遍历数组,去除重复元素。
  • 滑动窗口:在一个数组或字符串上,通过双指针形成一个窗口,进行窗口内的操作,如求和、计数等。
  • 二分查找:双指针可以用于实现二分查找算法,提高查找效率。
  • 最长连续子数组:找到一个数组中最长的连续子数组,满足特定条件。
  • 有序数组的合并:将两个有序数组合并成一个有序数组。
  • 寻找链表的中间节点:通过快慢指针可以找到链表的中间节点。
  • 判断链表是否有环:利用快慢指针可以检测链表中是否存在环。
使用实例(AI生成)

C++代码实现寻找链表中间节点的双指针示例:

#include <iostream>
// 链表节点结构体
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(NULL) {}
};
ListNode* middleNode(ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;

    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
int main() {
    // 创建示例链表
    ListNode* head = new ListNode(1);
    ListNode* node2 = new ListNode(2);
    ListNode* node3 = new ListNode(3);
    ListNode* node4 = new ListNode(4);
    ListNode* node5 = new ListNode(5);
    head->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = node5;
    ListNode* middle = middleNode(head);
    std::cout << "中间节点的值: " << middle->val << std::endl;
    return 0;
}

 双指针问题在链表中的一个典型应用是寻找链表的中间节点。以下是使用双指针解决该问题的示例代码:

public class MiddleNode {
    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;
    }
    public static void main(String[] args) {
        // 创建一个示例链表
        ListNode head = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        head.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        MiddleNode solution = new MiddleNode();
        ListNode middle = solution.middleNode(head);
        // 输出中间节点的值
        System.out.println("中间节点的值: " + middle.val);
    }
}
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
    }
}

middleNode方法接受一个链表头节点head。通过同时使用快慢两个指针slowfast,快指针每次移动两步,慢指针每次移动一步。当快指针到达链表末尾时,慢指针正好位于中间节点。最后,返回慢指针指向的节点。

快指针和慢指针的移动方向
  • 同向移动:快指针和慢指针沿着相同的方向移动,通常是从链表或数组的头部或尾部开始,向同一方向前进。这种情况下,快指针的移动速度比慢指针快,可以用于查找、比较或遍历链表或数组中的元素。
  • 相向移动:快指针和慢指针从链表或数组的两端向中间移动,直到它们相遇或满足特定条件。这种方式常用于对撞指针问题,例如在有序数组中查找两个数的和等于特定值的情况。
  • 交叉移动:快指针和慢指针在链表或数组中以交叉的方式移动,例如一个指针从头部开始,另一个指针从尾部开始,然后它们在中间的某个位置相遇。这种移动方式可以用于解决一些特定的问题,如寻找链表的中间节点或判断链表是否存在环。
判断快指针和慢指针是否相遇
  1. 比较指针的值:在某些情况下,可以直接比较快指针和慢指针的值来判断它们是否相遇。例如,在有序数组中,可以比较两个指针所指向的元素的值是否相等。
  2. 检查特定条件:根据具体的问题,可能存在一些特定的条件来判断指针是否相遇。例如,在链表中判断是否存在环时,可以检查快指针是否追上了慢指针。
  3. 计算指针移动的步数:可以通过计算快指针和慢指针移动的步数来判断它们是否相遇。例如,在一个固定长度的数组中,可以计算两个指针移动的步数是否相等。
  4. 使用标记或标志:可以在遍历过程中设置标记或标志来表示指针是否相遇。例如,在链表中,可以在相遇的节点上设置一个标记,然后在后续的遍历中检查该标记。
  5. 结合其他条件判断:有时候需要结合其他条件来判断指针是否相遇。例如,在一个有边界限制的区域内,可以根据指针的位置和边界条件来判断是否相遇。
避免指针碰撞
  • 设置边界条件:在使用双指针时,明确指针的移动范围和边界条件。通过在循环中检查指针是否超出边界,可以避免指针碰撞。
  • 使用合适的指针移动策略:根据具体问题,选择合适的指针移动策略。例如,在对撞指针中,确保左右指针按照正确的方向移动,避免它们相互超越。
  • 提前判断和处理:在指针移动之前,进行条件判断,确保移动不会导致碰撞。如果可能发生碰撞,可以提前采取相应的处理措施,如调整指针位置或结束循环。
  • 数据结构的选择:根据问题的特点,选择合适的数据结构来避免指针碰撞。例如,使用链表时,可以通过节点的连接关系来控制指针的移动,避免碰撞。
  • 仔细设计算法:在设计双指针算法时,充分考虑各种情况,特别是边界情况和特殊情况。通过仔细的逻辑设计,可以减少指针碰撞的可能性。
  • 调试和测试:在编写代码后,进行充分的调试和测试。通过输入各种边界和特殊情况的测试用例,检查指针是否正确移动,是否发生碰撞,并确保算法的正确性。
使用双指针法解决链表遍历问题
  1. 初始化指针:创建两个指针,一个称为快指针(fast pointer),一个称为慢指针(slow pointer)。通常将它们初始化为链表的头节点。
  2. 移动指针:通过迭代或循环,同时移动快指针和慢指针。快指针每次移动的步长较大,而慢指针每次移动的步长较小。
  3. 处理指针位置:在每次移动指针后,根据指针的位置进行相应的处理。这可能包括访问节点的值、修改节点的链接或执行其他操作。
  4. 判断结束条件:根据具体问题,确定合适的结束条件。这可能是当快指针到达链表的末尾、两个指针相遇或满足其他特定条件时。
  5. 输出结果:根据问题的要求,输出最终的结果。
#include <iostream>
// 链表节点结构体
struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(NULL) {}
};
// 找到链表中间节点的函数
ListNode* findMiddleNode(ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;
    while (fast!= NULL && fast->next!= NULL) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
// 打印链表的函数
void printList(ListNode* head) {
    ListNode* curr = head;
    while (curr!= NULL) {
        std::cout << curr->val << " ";
        curr = curr->next;
    }
    std::cout << std::endl;
}
int main() {
    // 创建一个示例链表
    ListNode* head = new ListNode(1);
    ListNode* node2 = new ListNode(2);
    ListNode* node3 = new ListNode(3);
    ListNode* node4 = new ListNode(4);
    ListNode* node5 = new ListNode(5);
    head->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = node5;
    std::cout << "原始链表: ";
    printList(head);
    ListNode* middleNode = findMiddleNode(head);
    std::cout << "中间节点的值: " << middleNode->val << std::endl;
    return 0;
}

注意点:AI生成

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

筱姌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值