最长不重复子串

最长不重复子串

题目描述:

   最长不重复子串(Longest No Repeat String,LNRS)就是从一个字符串中找到一个连续子串,该子串中任何两个字符都不能相同,且该子串的长度是最大的。

分析:

解法一:动态规划

  动态规划就是用来解决这种最优化问题,关于字符串的很多有趣的问题如最长公共自序列,最长上升子序列等都可以用动态规划来解,这道题我的第一想法也是动态规划。

  动态规划的核心在于寻找最优子结构,对于一个字符,如果他与他前面的最长不重复子串都没有相同的字符,那么他也可以加入这个子串中,构成一个新的子串。即对于字符数组a[],dp[i]表示以a[i]为结尾的最长不重复子串长度,dp[0] = 1,最后取dp中的最大值即可。代码如下:

#include <cstdio>
#include <cstdlib>
#include <string.h>

using namespace std;
char in[10001];

int dp[10001];

int LNRS_dp(char *in)
{
int i, j;
int len = strlen(in);
int last = 0; // 上一次最长子串的起始位置
int maxlen,maxindex;
maxlen = maxindex = 0;

dp[0] = 1;
for(i = 1; i < len; ++i)
{
for(j = i-1; j >= last; --j) // 遍历到上一次最长子串起始位置
{
if(in[j] == in[i])
{
dp[i] = i - j;
last = j+1; // 更新last_start
break;
}else if(j == last) // 无重复
{
dp[i] = dp[i-1] + 1;//长度+1
}
}
if(dp[i] > maxlen)
{
maxlen = dp[i];
}
}
return maxlen;
}

int main()
{
freopen("1530.in","r",stdin);
freopen("1530.out","w",stdout);

while(scanf("%s",in)!=EOF)
{
printf("%d ",LNRS_dp(in));
}
return 0;
}

复制代码
 1 #include <cstdio>
 2 #include <cstdlib>
 3 #include <string.h>
 4 
 5 using namespace std;
 6 char in[10001];
 7 
 8 int dp[10001];
 9 
10 int LNRS_dp(char *in)
11 {
12     int i, j;
13     int len = strlen(in);
14     int last = 0;     // 上一次最长子串的起始位置
15     int maxlen,maxindex;
16     maxlen = maxindex = 0;
17  
18     dp[0] = 1;
19     for(i = 1; i < len; ++i)
20     {
21         for(j = i-1; j >= last; --j) // 遍历到上一次最长子串起始位置
22         {
23             if(in[j] == in[i])
24             {
25                 dp[i] = i - j;
26                 last = j+1; // 更新last_start
27                 break;
28             }else if(j == last) // 无重复
29             {
30                 dp[i] = dp[i-1] + 1;//长度+1 
31             }
32         }
33         if(dp[i] > maxlen)
34         {
35             maxlen = dp[i];
36         }
37     }
38     return maxlen;
39 }
40 
41 int main()
42 {
43      freopen("1530.in","r",stdin);
44      freopen("1530.out","w",stdout);
45      
46      while(scanf("%s",in)!=EOF)
47      {
48           printf("%d
",LNRS_dp(in));
49      }
50      return 0;
51 }
复制代码

  显然这个方案是O(n2)的复杂度,且对字符种类没有限制。

解法二:Hash

  很多情况下,在描述一个字符串时,都是英文字母组成的字符,因此可能出现的元素是有限的(26个),因此就有了第二种想法,Hash。

  这种想法在于使用一个大小为26的数组visited[]记录每个字符是否出现,然后枚举最长不重复子串的起点。代码如下:

void LNRS_hash(char* s)
{
char visited[26];
int i, j;
int maxlen = 0;
int maxIndex;
int len = strlen(s);

memset(visited, -1, 26);
for (i = 0; i < len; i++)
{
visited[s[i] - 'a'] = 1;
for(j = i+1 ; j < len; j++)
{
if(visited[s[j]-'a'] == -1) //该字符没有出现
{
visited[s[j]-'a'] = 1;
if(j - i+1> maxlen)
{
maxlen = j - i+1;
maxIndex = i;//最长不重复子串的起点
}
}
else
break;
}
memset(visited, -1, 26);
}
printf("%d ", maxlen);
}

复制代码
 1 void LNRS_hash(char*  s)
 2 {
 3     char visited[26];
 4     int i, j;
 5     int maxlen = 0;
 6     int maxIndex;
 7     int  len = strlen(s);
 8 
 9     memset(visited, -1, 26);
10     for (i = 0; i < len; i++)
11     {
12         visited[s[i] - 'a'] = 1;
13         for(j = i+1 ; j < len; j++)
14         {
15             if(visited[s[j]-'a'] == -1) //该字符没有出现
16             {
17                 visited[s[j]-'a'] = 1;
18                 if(j - i+1> maxlen)
19                 {
20                     maxlen = j - i+1;
21                     maxIndex = i;//最长不重复子串的起点
22                 }
23             }
24             else
25                 break;
26         }
27         memset(visited, -1, 26);
28     }
29     printf("%d
", maxlen);
30 }
复制代码

  这也是O(n2)的复杂度。仅适用于字符种类明确的情形。

解法三:动态规划的改进

    有了第二种Hash的思想,我们是不是也可以考虑对第一种动态规划的方法进行针对性改造呢?

  回顾之前动态规划的解法,枚举每一个以a[i]为结尾的最长不重复子串,将a[i]与之前的子串元素一一比较,判断是否出现过。很显然,在字符种类有限的情况下(如只有字母组成的字符串),这种判重可以用Hash来实现。代码如下:

int LNRS_dp(char *in)
{
int i, j;
int len = strlen(in);
int last = 0; // 上一次最长子串的起始位置
int maxlen;
char visited[26];
memset(visited, -1, sizeof(visited));
dp[0] = 1;
visited[in[0]-'a'] = 0;
maxlen = 1;
for(i = 1; i < len; ++i)
{
if(visited[in[i]-'a'] == -1 || visited[in[i]-'a']<last)//未访问过,或者是之前访问的
{
dp[i] = dp[i-1] + 1;
visited[in[i]-'a'] = i;
}
else
{
dp[i] = i - visited[in[i]-'a'];
last = visited[in[i]-'a']+1;
visited[in[i]-'a'] = i;
}
if(dp[i] > maxlen)
{
maxlen = dp[i];
}
}
return maxlen;
}

复制代码
 1 int LNRS_dp(char *in)
 2 {
 3     int i, j;
 4     int len = strlen(in);
 5     int last = 0;     // 上一次最长子串的起始位置
 6     int maxlen;
 7      char visited[26];
 8      memset(visited, -1, sizeof(visited));
 9     dp[0] = 1;
10     visited[in[0]-'a'] = 0;
11     maxlen = 1; 
12     for(i = 1; i < len; ++i)
13     {
14         if(visited[in[i]-'a'] == -1 || visited[in[i]-'a']<last)//未访问过,或者是之前访问的 
15         {
16              dp[i] = dp[i-1] + 1;
17              visited[in[i]-'a'] = i; 
18         }
19         else
20         {
21               dp[i] = i - visited[in[i]-'a'];
22               last = visited[in[i]-'a']+1;
23               visited[in[i]-'a'] = i;
24         }
25         if(dp[i] > maxlen)
26         {
27             maxlen = dp[i];
28         }
29     }
30     return maxlen;
31 }
复制代码

  PS:这是一个OJ的题,http://ac.jobdu.com/problem.php?pid=1530,前两种方法可以过,第三种方法过不了,求大神指导下,第三种方法哪里没有考虑到,多谢!

原文地址:https://www.cnblogs.com/Leo_wl/p/3347495.html