3——无重复字符的最长子串

本文探讨了如何寻找字符串中最长的无重复字符子串,通过两次本地尝试,从失败的解决方案到逐步改进,最终采用“双指针”思想结合HashMap实现高效解法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一次本地尝试

思路

这里写图片描述

白纸代码

这里写图片描述

IDE 代码

import java.util.HashSet;

public class MaxUnrptSubStr_Wrong1 {
    public static int lengthOfLongestSubstring(String s) {
        // 辅助变量
        int tmpLength = 0, maxLength = 0;
        char[] strChars = s.toCharArray();
        // 默认大小与充填因子的HashSet
        HashSet charSet = new HashSet();
        // 遍历strChars
        for (char c : strChars) {
            charSet.add(c);
            ++tmpLength;
            if (charSet.size() != tmpLength) {
                maxLength = maxLength < --tmpLength ? tmpLength : maxLength;
                charSet.clear();
                charSet.add(c);
                tmpLength = 0;
            }
        }
        return maxLength;
    }

    public static void main(String[] args) {
        String s = "asdddqwdfsdd";
        System.out.println(MaxUnrptSubStr_Wrong1.lengthOfLongestSubstring(s));
    }
}

时间空间复杂度

  • 时间复杂度:HashSet要去重,时间复杂度未知,但是应该是小于O(n),所以该解法的时间复杂度小于O( n2 n 2 )【TODO:HashSet的时间复杂度是多少】

  • 空间复杂度:两个辅助变量,是O(1); 但是set,O(n)
    所以,空间复杂度,O(n )

结果不对的原因及串

所清掉的字符中,有的字符是可以算入下一次子串长度的,比如:
“ad d qwdfs d d“,结果应该是5,但是以上代码会返回3

  • 第一次记录为ad,然后遇见第二个d,清除,这个清除是没有问题的,因为两个d是挨着的;
  • 第二次记录为dqw,然后遇见第二个d,清除,这个清除就有问题了,因为qw对于下一子串是有意义的,这也是最终的返回结果,3
  • 而正确答案应该是5,qwdfs
  • 遇到重复值时,不应该清除set中所有的元素,仅清除它以及它前面的即可
  • ~结论:不用Set,Set无状态无序~

第二次本地尝试

思路

  • 贪心算法?不满足,无法拆分为子结构,也没有“贪“的策略。
  • 分治法?不满足,同样没办法分子问题啊,分了也没办法独立。
  • 回溯法?动态规划?我还只知道概念。。。
  • 暴力求解?不如不做

这里写图片描述

白纸代码

这里写图片描述

最终Ac代码

public class MaxUnrptSubStr {
    /**
     * 解空间[0,26],25为下标最大增加数
     */
    private static int MAX = 25;

    public int lengthOfLongestSubstring(String s) {
        int index = -1, maxLength = 0;
        char[] strChars = s.toCharArray();
        if (strChars.length== 0 | strChars.length== 1) {return strLength;}
        HashSet charSet = new HashSet();
        for (char c : strChars) {
            ++index;
            charSet.add(c);
            for (int i = 0; i < Math.min(strLength - index, MAX); i++) {
                charSet.add(strChars[index + i]);
                if (charSet.size() <= i) {
                    maxLength = maxLength < charSet.size() ? charSet.size(): maxLength;
                    charSet.clear();
                    break;
                }
            }
            maxLength = maxLength < charSet.size() ? charSet.size() : maxLength;
        }
        return maxLength;
    }
}

反思

不缜密/不好的地方:

  • 空字符串;
  • 第二个for中的if,不匹配if时,忘记了给maxLength赋值;
  • 时间复杂度仍然是O( n2 n 2 ),与暴力的情况唯一不同的是,判断是否重复不是自己一一匹配的,时间复杂度应该可以低于O( n2 n 2 )

结果也是很惨的,仅好于23.21%的人

借鉴他人解决方案后的改进思路

源代码出处

  • 作者是“双指针”的思想,使用HashSet的“叔叔”HashMap,该map中,“脑袋”指针一直往字符串末尾走,“尾巴”指针则停留在上一次重复的字符的下标+1的位置。【就如我的第一次尝试,仅需要删除重复字符以及其前面的字符就可】
  • 利用了HashMap相同key默认去重的特点,key是字符,value是下标,用下标减去“尾巴”更新maxLength的值
  • 我做的修改:没有什么实质的修改。。。把变量名改得更容易懂一些。。。

借鉴改进后最终代码

public int lengthOfLongestSubstring(String s) {
        int strLength = s.length();
        if (strLength == 0 | strLength == 1) {
            return strLength;
        }
        HashMap<Character, Integer> map = new HashMap<>();
        int maxLength = 0;
        for (int globalIndex = 0, newSubStrBegin = 0; globalIndex < s.length(); ++globalIndex) {
            if (map.containsKey(s.charAt(globalIndex))) {
                newSubStrBegin = Math.max(newSubStrBegin, map.get(s.charAt(globalIndex)) + 1);
            }
            // 重复的仍然put,对于key,hashmap的onlyIfAbsent参数默认为false,会替换相同key之前的value:putVal(hash(key), key, value, false, true);
            map.put(s.charAt(globalIndex), globalIndex);
            maxLength = Math.max(maxLength, globalIndex - newSubStrBegin + 1);
        }
        return maxLength;
    }

简单解释

这里写图片描述

时间及空间复杂度

  • 时间复杂度
    作者认为是O(n),他显然不计入hashmap判断containskey以及hashmap更新value的时间,
    时间不只O(n),比O( n2 n 2 )低一些
  • 空间复杂度
    O(n)
  • 结果优于54%的人,仍然有很大提升空间。

TODO

  • HashSet的时间复杂度是多少
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值