尺取法——双指针

2023-05-30 11:26:38 浏览数 (1)

方法解读

在尺取法中,有两种扫描方法:

  1. 反向扫描:

一个指针(i)从头到尾进行扫描,另一个指针(j)从尾到头进行扫描。

  1. 同向扫描:

两个指针i,j都是从头开始扫描,只不过是速度不同,我们也把这种方法称为快慢指针。快慢指针可以产生一个大小可变的滑动窗口。

反向扫描

891 · 有效回文 II

题目描述

给你一个字符串,在最多删去一个字符的情况下,判断该字符串是否为回文。

思路

我们还是用判断是否为回文的基本方法——反向指针法 用两个指针i,j。i指向字符串的头,j指向字符串的尾 如果两个指针指向的字符相等,那么就相对走一步——i ,j-- 如果不相等,就有两种情况:

  • 把i指向的字符删去——i
  • 把j指向的字符删去——j--

这两种情况我们都需要考虑的。

ac代码

代码语言:javascript复制
cppclass Solution {
public:
    bool validPalindrome(string &s) {
        int i=0,j=s.size()-1;
        while(i<j)
        {
            if(s[i]==s[j])
            {
                i  ;
                j--;
            }
            else//指向不同,进行下面两种情况的判断
            break;
        }
        int a=i 1, b=j--;
        //a,b是第一种情况,查找的是[i 1,j]
        //i,j是第二种情况,查找的是[i,j-1]
        while(a<b&&i<j)//他们之间的区间长度是一样的
        {
            if(s[a]==s[b]||s[i]==s[j])//只要满足其中一个条件就可以
            {
                a  ;
                b--;
                i  ;
                j--;
            }
            else
            return false;
        }
        return true;
    }
};

15. 三数之和

题目描述

给定一组数据,从这组数据中从中选取3个数——a,b,c。使得这三个数的和为0,即a b c=0。返回这样的三元组,要求返回的三元组中的数据不能有重复

思路

暴力的循环肯定是时间复杂度太高,不能用。 该到题目可以用排序 双指针的算法,要注意题目中对返回三元组的去重。 排序之后,如果当nums[i]>0的时候,就不需要进行后面数的遍历了,因为对于一个升序的数组,后面的数据都是大于0的,其结果不会等于0。同时在遍历的时候,在判断下一个数据(nums[i 1])的时候,也需要去重——即比较nums[i]与nums[i-1]不能相等。 双指针:i遍历数组中的元素——nums[i]看成a,用两个指针L,R控制b,c所在的区间即——nums[L]=b,nums[R]=c

  • 当a b c=0的时候,进入返回结果中,同时对区间进行去重,去重之后再进行迭代
  • 当a b c>0的时候,我们想要把结果变小,就要缩短右边,在缩短右边的时候,先进行去重,然后再R--
  • 当a b c<0的时候,我们想要把结果变大,就要缩短左边,在缩短左边的时候,先进行去重,然后再L

去重:循环比较自己和下一个数是否相等,即:nums[L]与nums[L 1],nums[R]与nums[R-1]进行比较。

代码语言:javascript复制
cppclass Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ret;
        sort(nums.begin(),nums.end());
        int L,R;
        for(int i=0;i<nums.size();i  )
        {
            if(nums[i]>0)//后面不可能有数据使他们的和为0
            break;
            if(i>0&&nums[i]==nums[i-1])//当前数据不能和之前的数据相同——去重
            continue;
            //设置区间
            L=i 1;
            R=nums.size()-1;
            while(L<R)
            {
                int sum =nums[i] nums[L] nums[R];
                if(sum==0)
                {
                    ret.push_back({nums[i],nums[L],nums[R]});
                    //去重
                    while(L<R&&nums[L]==nums[L 1])
                    L  ;
                    while(L<R&&nums[R]==nums[R-1])
                    //下一个
                    R--;
                    L  ;
                    R--;
                }
                else if(sum>0)
                {
                    //去重
                    while(L<R&&nums[R]==nums[R-1])
                    R--;
                    //下一个
                    R--;                
                }
                else
                {
                    //去重
                    while(L<R&&nums[L]==nums[L 1])
                    L  ;
                    //下一个
                    L  ;
                }
            }
        }
        return ret;
    }
};

正向扫描

快慢指针

3061 -- Subsequence

题目描述

给你一组数,该组数的每个数的范围都是大于10小于100000的。再给定一个值S,找出最短的连续区间,使区间和大于或者等于S

思路

显然我们用快慢指针就可以解决这个问题,指针i,j。 i为左区间,j为右区间,sum为i到j的区间和。 当sum大于s时,停止扫描,记录区间的长度(同时进行区间长度的判断),然后i ,继续另一个空间,直到区间完全扫描完。 最后返回最短的区间长度。

ac代码
代码语言:javascript复制
cpp#include <iostream>
#include <vector>

using namespace std;

int main()
{
    int n;
    cin >> n;
    while (n--)
    {
        int v, s;
        
        cin >> v >> s;
        int len = v;
        vector<int> a(v,0);
        for (int i = 0; i < v; i  )
            cin >> a[i];
        int i = 0, j = 0;
        int sum = 0;
        while (i<v&&j<v)
        {
            sum  = a[j];
            j  ;
            while(sum>=s)
            {
                sum -= a[i];
                if (len > j - i)
                    len = j - i;
                i  ;
            }
        }
        if (len == v)
            cout << 0 << endl;
        else
        cout << len << endl;
    }
    return 0;
}

234. 回文链表

题目描述

判断一个单链表是不是回文链表

思路

完整代码有注释

  • 快慢指针找出中间结点
  • 把后一半链表逆置
  • 遍历链表,进行判断
  • 还原链表
  • 时间复杂度O(N),空间复杂度O(1)
ac代码
代码语言:javascript复制
cppclass Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(!head->next)
        return true;
        //获取中间结点
        ListNode* mid= findMid(head);
        //反转后半部分结点
        ListNode* rmid= reverseList(mid);
        //判断回文
        ListNode* cur=rmid;
        while(head&&cur)
        {
            if(head->val!=cur->val)
            {
                //反转恢复
                reverseList(rmid);
                //不是回文
                return false;
            }
            head=head->next;
            cur=cur->next;
        }
        //反转恢复
        reverseList(rmid);
        //是回文
        return true;
    }
        ListNode* findMid(ListNode* h)
    {
        //快慢指针找到中间的结点
        ListNode* s,*q;//s慢指针,q快指针
        s=q=h;
        while(q)
        {
            s=s->next;
            q=q->next;
            if(q)
                q=q->next;
        }
        return s;
    }
    ListNode* reverseList(ListNode* h)
    {
        ListNode* cur=h->next;
        ListNode* curpro=h;
        while(cur)
        {
            ListNode* t=cur->next;
            cur->next=h;
            curpro->next=t;
            //更新头
            h=cur;
            cur=t;
        }

        return h;
    }
};

多指针

洛谷P1102

题目描述

给定一组数和一个目标值C,从这组数中选取2个数A,B。求像A-B=C,这种种数对有多少个。

思路
  • 多指针法

首先我们先排好序。 对于一个排好序的序列,如果该序列中没有重复的数字的话,完全可以用双指针解决该问题;但是因为可能会出现相同的数据,所以我们要用一个区间去控制这些相等的数。 因为排好数之后,数字是单调不减的,所以该区间会随着遍历往上增长。

  • 二分

先排序O(nlogn),依次遍历数组,把遍历的数据减去C,然后用二分查找观察减去的结果是否在原数据中。但是这样会超时,因为存在相同的数据,那么查找的时间的时间复杂度为O(KlogN),最bt的情况就是当k为n的时候,那么该算法的时间复杂度N2logNN^2logNN2logN,将超时

代码语言:javascript复制
cpp#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int bs(vector<int>& a, int t)
{
	int i = 0, j = a.size() - 1;
	int ret=0;
	while (i <= j)
	{
		int mid = (i   j) >>1;
		if (a[mid] == t)
			ret  ;
		else if (a[mid] < t)
		{
			i = mid   1;
		}
		else
		{
			j = mid - 1;
		}
	}
	return ret;
}
int main()
{
	int n, c;
	cin >> n >> c;
	vector<int> arr(n);
	for (int i = 0; i < n; i  )
		cin >> arr[i];
	sort(arr.begin(), arr.end());
	int l=0, r=0;
	long long ret = 0;
	for (int i = 0; i < n; i  )
	{
		ret =bs(arr, arr[i] - c);

	}
	cout << ret << endl;
	return 0;
}
  • map

二分超时是因为重复的值导致的查找变慢,我们用map进行查找就可以,map的底层是红黑树,查找时复杂度O(logN)——查找的是arr[i]-c,map中存储的是arr[i]存储的时间复杂度O(NlogN),总体时间复杂度O(NlogN),多开了O(N)个空间。用哈希也可以的,空间浪费是更大了。

ac代码
代码语言:javascript复制
cpp#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
	int n, c;
	cin >> n >> c;
	vector<int> arr(n);
	for (int i = 0; i < n; i  )
		cin >> arr[i];
	sort(arr.begin(), arr.end());
	int l=0, r=0;
	long long ret = 0;
	for (int i = 0; i < n; i  )
	{
		while (l < n && arr[l] - arr[i] < c) l  ;//左闭
		while (r < n && arr[r] - arr[i] <= c) r  ;//右开

		ret  = r - l;
	}

	cout << ret << endl;
	return 0;
}
代码语言:javascript复制
cpp#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;

int main()
{
	int n, c;
	cin >> n >> c;
	vector<int> arr(n);
	map<int, int> m;
	for (int i = 0; i < n; i  )
	{
		cin >> arr[i];
		m[arr[i]]  ;
	}
	long long ret = 0;
	for (int i = 0; i < n; i  )
	{
		ret  = m[arr[i] - c];
	}
	cout << ret << endl;
	return 0;
}
代码语言:javascript复制
cpp#include <iostream>
#include <algorithm>
#include <vector>
#include <unordered_map>
using namespace std;

int main()
{
	int n, c;
	cin >> n >> c;
	vector<int> arr(n);
	unordered_map<int, int> m;
	for (int i = 0; i < n; i  )
	{
		cin >> arr[i];
		m[arr[i]]  ;
	}
	long long ret = 0;
	for (int i = 0; i < n; i  )
	{
		ret  = m[arr[i] - c];
	}
	cout << ret << endl;
	return 0;
}

1. 两数之和

题目描述

给定一个数组num和一个目标值target,找到两个和为target的下标。

思路

先把数及其下标存入哈希表中,然后查找target-nums[i]的是不是在哈希表中。 注意:要查找的值不能是自己本身。具体的实现看下面的代码

ac代码

代码语言:javascript复制
cppclass Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> ret;
        unordered_map<int,int> m;
        for(int i=0;i<nums.size();i  )
        {//先查找在插入
            auto r=m.find(target-nums[i]);
            if(r!=m.end())
            {
                return {r->second,i};
            }
            m[nums[i]]=i;   
        }

        return ret;
    }
};

0 人点赞