LeetCode高频题3:无重复字符的最长子串

LeetCode高频题3:无重复字符的最长子串

提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
在这里插入图片描述


题目

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

本题是一个非常非常经典的题目,基本每个月都会有大厂考!!!


一、审题

示例 1:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


子串子序列:经典的解决思路:考虑以i开头或者结尾的情况

遇到子串:尝试考虑以i开头或者结尾的情况

本题是考虑以i结尾的子串,它的最长不重复字符的子串
整个字符串s的每一个位置i都求一个结果,整体max必在其中

比如:
pwwkew
看下图,i=0时,必须以i=0结尾的子串,最长就是p,长度为1
必须以i=1结尾的子串,最长就是pw,长度为2
必须以i=2结尾的子串,最长就是w,长度为1
必须以i=3结尾的子串,最长就是wk,长度为2
必须以i=4结尾的子串,最长就是wke,长度为3
必须以i=5结尾的子串,最长就是kew,长度为3
在这里插入图片描述
因此结果为3

代码怎么实现整个逻辑呢?
很巧妙!

来到i位置的,看到str[i]字符,你怎么求这个结果呢?
咱们至少要准备i-1位置之前的所有结果,不妨设放入dp数组
dp[i]就是:以i结尾的子串,它的最长不重复字符的子串长

这样的话,你看看i-1位置那个字符,最远能推多远呢?比如在j1处
下图,i-1处是b,b最远能推到j1=2处,j1=i-1-dp[i-1]
在这里插入图片描述
那么dp[i]是多少呢?还得看i位置字符a上次出现的位置j2
上图中上次j2=1
那么我dp[i]到底和j1和j2有啥关系呢?
实际上dp[i]最远能推到的位置是j1和j2的最大值!
因为i往前,谁都不能重复出现,上次dp[i-1]已经有一个界限了j1,你现在a上次出现虽然j2更远,但是你不能上海b字符,让b进来重复,不达标!
所以呢,j1就是a字符能推过去的最远位置
dp[i]=i-max(j1,j2)=5-2=3

当然,万一j2更大的话,就是去j2了,看看下图的例子,上次a出现的位置是3的话,情况就不一样了,但是计算方法一样
在这里插入图片描述
所以呢,j2就是a字符能推过去的最远位置
dp[i]=i-max(j1,j2)=5-3=2

明白?
其实咱们就只需要做2件事:
(1)用一个哈希表map将每个字符上次出现的位置i记忆下来,既然是遇到字符,就不用真的是哈希表
完全可以用数组替代,只不过字符就是ASCII码而已
在这里插入图片描述
直接用数组,0–255的ASCII码值做下标就行,这是常用额技巧,速度快
在这里插入图片描述
(2)还得记录一个结果,dp数组,用来记忆s以i结尾的子串,最长的不重复长度
比如上面案例中i=4时,dp[4]=2,dp[5]=3

想清楚这件事,其实手撕代码就好说了:

//复习:优化算法,用哈希表记忆上次字符出现的位置
        public int lengthOfLongestSubstringReview(String s) {
            if (s.compareTo("")  == 0 || s.length() == 0) return 0;

            //(1)用一个哈希表map将每个字符上次出现的位置i记忆下来,既然是遇到字符,就不用真的是哈希表
            //完全可以用数组替代,只不过字符就是ASCII码而已
            int[] map  = new int[256];
            for (int i = 0; i < 256; i++) {
                map[i] = -1;//最开始默认为-1,好计算结果
            }
            //(2)还得记录一个结果,dp数组,用来记忆s以i结尾的子串,最长的不重复长度
            char[] str = s.toCharArray();
            int N = str.length;
            int[] dp = new int[N];
            dp[0] = 1;//至少有一个字符的
            int max = 1;//结果
            map[str[0]] = 0;

            //每个i结尾,求一个答案,看看子串长度多少?
            for (int i = 1; i < N; i++) {
                //实际上dp[i]最远能推到的位置是j1和j2的最大值!
                int j1 = i - 1 - dp[i - 1];//这是可以优化的位置
                int j2 = map[str[i]];//上次出现的位置,-1也没事,--1=1
                dp[i] = i - Math.max(j1, j2);//看看i开始最远能推过去的位置
                max = Math.max(max, dp[i]);//每个结果都给max更新
                //最后别忘加入stri的位置i
                map[str[i]] = i;
            }

            return max;
        }

在这里插入图片描述

在这里插入图片描述
自己线下测试:

    public static void test(){
        Solution solution = new Solution();
        System.out.println(solution.lengthOfLongestSubstring2("aabcdb"));
        System.out.println(solution.lengthOfLongestSubstringReview("aabcdb"));
    }

    public static void main(String[] args) {
        test();
    }

问题不大:

4
4

所以情况就是这样了

精简一下代码:把dp省掉

但是真的需要把dp把每一个位置都存下来吗???
其实不需要,因为每次来求dp[i]时,咱只需要看i-1处和i的字符的状况
咱们用一个变量j2把i-1最远能推到的位置记住,省去dp了

重新改一个代码:

class Solution {
    
    //复习:优化算法,用哈希表记忆上次字符出现的位置
    public int lengthOfLongestSubstring(String s) {
        if (s.compareTo("")  == 0 || s.length() == 0) return 0;

        //(1)用一个哈希表map将每个字符上次出现的位置i记忆下来,既然是遇到字符,就不用真的是哈希表
        //完全可以用数组替代,只不过字符就是ASCII码而已
        int[] map  = new int[256];
        for (int i = 0; i < 256; i++) {
            map[i] = -1;//最开始默认为-1,好计算结果
        }
        //(2)还得记录一个结果,dp数组,省了
        char[] str = s.toCharArray();
        int N = str.length;
        int max = 0;//结果
        int j1 = -1;//i-1能推过去的最远位置,下次更新就作为i的推到的最远位置了

        //每个i结尾,求一个答案,看看子串长度多少?
        for (int i = 0; i < N; i++) {
            //i-1最远能推到的位置是j1,和当前i字符上次出现的位置,最大值下次就是i的最远推到的位置了
            j1 = Math.max(j1, map[str[i]]);//看看i开始最远能推过去的位置
            max = Math.max(max, i - j1);//i-j1就是咱们要的长度结果
            //最后别忘加入stri的位置i
            map[str[i]] = i;
        }

        return max;
    }
}

问题不大:
在这里插入图片描述
线下测试:

    public static void test(){
        Solution solution = new Solution();
        System.out.println(solution.lengthOfLongestSubstring2("aabcdb"));
        System.out.println(solution.lengthOfLongestSubstringReview("aabcdb"));
        System.out.println(solution.lengthOfLongestSubstringReview2("aabcdb"));
    }

    public static void main(String[] args) {
        test();
    }
4
4
4

总结

提示:重要经验:

1)本题是一个非常非常经典的题目,基本每个月都会有大厂考!!!
2)最重要的就是理解,来到i位置,以i结尾的达标子串,最远能推过去的位置是j1还是j2,j1是i-1位置最远能推过去的位置,j2是上次i位置字符出现的位置
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值