算法:求最多有k個不同字符的最長子字符串的長度

原文鏈接:https://blog.csdn.net/weixin_40564421/article/details/78997087

問題

給定一個字符串,找到最多有k個不同字符的最長子字符串,並返回其長度。
樣例
例如,給定 s = “eceba” , k = 3,
T 是 “eceb”,長度爲 4.

第一種思路

將母問題分爲兩個子問題:
1.如果從從頭開始找,求最長k不同子串長度
2.如果從第二個字符開始找,求最長k不同子串長度
然後不斷循環遞歸

實現

public static int longestSubstringDistinct(String s, int k) {
        // write your code here
        //邊界,當查找就剩最後k個字符或更少的時候,最長子串長度爲當前長度
        if (s.length() <= k) return s.length();
        //分成兩個子問題
 
        //第一個子問題:如果從當前字符串從頭開始找,求最長k不同子串長度
        //創建一個HashSet,僅存儲k個不同的字符
        HashSet<Character> kDinstinct = new HashSet<>();
        int p = 0;
        while (p < s.length()) {
            //在HashSet中存儲k個字符,超過的拋棄掉
            if (kDinstinct.size() < k) {
                kDinstinct.add(s.charAt(p));
            }
            //檢查k個不同字符中是否包含當前字符
            if (kDinstinct.contains(s.charAt(p))) {
                p++;
            } else {
                break;
            }
        }
        System.out.println(p);
        //第二個子問題:如果從當前下一個index找,求最長k不同子串長度
        int subLength = longestSubstringDistinct(s.substring(1, s.length()), k);
 
        //開始收集子問題的結果
        return Math.max(subLength,p);
    }

這種實現方式看似沒有什麼大問題,但是,當數據量很大的時候,頻繁的在JVM中創建遞歸方法的棧幀,很快的就會發生StackOverFlow異常!所以,來看下第二種思路的改進!

第二種思路

基於第一種思路,不過改爲嘗試用循環來替代遞歸,避免發生虛擬機棧溢出。

實現

public static int lengthOfLongestSubstringKDistinct(String s, int k) {
        // write your code here
        //邊界,當查找就剩最後k個字符或更少的時候,最長子串長度爲當前長度
        if (s.length() <= k) return s.length();
        int result = 0;
        for (int i = 0; i < s.length() - k; i++) {
            int p = 0;
            String subS = s.substring(i, s.length());
            //創建一個HashSet,僅存儲k個不同的字符
            HashSet<Character> kDinstinct = new HashSet<>();
            while (p < subS.length()) {
                //在HashSet中存儲k個字符,超過的拋棄掉
                if (kDinstinct.size() < k) {
                    kDinstinct.add(subS.charAt(p));
                }
                //檢查k個不同字符中是否包含當前字符
                if (kDinstinct.contains(subS.charAt(p))) {
                    p++;
                } else {
                    break;
                }
            }
            result = Math.max(result, p);
        }
        return result;
    }

第二種思路基本符合要求了,但是時間複雜度仍然爲平方階,接下來改進時間複雜度,提供給大家一個線性階的思路。

第三種思路

採用雙指針,用HashMap記錄雙指針中間的字符串是否滿足要求

實現

public static int lengthOfLongestSubstringKDistinct(String s, int k) {
        // write your code here
        int result = 0;
        int left = 0;
        //聲明一個HashMap,用來存儲k Distinct,還可以根據value用來判斷元素是否可以刪除
        HashMap<Character, Integer> map = new HashMap<>();
        for (int right = 0; right < s.length(); right++) {
            //右指針依次把字符串中的字符放到map中
            map.put(s.charAt(right),right);
            //當map元素大於k Distinct時,得到滿足要求的子字符串
            while (map.size() > k) {
                // 刪除最左的字符,刪除成功,則退出循環
                char leftChar = s.charAt(left);
                if (map.get(leftChar) == left) {
                    map.remove(leftChar);
                }
                // 左指針右移
                left++;
            }
            //子結果即左右指針之間的長度
            int subResult = right - left + 1;
            //結果取最大
            result = Math.max(result,subResult);
        }
        return result;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章