LeetCode(3)之Long Substring Without Repeating Characters

LeetCode 第三題 求不包含重複字符的最長子字符串的長度

例如 字符串 abbca 最長的不包含重複字符的子字符串 是 bca ,長度爲3
例如 字符串 qqq 最長的不包含重複字符的子字符串 是 qqq ,長度爲1
例如 字符串 abcabdb 最長的不包含重複字符的子字符串 是 cabd ,長度爲4算法

解法一:暴力求解

找全部子字符串,判斷子字符串是否有重複字符,返回最大不重複字符串的長度。
這是最直接,也是最容易想到的辦法。bash

例如 字符串 abcabdb 它的子字符串是:
包含下標爲0的 a ab abc abca abcad abcabdb
包含下標爲1的 b bc bca bcab bcabd bcabdb
包含下標爲2的 c ca cab cabd cabdb
包含下標爲3的 a ab abd abdb
...優化

下面看代碼:ui

public int lengthOfLongestSubstring(String s) {
    int max = 0;
	for(int i = 0 ; i < s.length() ; i++) {
		List<Character> temp = new ArrayList<>();
		for(int j = i ; j < s.length() ; j++) {
		    // 每一次的add操做,temp裏面都會造成一個新的子字符串
			temp.add(s.charAt(j));
			// 判斷子字符串是否包含重複字符
			if(listIsUnique(temp)) {
				max = Math.max(max, temp.size());
			}
		}
	}
	return max;
}
    
private static boolean listIsUnique(List<Character> list) {
	Set<Character> set = new HashSet<>();
	for(Character c : list) {
		if(set.contains(c)) {
			return false;
		}else {
			set.add(c);
		}
	}
	return true;
}
複製代碼

好的,提交到leetcode,看看結果

哈哈,最後一個用例耗時太長,不給經過。 粗略看看咱們的暴力求解時間複雜度吧。外層兩個for循壞,求出子字符串,而後又是一個for循環,判斷是否重複。時間複雜度大概是O(N^3)。咱們稍微優化下。spa

咱們關心的是子字符串的長度,而不是具體的子字符串。因此咱們能夠不維護具體的子字符串,只關心長度。構造子字符串的方法仍是兩個for循環,可是判斷是否重複時,能夠使用hashSet替代一個for循環。看代碼:code

public int lengthOfLongestSubstring(String s) {
	int max = 0;
	for(int i = 0 ; i < s.length() ; i++) {
	    // 使用hasSet判斷元素是否重複
		Set<Character> temp = new HashSet<>();
		for(int j = i ; j < s.length() ; j++) {
			char ch = s.charAt(j);
			// 由於是從下標爲i的字符開始,依次添加字符構造子字符串。
			// 因此當前元素重複,那麼後面構造的子字符串必然也重複,因此跳出循環。
			if(temp.contains(ch)) {
				break;
			}else {
				temp.add(ch);
			}
			max = Math.max(max, temp.size());
		}
	}
	return max;
}
複製代碼

提交LeetCode,查看結果。

終於經過了~,可是,只比14.83%的人快。 此次的時間複雜度接近O(N^2),想一想其餘辦法吧。

解法二:滑動窗口解法

想象一個窗口,窗口只能向右添加元素,從左刪除元素。 cdn

咱們設兩個變量 int windowLeft表示窗口坐下標,int windowRight表示窗口右下標。

咱們維護一個list表明當前窗口中的元素。從字符串的第一個元素開始,向右遍歷,若是窗口中不包含遍歷的當前元素,則將當前元素添加到list,windowRight++,同時記錄一個當前窗口的大小;若是窗口中包含遍歷的當前元素,則從list移除最左邊的元素,即下標爲0的元素,windowLeft++ 。 循環條件:windowLeft 和windowRight 小於字符串長度。 每次添加元素時,窗口的最大值,便是咱們要的結果。blog

看代碼:leetcode

public int lengthOfLongestSubstring(String s) {
	int windowLeft = 0;
	int windowRight = 0;
	LinkedList<Character> set = new LinkedList<>();
	int sLength = s.length();
	int max = 0;
	while (windowRight < sLength && windowLeft < sLength) {
		char ch = s.charAt(windowRight);
		if (!set.contains(ch)) {
			set.add(ch);
			windowRight++;
			max = Math.max(max, set.size());
		} else {
			set.remove(0);
			windowLeft++;
		}
	}

	return max;
    }
複製代碼

滑動窗口算法解釋

一、定義窗口的做用

咱們須要求字符不重複子字符串的最大長度,因此必定要先有子字符串,窗口所圈起來的範圍就是子字符串。當窗口大小固定爲1時,每向右滑動一次,便新獲得一個長度爲1的子字符串。當窗口長度爲2時,沒向右滑動一次,便獲得一個長度爲2的子字符串。 因而咱們的問題便轉換成了如何尋找一個最大的不含有重複元素的窗口rem

二、爲何當遇到新元素時,窗口向右擴張一個單位,而左邊不動?

咱們的目標是求最大的長度。若是當前窗口長度爲1,向右滑動,獲得的子字符串長度永遠爲1。可是,由於是新元素,因此咱們至少能夠獲得一個1+1=2長度的不重複子字符串。

三、爲何遇到重複元素時,窗口要從左邊縮小一個單位,而右邊不動?

咱們的窗口裏的元素永遠要是不重複的,由於咱們是從左向右滑動,由於要保證窗口元素不重複,因此窗口要從左邊縮小,直到沒有重複元素,此時才能夠進行下一輪的擴張。 最終,擴張期間,窗口的最大值即爲咱們所求。

提交 看看結果

哈!還不錯
相關文章
相關標籤/搜索