題目描述
給定一個字符串,請你找出其中不含有重復字符的 最長子串 的長度。
示例 1:
輸入: "abcabcbb"
輸出: 3
解釋: 因為無重復字符的最長子串是 "abc",所以其長度為 3。
示例 2:
輸入: "bbbbb"
輸出: 1
解釋: 因為無重復字符的最長子串是 "b",所以其長度為 1。
示例 3:
輸入: "pwwkew"
輸出: 3
解釋: 因為無重復字符的最長子串是 "wke",所以其長度為 3。
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
題目解析
這道題的目標是找出最長子串,並且該子串必須不包含重復字符,而且這個子串必須是原字符串中連續的一部分(見示例3中的解釋說明)。
拿到題目時先不要心急想什么騷操作,我們先從最普通的操作開始把題目解出來,然后再來看如何優化。
接下來,我們畫圖分析一下,先隨便弄一個長相普通的字符串:frankissohandsome
,我們要從中找出我們想要的子串,那少不了需要遍歷,我們設置兩個變量from
,to
,分別存儲尋找的目標子串在原字符串中的首尾位置。
首先,from
和to
的初始值都為0(String的序號從0開始),子串長度length = 1
,最大子串長度maxLength = 1
。
然后,我們將to
的指向往后移動,並判斷新遍歷的字符是否已經存在於子串中,如果不存在,則將其加入子串中,並將length
進行自增。
直到找到一個已存在於子串中的字符,或者to
到達字符串的末尾。這里,我們找到了一個重復的s
,序號為7
,此時的子串為frankis
,將此時的子串長度與最大子串長度相比較(目前為0
),如果比最大子串長度大,則將最大子串長度設置為當前子串長度7
。
接下來,我們繼續尋找符合條件的子串,這里比較關鍵的一點是下一個子串的起始位置,這里我們將from
直接跳到了序號為7
的位置,因為包含ss
的子串顯然都不能滿足要求。
然后我們依照之前的方法,找到第二個候選的子串sohand
,長度為6
,比目前的最大子串長度小,所以不是目標子串。
接着繼續尋找,找到另一個候選子串ohands
,長度小於最大子串長度,不是我們的目標子串。
繼續尋找。
to
到達了字符串末尾,找到另一個候選子串handsome
,長度大於最大子串長度,這就是我們的目標子串。
於是我們的最大子串長度就輕松加愉快的找到了。接下來的事情就是把上面的思路轉化成代碼。
這里只需要注意一下from
的跳轉即可,每次跳轉的序號為to
指向的字符在子串中出現的位置 + 1。
常規解法
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) return 0;
int from = 0, to = 1, length = 1, maxLength = 1;
// to遍歷直到字符串末尾
while (to < s.length()){
int site = s.substring(from, to).indexOf(s.charAt(to));
if (site != -1){
// to指向的字符已存在
length = to - from;
if (length > maxLength) maxLength = length;
// from 跳轉到site+1的位置
from = from + site + 1;
}
to++;
}
// 處理最后一個子串
if (to - from > maxLength) {
maxLength = to - from;
}
return maxLength;
}
}
這里沒有什么騷操作,考慮好邊界情況就行了。有一個小細節需要注意,site
代表的是子串中字符出現的位置,不是原字符串中的位置,因此from
在跳轉時,需要加上自身原來的序號。還有最后一個子串的處理不要忘記,因為當to
遍歷到字符串末尾時,會結束循環,最后一個子串將不會在循環內處理。
讓我們提交一下:
擊敗了73%
的用戶,還不錯。
常規解法優化
想想看,還有沒有優化的空間呢?
那肯定是有的,首先我們想一想,當我們找到的最大子串長度已經比from
所在位置到字符串末尾的位置還要長了,那就沒有必要再繼續下去了。
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) return 0;
int from = 0, to = 1, length = 1, maxLength = 0;
// to遍歷直到字符串末尾
while (to < s.length()){
int site = s.substring(from, to).indexOf(s.charAt(to));
if (site != -1){
// to指向的字符已存在
length = to - from;
if (length > maxLength) {
maxLength = length;
}
// 判斷是否需要繼續遍歷
if (maxLength > s.length() - from + 1) return maxLength;
from = from + site + 1;
}
to++;
}
// 處理最后一個子串
if (to - from > maxLength) {
maxLength = to - from;
}
return maxLength;
}
}
另外要處理類似bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
這樣的字符串,上面的方法還是有很大優化空間的,我們可以用一個HashSet
來存儲所有元素,利用其特性進行去重,如果找到的子串長度已經等於HashSet
中的元素個數了,那就不用再繼續查找了。
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) return 0;
int from = 0, to = 1, length = 1, maxLength = 0;
Set<Character> set = new HashSet<>();
for (int i = 0; i < s.length(); i++){
set.add(s.charAt(i));
}
// to遍歷直到字符串末尾
while (to < s.length()){
int site = s.substring(from, to).indexOf(s.charAt(to));
if (site != -1){
// to指向的字符已存在
length = to - from;
if (length > maxLength) {
maxLength = length;
}
if (maxLength > s.length() - from + 1) return maxLength;
if (maxLength >= set.size()) return maxLength;
from = from + site + 1;
}
to++;
}
// 處理最后一個子串
if (to - from > maxLength) {
maxLength = to - from;
}
return maxLength;
}
}
再提交一下:
哈哈哈哈,翻車了,所以這里引入一個HashSet
用空間來換時間的方式不一定合適,看來測試用例里像bbbbbbbbbbbbbb
這樣的用例並不多啊。
那么今天的翻車就到此為止了,如果覺得對你有幫助的話記得點個關注哦。