双指针技巧的小结

本文总结了双指针技巧在处理链表问题和数组问题中的应用,包括快慢指针在寻找链表中点、判断环、找倒数k节点、循环链表起点及链表相交点等问题中的策略;同时介绍了左右指针在二分查找、两数之和、反转数组和滑动窗口算法中的运用。
摘要由CSDN通过智能技术生成

                                                            双指针问题 总结

目录

双指针问题 总结

1、快慢指针的技巧和常用算法(一般为快指针移动一个位置,慢指针移动两个)

(1)可以求未知长度单链表的中间元素

(2)可以判断链表是否有环

(3)寻找倒数第 k 个节点

(4)求一个循环链表的循环开始节点

(5)判断两个链表是否相交及找到第一个交点

2、左右指针常用的算法与技巧

(1)二分查找(很简单)

  (2) 两数之和

  (3) 反转数组

  (4) 滑动窗口 算法


1、快慢指针的技巧和常用算法(一般为快指针移动一个位置,慢指针移动两个)

(1)可以求未知长度单链表的中间元素

  思路:当快指针走到链表尾时,此时慢指针的位置即为所求。因为慢指针走了快指针的一半。

当链表的⻓度是奇数时,slow 恰巧停在中点位置;如果⻓度是偶数,slow 最终的位置是中间偏右。

while (fast != null && fast.next != null)
{
    fast = fast.next.next;
    slow = slow.next;
}
// slow 就在中间位置
return slow;

寻找链表中点的⼀个重要作⽤是对链表进⾏归并排序

 例如对数组的归并排序就是求中点,然后递归地把数组⼆分,最后合并两个有序数组。

 对于链表,合并两个有序链表是很简单的,难点就在于⼆分。  此时用快慢指针能很方便地找到链表的中点,这样就能实现链表 了的⼆分。

(2)可以判断链表是否有环

思路:当链表无环时,快指针走到链表末尾时就会退出循环,给出判断;

           当链表有环时,快指针一定会追上慢指针,此时可以判断出链表有环。

boolean hasCycle(ListNode head)
{
    ListNode fast, slow;
    fast =  slow = head;
    while (fast != null && fast.next != null)
    {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow)
            return true;// 有环
    }
    return false; //无环
}

(3)寻找倒数第 k 个节点

     我们的思路还是使⽤快慢指针,让快指针先⾛ k 步,然后快慢指针开始同速前进。

     这样当快指针⾛到链表末尾NULL时,慢指针所在的位置就是倒数第 k 个链表节点。

ListNode *findkthtoLast(listNode *head, int k)
{
    ListNode *runner = head;
    ListNode *chaser = head;
    
    if(head == NULL || k < 0)
        return NULL;
    
    for(int i=0; i<k; ++i) // 快指针先走 k 步
    {
        runner = runner->next;
    }
    if(runner == NULL) //要判断是否k大于链表长度
        return NULL;
    
    while(runner->next != NULL){
        chaser = chaser->next;
        runner = runner->next;
    }
    return chaser;
}

(4)求一个循环链表的循环开始节点

思路:

  快指针以两倍速遍历,因为有环,所以慢指针肯定会和快指针相遇;

  相遇后,将慢指针指向head节点, 此时快指针在相遇的节点,两个指针再以相同的速度遍历链表,

  再次相遇的节点即为循环开始的节点。

分析:

第⼀次相遇时,假设慢指针 slow ⾛了 k 步,那么快指针 fast ⼀定⾛了 2k 步,也就是说 fast ⽐ slow 多⾛了 k 步(也就是环的⻓度)。

设相遇点距环的起点的距离为 m,那么环的起点距头结点 head 的距离为 k-m,也就是说如果从 head 前进 k - m 步就能到达环起点。 巧的是,如果从相遇点继续前进 k - m 步,也恰好到达环起点。

所以,只要我们把快慢指针中的任⼀个重新指向 head,然后两个指针同速 前进,k - m 步后就会相遇,相遇之处就是环的起点了。

(图片来自 labuladong 公众号文章,公众号对算法的分析和总结很全面,感兴趣的可以关注学习,本文就是参考其中的文章)

(5)判断两个链表是否相交及找到第一个交点

判断是否有交点

先判断两个链表是否有环,如果一个有环一个没环,肯定不相交;若都无环则判断两个链表的尾节点是否相等;若两个链表都有环,则判断一个链表环开始的节点是否在另一个链表上。

找第一个交点

求出两个链表的长度L1,L2(如果有环,则将快慢指针第一次相遇的节点作为尾节点来算),假设L1 < L2,用两个指针分别从两个链表的头部开始走,长度为L2的链表先走(L2 - L1),然后两个指针一起走,直到两者相遇,相遇的节点就是第一个交点。

若对空间复杂度没有要求,可以考虑使用栈来解决

创建两个栈,从头遍历两个链表,第一个栈存储第一个链表的节点,第二个栈存储第二个链表的节点。

每遍历到一个节点时,就将该节点入栈。两个链表都入栈结束后。则通过top判断栈顶的节点是否相等,即可判断两个单链表是否相交。因为我们知道,若两个链表相交,则从第一个相交节点开始,后面的节点都相交。 若两链表相交,则循环出栈,直到遇到两个出栈的节点不相同,则这个节点的后一个节点就是第一个相交的节点。

2、左右指针常用的算法与技巧

 左右指针在数组中实际是指两个索引值,⼀般初始化为 left = 0, right = nums.length - 1。

(1)二分查找(很简单)

int binarySearch(int[] nums, int target)
{
    int left = 0;
    int right = nums.length-1;
    while(lefr <= right)
    {
        int mid = (left + right) / 2;
        if(nums[mid] == target)
            return mid;
        else if(nums[mid] < target)
            left = mid + 1;
        else if(nums[mid] > target)
            right = mid - 1;
    }
    return -1;
}

(2) 两数之和

对于有序数组,就应该很快的想到双指针的技巧。leetcode 167. 两数之和

/*
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
​
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
​
来源:力扣(LeetCode)
*/
class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int left = 0;
        int right = numbers.size()-1;
        int sum;
        while(left <= right)
        {
            sum = numbers[left] + numbers[right];
            if(sum == target)
            return {left+1, right+1};
                
            if(sum > target)
                --right;
            if(sum < target)
                ++left;
        }
        return {-1, -1};
    }
};

(3) 反转数组

void reverse(int[]  nums)
{
    int left = 0;
    int right = nums.length - 1;
    while (left < right)
    {
        //  swap(nums[left], nums[right])
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
        left++;
        right--;
    }
}

(4) 滑动窗口 算法

滑动窗⼝算法的思路是这样:

(问题描述如: 在字符串S中找出:包含T所有字母的最小子串)

1) 我们在字符串S中使⽤双指针中的左右指针技巧,初始化left = right = 0,把索引闭区间 [left, right] 称为⼀个「窗⼝」。

2) 我们先不断地增加 right 指针扩⼤窗⼝ [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

3) 此时,我们停⽌增加 right,转⽽不断增加 left 指针缩⼩窗⼝ [left, right],直到窗⼝中的字符串不再符合要求(不包含 T 中的所有字符了)。 同时,每次增加 left,我们都要更新⼀轮结果。

4) 重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

这里贴出 labuladong 总结出的滑动窗⼝算法的模板:

int left = 0, right = 0;
while (right < s.size())
{
    window.add(s[right]);
    right++;
    while (valid)
    {
        window.remove(s[left]); 
        left++; 
    } 
}

Leetcode有几篇滑动窗口的应用题目,供大家参考:

Leetcode 76.最小覆盖子串(困难)

Leetcode 438. 找到字符串中所有字母异位词 (中等)双指针(滑动窗口)

Leetcode 3. 无重复字符的最长子串(中等)双指针(滑动窗口)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值