問題
給定一個字符串,找到最多有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;
}