LeetCode 3 最长无重复公共子串长度

问题描述

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度

问题分析

简单的思路是,依次以每一个数组元素a[i]做为起始字符,求对应的最大无重复公共子串的长度,从所有长度中选出最长的哪一个即可
从 j = i + 1 开始,看a[j]字符在i到j - 1之间有没有出现过:
如果出现了说明找到了以a[i]起始的最长公共子串,得到当前长度
如果没有,则看a[j]的下一个元素a[j + 1]
遍历结束就可以得到最大的字串长度

    public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() == 0)
            return 0;
        int maxLength = 0;
        for(int i = 0; i <s.length(); i++){
            int currentLen = 0;
            boolean getCurrentLen = false;
            for(int j = i + 1; j <s.length(); j++){
                for(int k = i;k < j; k++){
                    if(s.charAt(k) == s.charAt(j)){
                        currentLen = j - i;
                        getCurrentLen = true;
                        break;
                    }
                }
                if(getCurrentLen)
                    break;
            }
            if(!getCurrentLen && currentLen == 0)
                currentLen = s.length() - i;
            if(maxLength < currentLen)
                maxLength = currentLen;
        }
        return maxLength;
} 

结果

在这里插入图片描述
首先需要遍历数组,遍历时,要依次判断当前元素往后的元素能不能加进以该元素开头的子串中。时间复杂度为O(n^3)。参考第一道题,可以用HashMap来优化第三层循环:重复字符检索。具有一定效果。结果如下图所示。具体代码省略
在这里插入图片描述
可以看到,该优化具有一定的价值,但是时间复杂度仍然很高

观察上述解法,发现了一些冗余的操作:
输入:abcdaf
首先得到以a开头的最长子串为abcd,在这一过程中需要判断b、c、d、a能不能加入该子串。当要计算以b开头的最长子串时,又要计算c、d、a能不能加入b开头的子串。而这些步骤在得到abcd子串的时候都已经算过了。如何避免冗余的计算?可以利用滑动窗口——理解为两个索引(或C++指针)中间夹住的那一部分。窗口中的部分是无重复的不需要再计算,如下图所示
在这里插入图片描述
当窗口想要扩张到第二个 a 的时候发现,a 在窗口中已经出现过了。此时可知 a 开头子串最大长度为4。然后将窗口起始移到重复元素 a 的下一个位置,窗口的终止移动到第二个 a 处。根据前面的分析,窗口内的子串一定是无重复的,只需要判断 f 能不能加入到该窗口中

窗口的运动过程怎么实现呢?
起始时,窗口长度为1,起始点和终止点都在第一个元素
当下一个元素不在窗口范围内出现,窗口终止扩张一个元素
当下一个元素出现在窗口中,窗口起始点移动到重复元素之后,终止点扩张一个元素
当窗口终止点到达数组末尾则计算结束,只需要在窗口运动过程中计算出所有窗口的长度中最长的一个即可

    public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() == 0)
            return 0;
        int maxLength = 0,currentLength = 0;
        int start = 0,end = 0;
        while(end < s.length()){
            char tmpChar = s.charAt(end);
            //窗口滑动
            for(int i = start; i < end; i++){
                if(tmpChar == s.charAt(i)){
                    start = i + 1;
                    break;
                }
            }
            //计算当前窗口的长度
            currentLength = end - start + 1;
            //获得目前为止的最大长度
            maxLength = Math.max(currentLength,maxLength);
            end++;
        }
        return maxLength;
    }

结果

在这里插入图片描述
时间效率提高了很多。遍历一次需要O(n),在窗口内检查重复元素也需要O(n),时间复杂度为O(n^2)。
再思考一下发现,在窗口中检索有无重复字符时,可以利用HashMap来降低检索时间复杂度,代码如下

    public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() == 0)
            return 0;
        int maxLength = 0,currentLength = 0;
        int start = 0,end = 0;
        HashMap<Character,Integer> dict = new HashMap<>();
        while(end < s.length()){
            char tmpChar = s.charAt(end);
            if(dict.containsKey(tmpChar)){
                int pos = dict.get(tmpChar);
                //这个重复字符要在窗口内
                if(pos >= start)
                    start = pos + 1;
            }
            dict.put(tmpChar,end);
            currentLength = end - start + 1;
            maxLength = Math.max(currentLength,maxLength);
            end++;
        }
        return maxLength;
    }

结果

在这里插入图片描述
虽然理论上,检索的时间减少了,但是最终运行的时间却增加了。可能有两方面的原因:一是对哈希表的操作本来就要额外消耗时间,二是如果窗口长度比较短,依次比较窗口内是否存在重复元素所消耗的时间可能比操作哈希表还要少
类似的情况有:对于小数组快排的所用时间可能比简单排序还要长

动态规划

这一块,尚没有理清楚子问题的解与父问题的解到底有什么关系,先挖个坑

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值