无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
思路1:暴力法
以pwwkew为例:
设置leftIndex = 0,rightIndex = 0;
求p,最长为1
pw,最长为2
pww,存在重复字符串,因此放弃,leftIndex++;
w,最长为1
ww,有重复,放弃,leftIndex++;
w,最长为1
wk,最长为2
wke,最长为3
wkew,存在重复字符串,因此放弃,leftIndex++;
k,最长为1
ke,最长为2
kew,最长为3,由于rightIndex = nums.length - 1;leftIndex++;
e,最长为1
ew,最长为2,由于rightIndex = nums.length - 1;leftIndex++;
k,最长为1
需要解决的问题是,黄色部分,如何判断存在重复字符串?
假设,字符只包括26个字母,或者128个ASCII码,我们可以建立一个数组array,数组大小为26或者128,将每一个遍历到的字符存进数组,在判断新的字符的时候,查找数组对应的值array[i]是否>0
如果array[i]>0,说明已经存在对应的值,存在重复字符串
如果array[i] ==0,说明,不存在重复的字符
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s == null) return 0;
char[] charArray = s.toCharArray();
if(charArray.length == 0) return 0;
int max = 1;
for(int leftIndex = 0; leftIndex < charArray.length; leftIndex++)
{
//128个数组元素,preIndexArray[i] = j;表示第i个字符里面有j个
char[] preIndexArray = new char[128];
preIndexArray[charArray[leftIndex]]++;
int length = 1;//字符长度
for(int currentIndex = leftIndex + 1; currentIndex < charArray.length; currentIndex++){
if(preIndexArray[charArray[currentIndex]] > 0){
break;
}else{
preIndexArray[charArray[currentIndex]]++;
length++;
}
}
max = Math.max(max, length);
}
return max;
}
}
时间复杂度为:O(n^2)
空间复杂度为:O(n)
假设,字符不限于26个字母,也不限于128个ASCII码,也就是字符char什么都可以存,那么,我们就不能使用数组了,只能使用hashMap
思路2:DP
最值问题,考虑是否可以使用DP
是否为最值问题?
求无重复字符的最长子串,因此,是最值问题
能否使用DP?
- 原问题拆分为子问题
在求"pwwkew"的无重复字符的最长子串,可否变为求"pwwke"的无重复字符的最长子串,求完之后,结合前面的结果推算包含w的无重复字符的最长子串
2. 无后效性
前面的结果确定后,不会再改变,满足无后效性
DP解题步骤?
- 确定含义
dp(nums[i]),以nums[i]结尾的无重复字符的最长子串 - 边界值
dp(nums[0]) = 1; - 转移方程
如果dp(nums[i - 1])里面,没有nums[i]一样的字符,则dp(nums[i]) = dp(nums[i - 1]) + 1;
如果dp(nums[i - 1])里面,存在nums[i]一样的字符,假设该字符存在的位置是pi,则dp(nums[i]) = i - pi + 1;
注:
图中s[i],跟上面提到的nums[i]是一个东西
图中li,指的是nums[i - 1]的左边界
图中pi,指的是nums[i]字符,上一次出现的地方。这个作用主要体现在,不需要一步一步的变量查找
如何判断存在重复字符串?
假设字符只包括28个字母或者128个ASCII码,使用数组
之前在暴力法里面,array[i] = j;的含义是:字符i,有j个
现在,我们使用array[i] = j;表示的含义是:字符i,存储的位置为j
假设不限于28个字母,也不限于128个ASCII码,使用哈希map
先来个简单点的,假设,字符只包含128个ASCII码
class Solution {
public int lengthOfLongestSubstring(String s) {
//第一步:判断为空的边界值
if(s == null) return 0;
//字符串转换为数组
char[] sArray = s.toCharArray();
if(sArray.length == 0) return 0;
//至少有一个数据
//preIndexArray[i] = 3;符号i存在的位置为3
int[] preIndexArray = new int[128];
for(int i = 0; i<preIndexArray.length; i++){
preIndexArray[i] = -1;
}
preIndexArray[sArray[0]] = 0;
int max = 1;
//dp[i - 1]的左边边界
int leftIndex = 0;
for(int i = 1; i < sArray.length; i++){
int preIndex = preIndexArray[sArray[i]];
if(preIndex >= leftIndex){//之前出现的地方,在上一个左边界的右边或相等
leftIndex = preIndex + 1;
}
preIndexArray[sArray[i]] = i;
max = Math.max(max, i - leftIndex + 1);
}
return max;
}
}
现在,来看一下字符不限于ASCII码128个的时候
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s == null) return 0;
char[] charArray = s.toCharArray();
if(charArray.length == 0) return 0;
int max = 1;
int leftIndex = 0;
//建立hashMap,k是字符charArray[i],value是字符存在的位置
Map<Character, Integer> map = new HashMap<Character, Integer>();
map.put(charArray[0], 0);
for(int i = 1; i<charArray.length; i++){
Integer preIndex = map.get(charArray[i]);
if(preIndex != null && preIndex >= leftIndex){
leftIndex = preIndex + 1;
}
//存储preIndex到哈希map中
map.put(charArray[i], i);
max = Math.max(i - leftIndex + 1, max);
}
return max;
}
}
需要注意的地方:
map的创建以及取值、存储操作