算法与数据结构--- 双指针和尺取法

一、 双指针和尺取法 点击看详细知识点

1、Subsequence—子序列问题

题面翻译

题目描述:

有一个长度为 N N N的正整数序列 ( 10 < N < 100 , 000 ) (10 < N < 100,000) (10<N<100,000),每一个数字都小于等于 10000 10000 10000,再给定一个正整数 S S S ( S < 100 , 000 , 000 ) (S < 100,000,000) (S<100,000,000),试求一个连续子序列,使得该序列的数字之和大于或等于 S S S,并且要求该子序列尽量短。

输入输出格式

输入格式:

输入有多组数据。每组数据第一行为两个正整数 N N N S S S,中间用空格隔开。第二行给出这个序列,每两个整数之间用空格隔开。输入文件以EOF结尾。当这个连续子序列不存在时输出0

输出格式:

对于每组数据,输出一个整数,代表满足要求的最短子序列的长度。每组数据输出占一行。

输入输出样例

输入样例:

10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5

输出样例:

2
3

题目描述

PDF

分析

尺取法 (O(n)).
用s和t表示子序列的起点和终点,sum表示子序列[s,t)的和,ans表示最短的符合要求的子序列长度。

  1. 将t向后移动,并更新sum,直到sum≥S∣∣t=N,如果t=N&&sum<S,就退出,否则更新ans。

  2. 将s向后移动,并更新sum,再回到步骤1。

代码

#include <iostream>
#include <algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e5+5;
ll s[N];
int main()
{
	ll n;	
	cin>>n;
	while(n--){
		ll a,b;
		ll sum=0;
		cin>>a>>b;//输入序列长度 和要找的目标值
		memset(s,0,sizeof(s)); //每次循环前清空数组
	for(ll i = 1;i <= a;i++){
		cin>>s[i];//输入序列 
	}
	int j=1,i=1,ans=a+1;
	while(1){
		while(sum<b&&j<=a){
			sum+=s[j];
			j++;
		}
		if(sum>=b){
			ans=min(ans,j-i);//最小长度 
		    sum-=s[i];
		    i++; 
		}
		else break;	
		}
		if(ans>a) cout<<"0"<<endl;
     	else cout<<ans<<endl;	
	}    
    return 0;
}

2、Bound Found

Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 5078 Accepted: 1617 Special Judge

Description

Signals of most probably extra-terrestrial origin have been received and digitalized by The Aeronautic and Space Administration (that must be going through a defiant phase: “But I want to use feet, not meters!”). Each signal seems to come in two parts: a sequence of n integer values and a non-negative integer t. We’ll not go into details, but researchers found out that a signal encodes two integer values. These can be found as the lower and upper bound of a subrange of the sequence whose absolute value of its sum is closest to t.

You are given the sequence of n integers and the non-negative target t. You are to find a non-empty range of the sequence (i.e. a continuous subsequence) and output its lower index l and its upper index u. The absolute value of the sum of the values of the sequence from the l-th to the u-th element (inclusive) must be at least as close to t as the absolute value of the sum of any other non-empty range.
Input

The input file contains several test cases. Each test case starts with two numbers n and k. Input is terminated by n=k=0. Otherwise, 1<=n<=100000 and there follow n integers with absolute values <=10000 which constitute the sequence. Then follow k queries for this sequence. Each query is a target t with 0<=t<=1000000000.
Output

For each query output 3 numbers on a line: some closest absolute sum and the lower and upper indices of some range where this absolute sum is achieved. Possible indices start with 1 and go up to n.

Sample

InputOutput
5 1
-10 -5 0 5 10
35 4 4
10 2
-9 8 -7 6 -5 4 -3 2 -1 0
55 2 8
119 1 1
15 2
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
1515 1 15
10015 1 15
0 0

分析

题意:给你n个数,让你找一个子区间使得该子区间之和的绝对值和t的差尽可能小,输出该区间的和,以及区间的左右端点。

题解:因为数组中的数有正有负,故不满足尺取单调性的要求,我们可以考虑求前缀和,因为两个前缀和相减对应的是任意一个区间的和,因为有绝对值,所以不用考虑端点的先后顺序,我们将所有前缀和从小到大排序,然后按照尺取遍历即可。

代码

#include<math.h>
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
int a[100005],l,r,n,k;
ll sm,ans,t;
const long long N = 1e19;
ll abss(ll x)
{
	if(x<0)
		x=-x;
	return x;
}//绝对值函数
struct node
{
	ll s;//数字 
	ll p;//位置 
}sum[100005];
bool comp(node a,node b)
{
	return a.s<b.s;//升序输出 
}
int main()
{
	int i;
	while(cin >> n >> k && n && k)
	{
		sum[0].p=sum[0].s=0;
		for(i=1;i<=n;i++){
			scanf("%d",&a[i]);
			sum[i].s = sum[i-1].s+a[i]; //前缀和 
			sum[i].p = i;
		}
			
		sort(sum , sum+n+1 , comp);//将前缀和按照数的大小升序输出 
		while(k--) 
		{
			ans=N;
			cin >> t;
			l = 0;
			r = 1; 
			ll ans1;
		    int ls,rs;
			while(l <= n && r <= n)
			{
				ll tmp = abss( sum[l].s - sum[r].s);
				ll tmp1 = abss(tmp - t);
				if( tmp1 < ans)
				{
					ans = tmp1;//tmp1更接近t 
					ans1 = tmp;//此时的区间差 
					ls = sum[l].p;//左边界的位置 
					rs = sum[r].p;//右边界的位置 
				}
				if(tmp > t) l++; 
				else if(tmp < t) r++;
				else break;
				if(l==r) r++;
			}
			if(ls > rs) swap(ls,rs);
			printf("%lld %d %d\n",ans1,ls+1,rs);
		}
	}
	return 0;
}

前缀和

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    int n;
    cin >> n;
    int sum[100005] = {0};
    for (int i = 1; i <= n;i++){
        int x;
        cin >> x;
        sum[i] = sum[i - 1] + x;
    }
    // sum(l,r) = sum[r] - sum[l - 1] 先O(n)预处理前缀和,O(1)计算每个区间和
    int q;
    cin >> q;
    while(q--){
        int l, r;
        cin >> l >> r;
        cout << sum[r] - sum[l - 1] << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t--){
        solve();
    }
    return 0;
}

关于ios::sync_with_stdio(false);和cin.tie(0);cout.tie(0);

http://t.csdn.cn/poBjm

3、# A-B 数对

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N , C N,C N,C

第二行, N N N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 A − B = C A - B = C AB=C 的数对的个数。

样例 #1

样例输入 #1

4 1
1 1 2 3

样例输出 #1

3

提示

对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1N2×105 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0ai<230 1 ≤ C < 2 30 1 \leq C < 2^{30} 1C<230

2017/4/29 新添数据两组

分析

我们考虑题目要求求出所有A-B=C的数对,我们可以先将原数组排序,然后就会发现每个数
A,对应的数B一定是一段连续的区间。

  1. 然后我们再考虑如何去找到这个区间。
  2. 我们显然是要找到这个连续区间的左端点和右端点。
  3. 考虑到排序之后序列的有序性,我们枚举每个数,他们的左端点和右端点都是单调不降的,因此我们可以用 two-pointers 也就是双指针来维护这个东西。
  4. 具体的实现就是,我们维护两个右端点r1 , r2,每次r1右移到a[r1] - a[l] <= c的最后位置的下一位, r2右移到满足a[r2] - a[l] < c最后一位.
  5. 也就是说, 此时如果a[r2] - a[l] == c && a[r1 - 1] - a[l] == c,中间的那一段一定都是满足条件的,我们让ans += r1 - r2即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 2e5 + 10;
int n , c;
int a[N];

int main () 
{
	cin >> n >> c;
	for(int i = 1 ; i <= n ; i ++)
    cin >> a[i];
	sort(a + 1 , a + 1 + n);//一定要排序
	int l = 1, r1 = 1 , r2 = 1;
	ll ans = 0;
	for(l = 1 ; l <= n ; l ++) {
		while(r1 <= n && a[r1] - a[l] <= c) r1 ++;
		while(r2 <= n && a[r2] - a[l] < c ) r2 ++;
		if(a[r2] - a[l] == c && a[r1 - 1] - a[l] == c && r1 - 1 >= 1) 	
			ans += r1 - r2;
	}
	cout << ans;
	return 0;
}

4、Problem A: Unique Snowflakes—最长不同连续子序列

题目 描述

Emily the entrepreneur has a cool business idea: packaging and sellingsnowflakes. She has devised a machine that captures snowflakes as theyfall, and serializes them into a stream of snowflakes that flow, one byone, into a package. Once the package is full, it is closed andshipped to be sold.
The marketing motto for the company is "bags of uniqueness."To live up to the motto, every snowflake in a packagemust be different from the others. Unfortunately, this iseasier said than done, because in reality, many of the snowflakesflowing through the machine are identical. Emily would like toknow the size of the largest possible package of unique snowflakesthat can be created. The machine can start filling the packageat any time, but once it starts, all snowflakes flowing fromthe machine must go into the package until the package is completedand sealed. The package can be completed and sealed before all ofthe snowflakes have flowed out of the machine.

Input Specification

The first line of input contains one integer specifying the number oftest cases to follow. Each test case begins with a line containingan integer n, the number of snowflakes processed by the machine.The following n lines each contain an integer (in the range0 to 10^9, inclusive) uniquely identifyinga snowflake. Two snowflakes are identified by the same integer ifand only if they are identical.The input will contain no more than one million total snowflakes.
Sample Input
1
5
1
2
3
2
1

Output Specification

For each test case output a line containing single integer, the maximumnumber of unique snowflakes that can be in a package.
Output for Sample Input

3

题意:

给n个数,求最长不同连续子序列。n<=1e6。

解题过程:

1.记录数据存于数组

2.用左右指针l和r指向这段连续区间

3.右指针往右走,如果遇到没有存在于set集合的数就插入集合

否则左指针往右走,逐渐删去set里的数据,不断更新答案,一直保留最大值。

代码

#include<iostream>
#include<set>
using namespace std;
const int maxn = 1e6 + 5;
int A[maxn];
int main(){
    int T,n;
    cin>>T;
    while(T--){
        cin>>n;
        for(int i = 0; i < n; i++)
		cin>>A[i];
        set<int> s;
        int i = 0, j = 0,ans = 0;
        while(j < n){
            while(j<n&&!s.count(A[j])) 
			s.insert(A[j++]);
            ans=max(ans,j-i);
            s.erase(A[i++]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

数组去重

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    int n;
    cin >> n;
    int a[100005];
    for (int i = 1; i <= n;i++){
        cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    int i = 1, j = 1;
    while (j < n){
        j++;
        if(a[j] != a[i]){
            i++;
            a[i] = a[j];
        }   
    }
    for (int k = 1; k <= i;k++){
        cout << a[k] << " ";
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t--){
        solve();
    }
    return 0;
}
/*
6 
1 2 2 3 3 3

1 2 3
*/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值