最字头之四:最长不重复字串

文章作者:Yx.Ac   文章来源:勇幸|Thinking (http://www.ahathinking.com)   转载请注明,谢谢合作。

---

注:代码为本人参考作者实现的。并非作者的代码,如果有错误请见谅!

题:从一个字符串中找到一个连续子串,该子串中任何两个字符不能相同,求子串的最大长度并输出一条最长不重复子串。

本节从最直接的方法逐步优化,渐进探索了四种实现方式,并最终找到时间复杂度为O(N),辅助空间为常数的方案,内容如下:

==基本算法 使用Hash==

==DP方案==

==DP + Hash 方案==

==DP + Hash 优化方案==

==================================

基本算法 使用Hash

要求子串中的字符不能重复,判重问题首先想到的就是hash,寻找满足要求的子串,最直接的方法就是遍历每个字符起始的子串,辅助hash,寻求最长的不重复子串,由于要遍历每个子串故复杂度为O(n^2),n为字符串的长度,辅助的空间为常数hash[256]。代码如下:

#include <iostream>
#include <string>
#include <memory.h>
using namespace std;

int main(){
   string str;
   int maxlen = 0;
   int index = 0;
   int visit[27];
   cin >> str;
   int size = str.size();
   for(int i = 0; i < size; i++){
	  memset(visit,0,sizeof(visit));
      for(int j = i;j < size; j++){
	     if(visit[str[j]-96] == 0){
		    visit[str[j]-96] = 1;
		 }else{
		    if(j - i > maxlen){
			   maxlen = j - i;
			   index = i;
			}
			break;//the point!
		 }
	  }
   }
   for(int i = index; i < index + maxlen; i++)
	   cout << str[i];
   cout << endl;
   return 0;
}

DP方案

前面刚刚讨论过最长递增子序列的问题,咋一想就觉得二者有点类似,何不向DP方面想一下,为什么说二者类似,在LIS问题中,对于当前的元素,要么是与前面的LIS构成新的最长递增子序列,要么就是与前面稍短的子序列构成新的子序列或单独构成新子序列;

同理,对于最长不重复子串,某个当前的字符,如果它与前面的最长不重复子串中的字符没有重复,那么就可以以它为结尾构成新的最长子串;如果有重复,且重复位置在上一个最长子串起始位置之后,那么就与该起始位置之后的稍短的子串构成新的子串或者单独成一个新子串。

举个例子:例如字符串“abcdeab”,第二个字符a之前的最长不重复子串是“abcde”,a与最长子串中的字符有重复,但是它与稍短的“bcde”串没有重复,于是它可以与其构成一个新的子串,之前的最长重复子串“abcde”结束;

再看一个例子:字符串“abcb”,跟前面类似,最长串“abc”结束,第二个字符b与稍短的串“c”构成新的串;

这两个例子,可以看出些眉目:当一个最长子串结束时(即遇到重复的字符),新的子串的长度是与第一个重复的字符的下标有关的,如果该下标在上一个最长子串起始位置之前,则dp[i] = dp[i-1] + 1,即上一个最长子串的起始位置也是当前最长子串的起始位置;如果该下标在上一个最长子串起始位置之后,则新的子串是从该下标之后开始的

于是类似LIS,对于每个当前的元素,我们“回头”去查询是否有与之重复的,如没有,则最长不重复子串长度+1,如有,则考察上一个子串起始位置与重复字符下标的关系,当然,如果DP使用O(n^2)的方案,则我们只需在内层循环遍历到上一个最长子串的起始位置即可,如下。

O(N^2)的DP方案,我们可以与LIS的DP方案进行对比,是一个道理的。代码如下:

作者的代码:

/* LNRS dp */
int dp[30];
 
void LNRS_dp(char * arr, int size)
{
    int i, j;
    int last_start = 0;     // 上一次最长子串的起始位置
    maxlen = maxindex = 0;
 
    dp[0] = 1;
    for(i = 1; i < size; ++i)
    {
        for(j = i-1; j >= last_start; --j) // 遍历到上一次最长子串起始位置
        {
            if(arr[j] == arr[i])
            {
                dp[i] = i - j;
                last_start = j+1; // 更新last_start
                break;//Point!!!
            }else if(j == last_start) // 无重复
            {
                dp[i] = dp[i-1] + 1;
            }
        }
        if(dp[i] > maxlen)
        {
            maxlen = dp[i];
            maxindex = i + 1 - maxlen;
        }
    }
    output(arr);
}
我的实现代码:

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
//bool contains(char x,string str){
   // bool con = false;
  // for(int i = 0; i < str.size(); i++)
	//   if(str[i] == x)
	//	   return true;
  // return false;
//}
int contains(char x,string str,int index){
   for(int i = 0; i < str.size(); i++)
	   if(str[i] == x)
		   return i + index;
   return -1;
}
int main(){
  string str;
  int maxlen = 0;
  int index = 0;
  int forindex = 0;
  int dp[40] = {0};
  cin >> str;
  dp[0] = 1;
  for(int i = 1; i < str.size(); i++){
     if(contains(str[i],str.substr(forindex,i-forindex),forindex) == -1){
	    dp[i] = dp[i-1] + 1;
		if(dp[i] > maxlen){
		  maxlen = dp[i];
		  index = i;
		}
	 }
	 else{
	    forindex = contains(str[i],str.substr(forindex,i-forindex),forindex)+1;
		dp[i] = i - forindex + 1;
		if(dp[i] > maxlen){
		  maxlen = dp[i];
		  index = i;
		}
	 }
  }
  //sort(dp,dp+str.size());
  for(int i = index - maxlen + 1; i <= index; i++)
	  cout << str[i];
  cout << endl;
  return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值