双指针法的应用

什么是双指针法

双指针法,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向或者相反方向的指针进行扫描,从而达到相应的目的。 这里的指针,并非专指c中指针的概念,而是指索引,游标或指针,可迭代对象。

左右双指针

分别指向两头,向中间逼近。通常应用在一个有序数组中。

常见用法

二分查找

二分查找算法其实就是左右双指针的应用之一,详见二分查找

两数之和

在一个有序数组中,寻找两数的和等于目标值。通过左右指针调整和的大小。

两数之和 II - 输入有序数组-leetcode

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int left = 0;
        int right = numbers.size() - 1;
        while(left < right){
            int sum = numbers[left] + numbers[right];
            if(target > sum)
                left++;
            else if(target < sum)
                right--;
            else
                return {left + 1, right + 1};
        }
        return {-1, -1};
    }
};
反转数组

反转字符串-leetcode

class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0;
        int right = s.size() - 1;
        while(left < right){
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
            left++;
            right--;
        }
    }
};

轮转数组-leetcode

因为在数组轮转的过程中,右边的数会转到左边来,轮转一遍时又变成了原来的数组。所以我们先将数组翻转,再将0~k的部分翻转,最后再将后半部分翻转,就得到的我们想要的结果!

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        k = k % nums.size();
        reverse(nums.begin(), nums.end());
        reverse(nums.begin(), nums.begin() + k);
        reverse(nums.begin() + k, nums.end());
    }
};

双指针在这道题的应用主要是反转数组的部分,在做题时我们可以直接调用reverse()函数,但是也要知道它的原理。

void reverse(vector<int>& nums,int begin,int end){
    int temp;
    while(begin<end){ //两个指针begin和end
        temp = nums[begin];
        nums[begin] = nums[end];
        nums[end] = temp;
        begin++;
        end--;
    }
}
滑动窗口

滑动窗口是更复杂的左右双指针问题,主要用来解决字符串匹配,详见滑动窗口

例题

有序数组的平方-leetcode

这题的普通解法是先对数组里的数进行平方然后再进行排序。因为原数组是有序的,所以进行平方后较大的数在两端而不是中间,双指针的解法是用两个指针分别指向数组的第一个数和最后一个数,再创建一个数组,用指针指向最后,比较上面两个指针所指数平方的大小,大的放到下面的数组,然后这个指针移动,直到两个指针相等。

image-20220215134259016

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> result(nums.size(), 0);
        int k = result.size() - 1;
        for(int i = 0, j = nums.size() - 1; i <= j;){ //两个指针i和j
            if(nums[i] * nums[i] <= nums[j] * nums[j]){
                result[k--] = nums[j] * nums[j];
                j--;
            }else{
                result[k--] = nums[i] * nums[i];
                i++;
            }
        }
        return result;
    }
};

快慢双指针

通常从头开始,只不过一个指针在前,另一个指针在后。通常应用在链表中。

常见用法

判断链表是否有环

image-20220216143715313

在一个环状链表中,只用一个指针无法判断。我们可以用两个指针,快指针每次走两步,慢指针每次走一步,当他们相遇时,证明链表有环。就像是两个人在环形跑道上跑步,虽然同时同地出发,但是跑的快的可以追上跑的慢的,因为这时候已经超过一圈了。

环形链表-leetcode

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *slow, *fast;
        slow = fast = head;
        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow)
                return true;
        }   
        return false;
    }
};
判断链表有环,找环的起点

image-20220216151038293

首次相遇时慢指针走的距离为D+S1,快指针走的距离为D+S1+n(S1+S2),(由于两个指针并不是“同步”走的,所以首次相遇不一定只超过一圈)因为快指针每次走两步,慢指针每次走一步,所以走的距离也是它的2倍,2(D+S1)=D+S1+n(S1+S2),即D=(n-1)(S1+S2)+S2。可以看出让慢指针回到头节点,慢指针和快指针每次都走一步,当它们再次相遇时,正好是入环点。

环形链表 II-leetcode

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *slow, *fast;
        slow = fast = head;
        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
            if(slow == fast)
                break;
        }
        if (fast == NULL || fast->next == NULL) {
        return NULL;
        }
        slow = head;
        while(slow != fast){
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
    }
};
寻找链表中点

由于快指针每次走两步,慢指针每次走一步,所以快指针到达终点时,慢指针在链表中点。寻找链表中点的一个重要作用是对链表进行归并排序,详见归并排序

链表的中间结点-leecode

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode *slow, *fast;
        slow = fast = head;
        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};
寻找链表倒数第n个元素

由于倒数第n个元素与链表末尾相差n,所以快指针先走n步,然后两个指针一起走,当快指针到末尾时,慢指针就是我们要的。

删除链表的倒数第 N 个结点-leetcode

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *slow, *fast;
        slow = fast = head;
        while(n--)
            fast = fast->next;
        if(fast == NULL)
        return head->next;
        while(fast != NULL && fast->next != NULL){
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return head;
    }
};

例题

移动零-leetcode

这题虽然是在数组中,但是也用到了快慢双指针,所以要注意灵活使用。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slow = 0;
        int fast = 0;
        while(fast < nums.size()){ //将数组中的0去掉
            if(nums[fast] != 0){
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        for(; slow < nums.size(); slow++) //在后面填充0
            nums[slow] = 0;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值