LeetCode03 - 無重復字符的最長子串(Java 實現)


LeetCode03 - 無重復字符的最長子串(Java 實現)

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters

題目描述

給定一個字符串,請你找出其中不含有重復字符的 最長子串 的長度。

示例 1:

輸入: "abcabcbb"
輸出: 3 
解釋: 因為無重復字符的最長子串是 "abc",所以其長度為 3。

示例 2:

輸入: "bbbbb"
輸出: 1
解釋: 因為無重復字符的最長子串是 "b",所以其長度為 1。

示例 3:

輸入: "pwwkew"
輸出: 3
解釋: 因為無重復字符的最長子串是 "wke",所以其長度為 3。
     請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。

相關知識點補充

Java 數組的下標可以是:

  • 整型常量
  • 整型變量
  • 整型字符常量(字符的 ASCII 碼)
  • 整型表達式

例如:

int a[300],i;
// 下標是整型常量,整數 5
a[5] = 5; 

// 下標是整型變量, i 現在數值 5, 就是數組元素 a[5]。
i = 5;
a[i] = 5; 

// 下標是整型字符常量,等於 F 的 ASCII 碼值,就是數組元素 a[70]。
a['F'] = 5; 

// 下標是整型表達式,表達式運算結果是 5,就是數組元素 a[5]。
a['F' - 'A'] = 5; 

怎么查看 ASCII 碼的值?

// 比如查看 a 的 ASCII 值
System.out.println((int)'a');
// 結果為 97

System.out.println((int)'z');
// 結果為 122

Java 實現與實現思路

import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 03:給定一個字符串,請你找出其中不含有重復字符的最長子串的長度。
 *
 * @author XiaoPengwei
 * @since 2019-07-14
 */
public class LC03LongestSubstring {

    public static void main(String[] args) {

        // 該代表性示例,最長為 4,即 abcd
        // String str = "abcabcdbcs";
        String str = "abba";

        int lengthByMethod1 = lengthOfLongestSubstringMethod1(str);
        System.out.println("lengthByMethod1-->" + lengthByMethod1);

        int lengthByMethod2 = lengthOfLongestSubstringMethod2(str);
        System.out.println("lengthByMethod2-->" + lengthByMethod2);
    }

    /**
     * 方法一:
     * 求不含有重復字符的最長子串的長度
     * 思路:創建一個 pre 數組表示長度,從左到右遍歷字符串數組,查看
     *
     * @param s 字符串參數
     * @return int 長度
     */
    public static int lengthOfLongestSubstringMethod1(String s) {

        // 數組沒有賦值的時,所有元素會初始化為 0
        // 字符為下標時,會將 ASCII 碼作為下標
        int[] pre = new int[128];

        int max = 0, t = 0;

        // i 表示當前處理的第 i 個字符
        for (int i = 0; i < s.length(); i++) {

            // c 為依次取出的單個字符
            char c = s.charAt(i);

            /* 如果 pre[c] 不等於 0 表示數組中該位置被修改過,也就代表前面有重復字符
             */
            if (pre[c] != 0 && pre[c] > t) {

                // 更新 max 最大值
                // i - t 重復元素下標 - 上一次沒重復的下標
                max = Math.max(max, i - t);

                // t 是為求下一個子串長度做准備,因為要求出的是最長的子串長度
                // 更新 t,上一次沒重復的下標
                t = pre[c];
            }

            // 如果 pre[c] 為 0,或者 pre[c] <= t
            pre[c] = i + 1;
        }

        return Math.max(max, s.length() - t);
    }

    /**
     * 方法二:
     * 定義一個 map 數據結構存儲 (k, v),其中 key 值為字符,value 值為字符位置 +1,加 1 表示從字符位置后一個才開始不重復
     * 我們定義不重復子串的開始位置為 start,結束位置為 end; [start, end] 可理解為滑動窗口
     * 隨着 end 不斷遍歷向后,會遇到與 [start, end] 區間內字符相同的情況,此時將字符作為 key 值,獲取其 value 值,並更新 start,此時 [start, end] 區間內不存在重復字符
     * 無論是否更新 start,都會更新其 map 數據結構和結果 max。
     * 時間復雜度:O(n)
     *
     * @param s 字符串參數
     * @return int 長度
     * @author guanpengchn
     * @link https://leetcode-cn.com/problems/two-sum/solution/hua-jie-suan-fa-3-wu-zhong-fu-zi-fu-de-zui-chang-z/
     */
    public static int lengthOfLongestSubstringMethod2(String s) {

        // max 表示所求的最大長度
        int max = 0;

        /* Map 中 key 值為字符,value 值為字符位置 +1,加 1 表示從字符位置后一個才開始不重復
         * 同一個字符 key,在 map 中只存在一個。當重復時更新它的值。
         */
        Map<Character, Integer> map = new HashMap<Character, Integer>();

        /* start 指向不重復子串的第一個字符的下標。
         * 當存在重復字符時 start 指向后面一個重復字符,指向下一個不重復子串的第一個字符的下標
         */
        for (int start = 0, end = 0; end < s.length(); end++) {

            char c = s.charAt(end);

            if (map.containsKey(c)) {

                /* 如果含有重復字符串,將滑動窗戶的開始位置更新,后移
                 * map.get(c) 的值表示該出現重復的字符,上一次出現時下標+1
                 * 什么時候會出現 start<map.get(c)?
                 * 答:map.get(c) 該重復字符出現的位置不一定,如果重復字符出現的順序和之前一樣,
                 * 比如:abcabcd,先重 a,再重 b
                 * 什么時候會出現 start>map.get(c)?
                 * 答:第二次出現重復時,如果重復的字符在第一次出現重復的字符前面
                 * 比如 abba,先重 b,再重 a。出現 a 重復時,start 為 2 > map.get('a') 為 1
                 */
                start = Math.max(map.get(c), start);
            }

            /* 不管是否重復這里都會執行
             * 每次執行判斷一次滑動窗口長度是否超過當前最大長度,是則更新
             * end - start + 1 表示滑動窗口長度,子串長度,不重復子串最短也為 1
             * 為什么要 + 1?
             * 當 start 和 end 指向一個是元素時,下標一樣,end-start 為 0,此時存在一個不重復子串為 1 個元素
             * 當 end 指向 start 后面相鄰一個,end-start 為 1,此時不重復子串為 2 個元素
             */
            max = Math.max(max, end - start + 1);

            /* 不管是否重復這里都會執行
             * 如果不重復,會新添加一個 key,值為:位置下標+1
             * 如果重復,會更新這個 key 對應的值為:后面又重復出現的該字符的位置下標+1
             */
            map.put(c, end + 1);
        }

        return max;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM