算法:双指针

文章介绍了如何通过优化算法,将暴力遍历的时间复杂度从O(n^2)降低到O(n),主要方法是利用两个指针i和j的动态调整,保持子段满足特定条件。示例包括分割字符串、寻找无重复数字的连续子序列以及找到最少成本的美术馆参观票。
摘要由CSDN通过智能技术生成

暴力:遍历所有子段

时间复杂度为O(n^2)

for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
 }
}

优化:

i,j总共走2n,则时间复杂度为O(n)

法一:

j:当i移动到一个位置时,j往左最远能到什么位置,使此时的i,j为满足的子段

j指向该满足题意子段的起点,i指向该满足题意子段的终点

第一层写for,第二层写while(j<终点&&check(i,j))j++;
for(int i=0;j=0;i<n;i++){
while(j<=i&&check(j,i)) j++;
//每道题的具体逻辑
}

适用于后面的指针i从1到n遍历,前面的指针j随之变动,维持区间满足要求

当然,将j<=i改为j<n也可以后面指针j移动维持区间满足要求

(其中一个指针不停地严格地一个一个往后移,另一个指针来维持区间满足要求)

while循环用以维持区间满足要求

注意边界,可能后面的指针出边界了,但是仍将其作为答案

 法二:利用i,j两个指针,i,j都从头开始走,然后j先开始移动,使得[i,j]为满足题意的区间子段,然后j往后一格,i移动维持[i,j]仍满足题意 

while(i<=j&&j<=n){
if(){
j++;
}
else{
i++;
}
}

既可以后面指针一个一个移动,前面指针维持区间满足要求,也可以前面指针一个一个移动,后面指针满足区间要求 

利用if和else 使得两个指针的移动每次都二选一,且每次只移动一格,只要考虑什么时候前面的指针移动,什么时候后面的指针移动(全部都写在if和else里面)

注意,因为每次只移动一格,因而不是每时每刻区间都是满足的,所以,提取答案时要有条件,一是保证区间满足题意,二是边界,可能后面的指针出边界了,但是仍将其作为答案

 例1:

输入:

abc def ghi

输出:

abc

def

ghi

法一: 

#include<iostream>
#include<string.h>
using namespace std;
int main()
{
char str[1000];
gets(str);
int n=strlen(str);
for(int i=0;i<n;i++){
int j=i;
while(j<n&&str[j]!=' ') j++;
for(int k=i;k<j;k++) cout<<str[k];
cout<<endl;
i=j;
}
return 0;
}

法二:

#include<iostream>
#include<string.h>
using namespace std;
int main()
{
char str[1000];
gets(str);
int n=strlen(str);
str[n]=' ';
int i=0,j=0;
while(i<=j&&j<=n){
if(str[j]!=' '){
j++;
}
else{
for(int k=i;k<=j;k++) cout<<str[k];
cout<<endl;
i=j+1;
j=i;
}
}
return 0;
}

 例2:

给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度

s数组用于计数,桶的计数

当i向右移动的时候,那么[j,i]区间子段就加了一个数,由于加了一个数,那么该子段就可能会有重复的数,而该重复的数只可能由加入的这个数产生,因此如果s[a[i]]>1,则说明重复,于是j移动,使得[i,j]区间子段维持没有重复数字

s[a[j]]--表示[i,j]区间子段减去一个数,桶a[j]计数相应减去一个

s[a[i]]++表示[i,j]区间子段加上一个数,桶a[i]计数相应加上一个

法一: 

#include<iostream>
using namespace std;
const int N=100010;
int a[N],s[N];
int main()
{
   int n;
   cin>>n;
   for(int i=0;i<n;i++) cin>>a[i];
   int res=0;
   for(int i=0,j=0;i<n;i++){
     s[a[i]]++;
    while(j<=i&&s[a[i]]>1){
      s[a[j]]--;
      j++;
    }
     res=max(res,i-j+1);
   }
   cout<<res<<endl;
   return 0;
}

法二:

#include<iostream>
using namespace std;
const int N=1e6+5;
int a[N],s[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int res=0;
int i=1,j=1;
s[a[1]]++;
while(i<=j&&j<=n){
if(s[a[j]]>1){
s[a[i]]--;
i++;
}
else{
j++;
s[a[j]]++;
}
if(s[a[j]]==1&&j<=n) res=max(res,j-i+1);
}
cout<<res<<endl;
return 0;
}

例3: 

博览馆正在展出由世上最佳的 m 位画家所画的图画。

游客在购买门票时必须说明两个数字a和b,代表他要看展览中的第a幅至第b幅画(包含 a,b)之间的所有图画,而门票的价钱就是一张图画一元。

W不仅仅是一个acmer,还是一个艺术爱好者。

所以W希望入场后可以看到所有名师的图画。

于是他让可怜的工具人Tree去帮助他买到最便宜的票。

你能帮忙Tree算出花费最少的票(a,b)吗

如果有多种最便宜的票请输出a最小的一组

前面的指针一个一个移动,后面的指针维持区间满足要求 

法一:

#include<iostream>
using namespace std;
const int N=1e6+5;
int a[N],s[N];
int main()
{
int n,m,t,cnt=1;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int res=1e6+5,x=0,y=0;
s[a[1]]++;
for(int i=1,j=1;i<=n;i++){
    while(j<n&&cnt!=m){
    	j++;
    	s[a[j]]++;
    	if(s[a[j]]==1) cnt++;
	}
	if(cnt==m){
		if(res>j-i+1){
			res=j-i+1;
			x=i;
			y=j;
		}
		s[a[i]]--;
		if(s[a[i]]==0) cnt--; 
	}
}
cout<<x<<' '<<y<<endl;
return 0;
}   

法二: 

#include<iostream>
using namespace std;
const int N=1e6+5;
int a[N],s[N];
int main()
{
int n,m,t,cnt=1;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int res=1e6+5,x=0,y=0;
int i=1,j=1;
s[a[1]]++;
while(i<=j&&j<=n){
if(cnt!=m){
j++;
s[a[j]]++;
if(s[a[j]]==1) cnt++;
}
else{
if(res>j-i+1){
res=j-i+1;
x=i;
y=j;
}
s[a[i]]--;
if(s[a[i]]==0) cnt--;
i++;
}
}
cout<<x<<' '<<y<<endl;
return 0;
}                    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值