LeetCode-03无重复字符的最长子串(多种解法)

/**
 *  给定⼀个字符串,找到没有重复字符的最⻓⼦串,返回它的⻓度
 *
 *  'pwwkew'  answer is 'wke' 3
 *   nnn  is n 1
 *   abcabcbb  is abc 3
 */
public class Test03 {
    public static void main(String[] args) {
//        System.out.println(lengthOfLongestSubstring("pwwkewq"));
//        System.out.println(lengthOfLongestSubstring("abcabcbb"));
//        System.out.println(lengthOfLongestSubstring02("pwwkewq"));
//        System.out.println(lengthOfLongestSubstring03("pwwkewq"));
        System.out.println(lengthOfLongestSubstring04("pwwkewq"));

    }

    /**
     *  解法一: 2个指针移动 截取范围内的字符串(双重 for)
     *  先固定一指针, 内层移动另一指针, 两指针中的字符串每位字符放在Set中,如果有重复,不用比较,指针向右移动
     *  判断 字符串是否重复, 不重复
     *  和当前保存的字串长度比较, 若更大, 更新变量值
     *
     *  时间复杂度:两个循环,加上判断⼦串满⾜不满⾜条件的函数中的循环,O(n³)。
     *
     *  空间复杂度:使⽤了⼀个 set,判断⼦串中有没有重复的字符。由于 set 中没有重复的字符,所以最⻓就是
     * 整个字符集,假设字符集的⼤⼩为 m ,那么 set 最⻓就是 m 。另⼀⽅⾯,如果字符串的⻓度⼩于 m ,是
     * n 。那么 set 最⻓也就是 n 了。综上,空间复杂度为 O(min(m,n))
     * @param s
     * @return
     */
    static int lengthOfLongestSubstring(String s) {
        int ans =0;//保存当前得到满⾜条件的⼦串的最⼤值;
        int n = s.length(); //字符串长度
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j <= n; j++) {//之所以 j<= n,是因为我们⼦串是 [i,j),左闭右开
                if (allUnique(s, i, j)) {
                    ans = Math.max(ans, j - i); //比较当前保存的字串长度,并重新赋值更新 ans
                }
            }
        }
        return ans;
    }

    private static boolean allUnique(String s, int start, int end) {
        Set<Character> set = new HashSet<>();//初始化 hash set
        for (int i = start; i < end; i++) {//遍历每个字符
            Character ch = s.charAt(i);
            if (set.contains(ch)){ return false;} //判断字符在不在 set 中
            set.add(ch);//不在的话将该字符添加到 set ⾥边
        }
        return true;
    }

    /**
     *     其实整个关于 j 的循环我们完全可以去掉了,此时可以理解变成了⼀个「滑动窗⼝」。
     *     整体就是橘⾊窗⼝在依次向右移动。
     *     做了很多重复的⼯作,因为如果 str[0,3) 中没有重复的字符,我们不需要再判断整个字符串 str[0,4) 中有没
     *     有重复的字符,⽽只需要判断 str[3] 在不在 str[0,3) 中,不在的话,就表明 str[0,4) 中没有重复的字符。
     *     如果在的话,那么 str[0,5) ,str[0,6) ,str[0,7) ⼀定有重复的字符,所以此时后边的 j 也不需要继续增加
     *     了。i ++ 进⼊下次的循环就可以了。
     *
     *    判断⼀个字符在不在字符串中,我们需要可以遍历整个字符串,遍历需要的时间复杂度就是 O(n),加上
     * 最外层的 i 的循环,总体复杂度就是 O(n²)。我们可以继续优化,判断字符在不在⼀个字符串,我们可以
     * 将已有的字符串存到 Hash ⾥,这样的时间复杂度是 O(1),总的时间复杂度就变成了 O(n)。
     *
     * 时间复杂度:在最坏的情况下,while 循环中的语句会执⾏ 2n 次,例如 abcdefgg,开始的时候 j ⼀直后移
     * 直到到达第⼆个 g 的时候固定不变 ,然后 i 开始⼀直后移直到 n ,所以总共执⾏了 2n 次,时间复杂度为 O
     * (n)。
     * 空间复杂度:和上边的类似,需要⼀个 Hash 保存⼦串,所以是 O(min(m,n))
     */

    static int lengthOfLongestSubstring02(String s) {
        int n = s.length(); //pwwkew
        Set<Character> set = new HashSet<>();
        int ans = 0, i = 0, j = 0;
        while (i < n && j < n) {
            if (!set.contains(s.charAt(j))) {
                set.add(s.charAt(j++));
                ans = Math.max(ans, j - i);
            } else {
                set.remove(s.charAt(i++));
            }
        }
        return ans;
    }

    /**
     *  解法3: i 直接跳转到出现重复字符的下一位
     *  所以需要用 map 记录字符及对应的下标
     * @param s
     * @return
     */
    static int lengthOfLongestSubstring03(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>();
        for (int i = 0, j = 0; j< n; j++) {
            if(map.containsKey(s.charAt(j))) {
                i = Math.max(i, map.get(s.charAt(j)));
            }
            ans = Math.max(ans, j-i +1);
            map.put(s.charAt(j), j + 1);//下标 + 1 代表 i 要移动的下个位置
        }
        return ans;
    }

    /**
     * 和解法三思路⼀样,区别的地⽅在于,我们不⽤ Hash ,⽽是直接⽤数组,字符的 ASCII 码值作为数组的下
     * 标,数组存储该字符所在字符串的位置。适⽤于字符集⽐较⼩的情况,因为我们会直接开辟和字符集等⼤的
     * 数组。
     * 和解法 3 不同的地⽅在于
     * @param s
     * @return
     */
    static int lengthOfLongestSubstring04(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128];
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1;//(下标 + 1) 代表 i 要移动的下个位置
        }
        return ans;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值