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长度的不重复子字符串。

三、为何遇到重复元素时,窗口要从左边缩小一个单位,而右边不动?

咱们的窗口里的元素永远要是不重复的,由于咱们是从左向右滑动,由于要保证窗口元素不重复,因此窗口要从左边缩小,直到没有重复元素,此时才能够进行下一轮的扩张。 最终,扩张期间,窗口的最大值即为咱们所求。

提交 看看结果

哈!还不错
相关文章
相关标签/搜索