最长不含重复字符的字符串

题目

  • 请从字符串中找出一个最长的不包含重复字符的字符串,计算最长子字符串的长度;假设字符串中只包含‘a’~‘z’的字符;
  • 例如,在字符串“arabcacfr”中,最长的不包含重复子字符串是“acfr”,长度为4;

 

 

分析

  • 用动态规划来提高效率;首先定义一个函数f(i)表示以第i个字符为结尾的不包含重复字符的子字符串的最长长度;f(i-1)表示以第i-1个字符为结尾的不包含重复的字符串长度;
  • 如果第i个字符没有出现过,那么f(i)=f(i-1)+1;
  • 例如,在字符串“arabcacfr”中,显然f(0)等于1,;在计算f(1)时,下标为1的字符‘r‘之前也没有出现过,因此f(1)=2,即f(1) = f(0)+1;,到此为止,最长字符串为“ar”
  • 如果第i个字符之前已经出现过,那情况就复杂了;我们先计算第i个字符和他上次出现在字符串中的位置的距离,d;
  • 第一种情况:d<=f(i-1) ,此时第i个字符上次出现在f(i-1)对应的最长子字符串中,因此f(i)=d;我们接着上面的算f(2),下标为2的值是a,a之前出现过,上一次出现的位置是0,他们之间的距离是d=(i-preIndex)为2,也就是字符’a‘出现在f(1)对应的最长不含重复字符的子字符串“ar”中,此时f(2)=d,即f(2)=2;
  • 第二种情况:d>f(i-1),此时字符串上次出现的位置在f(i-1)对应的最长子字符串的前面,因此仍然有f(i)=f(i-1)+1;现在看最后一个字符’r‘就是,他重复的就不再前一个的最长子字符串里,所以还是f(8)=f(7)+1=4;

 

分析

  • 核心方法findLongestSubstring()
    • curLen=0当前没有重复的字符串长度,也就是f(i-1)
    • maxLen=0最大字符串长度
    • 定义一个位置数组,表示当前的a~z在str里出现的位置
    • 最开始全都设成-1,证明没有在str里出现过
    • 从头开始遍历字符串
      • 得到字符
      • char t=str.charAt(i);
      • 去位置数组里,找到该字符在str字符串里出现的位置,为-1就是没有出现过
      • int preIndex=position[t-'a']
      • 得到两个重复字符的距离(i-preIndex)用当前位置i,减去之前出现的位子
      • 如果preIndex=-1||d》curLen
      • preIndex等于-1就是代表没在str字符串里出现过,d》curLen出现的重复的字符不在f(i-1)里,所以没关系
        • 当前长度++
      • 否则就是重复在当前长度里
        • 前面的就不要了,保留从现在开始
        • 当前长度=d
      • 记录一下当前字符出现在str里,position[str.charAt(i)-'a']=i
      • 如果当前长度》最大长度
        • 就赋值
    • 最后返回最大长度

 

 

 

 

 

 

 

 

 

 

package No15_优化空间和时间效率;

 

/**

 * 创建人:Jason

 * 创建时间:2019/1/18

 * 描述你的类:

 * //题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子

 * //字符串的长度。假设字符串中只包含从'a'到'z'的字符。

 动态规划,

 定义f(i)表示以第i个字符为结尾的不含重复字符的子字符串长度。

 定义f(i -1)表示以第i-1个字符为结尾的不含重复字符的子字符串长度。

 如果第i个字符之前没有出现过,则f(i) = f(i -1) +1,比如‘abc',f(0) = 1是必然的,

 再字符'b'之前没有出现过, 则f(1) = f(0)+1,

 字符’c'之前没有出现过,那么f(2) = f(1) +1,每次计算都会用到上一次计算的结果。

 如果第i个字符之前出现过呢?找到该字符上次出现的位置preIndex,当前位置d=i-preIndex就得到这两个重复字符之间的距离, 设为d。

 此时有两种情况:

 如果d <= f(i-1),说明当前重复字符必然在f(i-1)所对应的字符串中,比如'bdcefgc',当前字符c前面出现过了,preIndex为2,

 此时d = 6 -2 = 4 ,小于 f(i -1) = 6 (bdcefg)我们只好丢弃前一次出现的字符c及其前面的所有字符,得到当前最长不含重复字符的子

 字符串为’efgc‘,即f(i) = 4, 多举几个例子就知道,应该让f(i) = d;

 如果d > f(i-1), 这说明当前重复字符必然出现在f(i-1)所对应的字符串之前,比如erabcdabr当前字符r和索引1处的r重复,

 preIndex =1, i = 8,d=(i-preIndex) = 7。而f(i -1) = 4 (取索引4之后不包含重复字符的串cdab),而7>4,

 此时直接加1即可, 即f(i) = f(i-1) +1

 */

public class LongestSubstringWithoutDup {

 

//核心方法

public int findLongestSubstring(String str){

 

//当前长度

int curLen = 0;

//最大长度

int maxLen = 0;

 

//定义一个位置数组,用来储存该字符出现在str的位置

int []pos = new int[26];

for(int i = 0;i < 26;i++){

//初始化全是-1

pos[i] = -1;

}

 

 

//从头开始遍历字符串

for(int i = 0;i < str.length();i++){

char t = str.charAt(i);

//查看该字符是否出现在str里,如果出现过的话,preIndex就不是-1

int preIndex = pos[t-'a'];

 

//得到两个重复字符的距离,查看这个距离和f(i-1)的大小,这个重复的是数,是否在f(i-1)里

int d = i - preIndex;

//不重复或者重复在外面,不在f(i-1)里面

if(preIndex == -1 || d > curLen){

curLen++;

}

//如果在里面的话,前面就不要了,从该位置开始

else{

curLen = d;

}

//证明该点出现过

pos[t-'a'] = i;

if(curLen > maxLen){

maxLen = curLen;

}

}

 

return maxLen;

}

 

 

    // ====================测试代码====================

    void testSolution1(String  input, int expected){

        int output = findLongestSubstring(input);

        if(output == expected)

            System.out.printf( "passed, with input: "+input+" expected:"+expected+"\n");

        else

            System.out.printf( "FAILED, with input: " +input+"\n");

    }

 

 

 

    void test(String  input, int expected){

        testSolution1(input, expected);

    }

 

    void test1(){

        String  input = "arabcacfr";

        int expected = 4;

        test(input, expected);

    }

 

    void test2()

    {

        String  input = "acfrarabc";

        int expected = 4;

        test(input, expected);

    }

 

    void test3()

    {

        String  input = "arabcacfr";

        int expected = 4;

        test(input, expected);

    }

 

    void test4()

    {

        String  input = "aaaa";

        int expected = 1;

        test(input, expected);

    }

 

    void test5()

    {

        String  input = "abcdefg";

        int expected = 7;

        test(input, expected);

    }

 

    void test6()

    {

        String  input = "aaabbbccc";

        int expected = 2;

        test(input, expected);

    }

 

    void test7()

    {

        String  input= "abcdcba";

        int expected = 4;

        test(input, expected);

    }

 

    void test8()

    {

        String  input = "abcdaef";

        int expected = 6;

        test(input, expected);

    }

 

    void test9()

    {

        String  input= "a";

        int expected = 1;

        test(input, expected);

    }

 

    void test10()

    {

        String  input = "";

        int expected = 0;

        test(input, expected);

    }

 

    public static void  main(String args[]){

        new LongestSubstringWithoutDup().test1();

        new LongestSubstringWithoutDup().test2();

        new LongestSubstringWithoutDup().test3();

        new LongestSubstringWithoutDup().test4();

        new LongestSubstringWithoutDup().test5();

        new LongestSubstringWithoutDup().test6();

        new LongestSubstringWithoutDup().test7();

        new LongestSubstringWithoutDup().test8();

        new LongestSubstringWithoutDup().test9();

        new LongestSubstringWithoutDup().test10();

    }

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值