题目
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
答案
1.暴力解法
这个方法乍一看确实很暴力,但你仔细推敲的话,就发现其实也挺巧妙的,时间复杂度为O(n^3)。
首选通过两个循环穷举出所有的子串:
for(int i = 0; i < s.length() - 1; i++) {
for(int j = i + 1; j < s.length(); j++) {
从 i-j 就是一个子串
}
}
这时候如果你细心的话就已经注意到,上面这个穷举子串的循环并没有穷举出所有单个字符的子串,也就是说如果s = "abc"
它只能穷举出ab abc bc c
,并不能穷举出a b
。不必担心这点,因为在最里层的循环变量k
这里,他的界限是k < j
所以就很好的避免了这一点。
然后就是在子串上做处理:
boolean flag = false;
for(int k = i; k < j; k++)
if(s.charAt(k) == s.charAt(j)) {
flag = true;
break;
}
变量k 从 i 到 j,就是对每个子串的字符作比较,flag
是个标志变量,后面就可以看到他的作用。
最后这段代码就是整个暴力解法的巧妙之处:
if(!flag) {
result = Math.max(result, j - i + 1);
}else {
break;
}
如果有一个小的子串出现重复字符,则所有包含这个小的子串的大的子串,都不在对其进行访问。
举个栗子,s= "abbc"
,abb
中有重复,则会跳过对abbc
的访问,bb
有重复,则会跳过对bbc
的访问。
/*
* 暴力解法
*/
public static int method1(String s) {
if(s.length() == 0)
return 0;
int result = 1;
for(int i = 0; i < s.length() - 1; i++) {
for(int j = i + 1; j < s.length(); j++) {
//对子串操作
boolean flag = false;
for(int k = i; k < j; k++)
if(s.charAt(k) == s.charAt(j)) {
flag = true;
break;
}
if(!flag) {
result = Math.max(result, j - i + 1);
}else {
break;
}
}
}
return result;
}
2.滑动窗口
ps:图片是截得网络上的,侵权马上删!!!
滑动窗口,顾名思义就是,滑动两个字符串下标,来访问字符串子串。
要注意的是int[] freq = new int[123]
这个数组也相当于一个标志符,用字符串的字符对应的Unicode值作为数组下标,如果在当前子串中有这个字符出现,这个字符值下标对应的数组值为1,如果当前子串中没有某个字符,则那个字符值下标对应的数组值为0。
因为a-z 的值是 97 - 122
,A - Z 的值是 65 - 90
,所以这里将数组的大小设置为123。
时间复杂度为O(2n)= O(n)。
/*
* 滑动窗口
*/
public static int method2(String s) {
int n = s.length();
int result = 0;
int left = 0;
int right = -1;
int[] freq = new int[123];
while(left < n) {
if(right + 1 < n && freq[s.charAt(right + 1)] == 0) {
right++;
freq[s.charAt(right)] = 1;
}else {
freq[s.charAt(left)] = 0;
left++;
}
result = Math.max(result, right - left + 1);
}
return result;
}
3.滑动窗口改进版
改进版和普通版相比,时间复杂度更少,里面用了HashMap,这个的时间复杂度为O(n)。
因为它将访问过的每个字符放在了HashMap中(字符本身为键值,字符对应的下标的下一位为值map.put(stemp, end + 1);
),在每一次访问新的字符时,会在HashMap中查找是否已经出现过,如果没有,继续将这个字符放进Hashmap,如果出现过,就让左边的滑动变量直接跳到这个字符所对应的下标的下一位start = Math.max(start, map.get(stemp))
。
/*
* 活动窗口的改进版
*/
public static int method3(String s) {
int n = s.length();
int result = 0;
HashMap<Character , Integer> map = new HashMap<>();
for(int end = 0, start = 0; end < n; end++) {
char stemp = s.charAt(end);
if(map.containsKey(stemp)) {
start = Math.max(start, map.get(stemp));
}
map.put(stemp, end + 1);
result = Math.max(result, end - start + 1);
}
return result;
}
全部代码如下:
package leetCode;
import java.util.HashMap;
public class LengthOfLongestSubstring {
public static void main(String[] args) {
String s = "pwwkew";
int result = 0;
// result = method1(s);
// result = method2(s);
result = method3(s);
System.out.println(result);
}
/*
* 暴力解法
*/
public static int method1(String s) {
if(s.length() == 0)
return 0;
int result = 1;
for(int i = 0; i < s.length() - 1; i++) {
for(int j = i + 1; j < s.length(); j++) {
//对子串操作
boolean flag = false;
for(int k = i; k < j; k++)
if(s.charAt(k) == s.charAt(j)) {
flag = true;
break;
}
if(!flag) {
result = Math.max(result, j - i + 1);
}else {
break;
}
}
}
return result;
}
/*
* 滑动窗口
*/
public static int method2(String s) {
int n = s.length();
int result = 0;
int left = 0;
int right = -1;
int[] freq = new int[123];
while(left < n) {
if(right + 1 < n && freq[s.charAt(right + 1)] == 0) {
right++;
freq[s.charAt(right)] = 1;
}else {
freq[s.charAt(left)] = 0;
left++;
}
result = Math.max(result, right - left + 1);
}
return result;
}
/*
* 活动窗口的改进版
*/
public static int method3(String s) {
int n = s.length();
int result = 0;
HashMap<Character , Integer> map = new HashMap<>();
for(int end = 0, start = 0; end < n; end++) {
char stemp = s.charAt(end);
if(map.containsKey(stemp)) {
start = Math.max(start, map.get(stemp));
}
map.put(stemp, end + 1);
result = Math.max(result, end - start + 1);
}
return result;
}
}