一、题目
1、原题链接
1460. 我在哪?
2、题目描述
农夫约翰出门沿着马路散步,但是他现在发现自己可能迷路了!
沿路有一排共 N 个农场。
不幸的是农场并没有编号,这使得约翰难以分辨他在这条路上所处的位置。
然而,每个农场都沿路设有一个彩色的邮箱,所以约翰希望能够通过查看最近的几个邮箱的颜色来唯一确定他所在的位置。
每个邮箱的颜色用 A…Z 之间的一个字母来指定,所以沿着道路的 N 个邮箱的序列可以用一个长为 N 的由字母 A…Z
组成的字符串来表示。
某些邮箱可能会有相同的颜色。
约翰想要知道最小的 K 的值,使得他查看任意连续 K 个邮箱序列,他都可以唯一确定这一序列在道路上的位置。
例如,假设沿路的邮箱序列为 ABCDABC 。
约翰不能令 K=3,因为如果他看到了 ABC,则沿路有两个这一连续颜色序列可能所在的位置。
最小可行的 K 的值为 K=4,因为如果他查看任意连续 4 个邮箱,那么可得到的连续颜色序列可以唯一确定他在道路上的位置。
输入格式
输入的第一行包含 N,第二行包含一个由 N 个字符组成的字符串,每个字符均在 A…Z 之内。
输出格式
输出一行,包含一个整数,为可以解决农夫约翰的问题的最小 >K 值。
数据范围
1≤N≤100
输入样例:
7
ABCDABC
1
2
输出样例:
4
1
二、解题报告
1、思路分析
思路来源:AcWing 1460. 我在哪?(蓝桥杯集训·每日一题)
y总yyds
数据量为100,时间复杂度控制在 O(n3) 左右。
暴力解法:
(1)题目要求的是长度最小的子串,且满足该子串与任意一个子串都不相同,求此最小长度k。
(2)暴力枚举,第一层枚举所有k的可能取值,第二层枚举长度为k的所有子串,第三层判断以当前长度k作为答案是否满足条件(即判断是否存在长度为k的子串与当前子串相同,如果都不相同,则k满足条件直接输出,否则继续查找),直到找到答案为止。
二分+哈希优化:
(1)在暴力基础上,利用二分来查找满足条件的k(因为k是满足条件的最小长度,所以小于k的的数一定不满足条件,而大于等于k的一定满足条件,具有二段性,可以二分),取代第一层循环。
(2)利用哈希表来查找是否存在长度为k,与当前子串长度相同的子串,取代字符串相等的比较。
2、时间复杂度
暴力解法时间复杂度最坏情况O(n4)
优化解法时间复杂度O(n2logn)
3、代码详解
暴力解法代码
#include <iostream>
#include <string>
using namespace std;
int n;
string s;
int main(){
cin>>n;
cin>>s;
for(int k=1;k<=n;k++){ //枚举k的所有可能取值
bool flag=true; //记录当前k是否满足条件
for(int i=0;i<n-k+1;i++){ //枚举长度为k的子串
for(int j=i+1;j<n-k+1;j++){ //枚举剩余字符串的子串中长度为k的子串
if(s.substr(i,k)==s.substr(j,k)){ //如果存在与当前长度为k的子串相同的子串,则当前k满足条件,直接跳出循环
flag=false;
break;
}
}
if(!flag) break; //当前k不满足条件,直接跳出循环
}
if(flag){ //当前k满足条件,输出k,跳出循环,程序结束
cout<<k;
break;
}
}
return 0;
}
优化代码
#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;
int n;
string s;
unordered_set<string> st; //哈希表存储每个子串
//判断长度为x作为答案是否满足条件
bool check(int x){
//枚举所有长度为x的子串
for(int i=0;i<n-x+1;i++){
string tmp=s.substr(i,x);
if(!st.count(tmp)) st.insert(tmp); //如果不存在与该子串相同的子串,则将该子串放入哈希表中
else return false; //如果存在与该子串相同的子串,则说明不满足条件,直接返回false
}
return true; //如果遍历完所有长度为x的子串且这些子串互不相等,则x满足题目要求,返回true
}
int main(){
cin>>n;
cin>>s;
int l=1,r=n; //答案k范围在区间[1,n]
//二分查找答案
while(l<r){
int mid=l+r>>1;
if(check(mid)) r=mid; //如果mid满足条件,则说明mid比答案k大,将搜索区间缩小为[l,mid]
else l=mid+1; //如果mid不满足条件,则说明mid比答案k小,将搜索区间缩小为[mid+1,r]
}
cout<<l;
return 0;
}
三、知识风暴
二分查找
二分查找可以快速地进行查找,每次将区间缩小一半,只要符合某个数只有两种情况:满足条件或者不满足条件,就可以用二分来查找满足条件或者不满足条件的分界点。
哈希表
哈希表存储一种映射关系,可以快速地进行查找,STL中的map、set等容器就是基于哈希表。
关于代码中涉及的一些操作:
unordered_set 是无序的set,而且对容器中元素去重。
count() 用于查找某个数(或字符或字符串)出现的次数。
insert() 向哈希表中插入一个元素。