字节跳动面试题——最长不重复子串

偶然看到一个字节跳动的面试题,研究记录一下
给定一个字符串,得出最长不重复子串

比如 adca 结果为 adc
adcdedfckop结果为edfckop

解法一 暴力迭代:

遍历出给定字符串的所有子串,判断其中是否有重复字符,没有则记录长度,与下一次也无重复字符的子串比较长度,最长的即为所求

public static String noDuplicate(String str) {
    if(str==null||str.length()==0){
        return null;
    }
    Set<String> set = new HashSet<>();
    String result = "";
    System.out.println("length:" + str.length());
    //获得所有子串
    for (int i = 0; i < str.length(); i++) {
        for (int j = i + 1; j <= str.length(); j++) {
            String s = str.substring(i, j);
            set.add(s);
        }
    }
    int max = 0;
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
        LinkedHashSet<String> setchar = new LinkedHashSet<>();
        String  st = iterator.next().toString();
        //子串去重放入set
        for (int a = 0; a < st.length(); a++) {
            setchar.add(String.valueOf(st.charAt(a)));
        }
        //长度不变认为不重复,取最长子串
        if(setchar.size()==st.length()){
            int len = st.length();
            if(max<len){
                max = Math.max(max, len);
                result = st;
            }
        }
    }
    System.out.println(max);
    return result;
}

暴力法的时间复杂度为:n2+n2===>2n2 即O(n2) ,显然不是面试管期待的解法

解法二 滑动窗口,有回溯

/**
 * 滑动窗口:保证窗口中都是不重复的子串实现 _有回溯
 * 从每个位置开始找最长不重复串0-n,1-n,...n-1-n
 * 保留上述步骤中最长串开始位置和长度
 * @param s
 * @return
 */
public static String getMaxsubHuisu(String s) {
    if (s == null || s.length() == 0) {
        return null;
    }
    int start = 0;//滑动窗口的开始值
    int maxlen = 0;
    int len = 0;//窗口长度
    int startMaxIndex = 0;//最长子串的开始值
    Map<Character, Integer> map = new HashMap<>();//存储窗口内字符跟位置,用于判断是否重复
    int i;
    for (i = 0; i < s.length(); i++) {
        char ch = s.charAt(i);
        Integer value = map.get(ch);
        if (map.containsKey(ch)) {//出现重复字符
            start = value + 1;//保存下一个窗口值
            len = 0;窗口长度归0
            map = new HashMap<>();//窗口内字符置空
            i=value;//下次从重复的值位置开始找最长不重复串
        } else {
            map.put(ch, i);//不存在重复的,就存入map
            len++;//每次进来长度自增1
            if (len > maxlen) {//如果当前的窗口长度>最长字符串则,更新最长串,跟最长子串开始位置
                maxlen = len;
                startMaxIndex = start;
            }
        }
    }
    return s.substring(startMaxIndex, (startMaxIndex + maxlen));//截取字符串
}

从第一个下标开始找最长不重复串0:n,1:n,…n-1:n最后筛选出最长串

最好情况下不发生回溯时间复杂度O(n)
最坏情况,每次都回溯O(2n)
平均值为O(3/2 *n)

解法三 滑动窗口无回溯

/**
 * 滑动窗口:保证窗口中都是不重复的子串实现 _无回溯
 * @param s
 * @return
 */
public static String getMaxsub(String s) {
    if (s == null || s.length() == 0) {
        return null;
    }
    int start = 0;//滑动窗口的开始值
    int maxlen = 0;
    int len = 0;
    int startMaxIndex = 0;//最长子串的开始值
    Map<Character, Integer> map = new HashMap<>();
    int i;
    for (i = 0; i < s.length(); i++) {
        char ch = s.charAt(i);
        Integer value = map.get(ch);
        if (value != null && value >= start) {//字符重复,且字符的位置在窗口范围内,此时窗口需要向右伸缩
            start = value + 1;//新窗口开始位置,重复字符的下一个位置
            len = i - value;//长度为当前的遍历字符的位置-重复字符的位置
        } else {
            len++;//若不存在,则字符+1
            if (len > maxlen) {//若当前窗口的长度>最长子串的位置,则更新最长子串的长度跟最长子串的起始位置
                maxlen = len;
                startMaxIndex = start;
            }
        }
        map.put(ch, i);//遍历的 字符存入map中,重复的则覆盖用于判断是否重复
    }
    return s.substring(startMaxIndex, (startMaxIndex + maxlen));
}

思路分析,有回溯的滑动串口是不断扩大直到出现重复,窗口后移重复查找的过程,无回溯的方法,就要让窗口一直向前,实现一个可伸缩的窗口
在这里插入图片描述

如图,可理解为一个动态可伸缩窗口,窗口不断向前,右移的条件是窗口内出现重复字符,则窗口右移至窗口内第一个重复字符的下一个位置,舍弃的字符虽然是不重复的,但是肯定没有已经记录的最大子串长度更长且不会再变长,所以可以舍弃继续遍历字符拉伸窗口长度

很明显无回溯时间复杂度为:O(n)

如果理解了第三种方法,下面的方法更加简洁巧妙

public int lengthOfLongestSubstring(String s) {
        int r = 0, l = 0, res = 0;
        HashSet<Character> set = new HashSet<>();
        while(r<s.length()){
            char c = s.charAt(r++);
            while(set.contains(c)){
                set.remove(s.charAt(l++));
            }
            set.add(c);
            res = Math.max(res,set.size());
        }
        return res;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值