题目链接:https://leetcode.com/problems/longest-substring-without-repeating-characters/
要求最长不重复子字符串而不是子序列
思路一:
最先想到的就是暴力解法,就是将所以长度大于2的子字符串都检测一遍,在对每个子字符串检测时,需要一个数据结构来存储字符出现的信息,并且当某个子串出现重复时,后续包含该子串的字符串就不需要检查了。上代码;
public static int lengthOfLongestSubstring(String s) {
if(s.length()==0)
return 0;
int ret=1;
for(int i=0;i<s.length()-1;i++)
{
for(int j=i+1;j<s.length();j++)
{
int[] record=new int[95];
int k=i;
for(;k<j+1;k++)
{
if(record[s.charAt(k)-' ']==0)
record[s.charAt(k)-' ']++;
else
break;
}
if(k<j+1)
break;
ret=Math.max(ret,j-i+1);
}
}
return ret;
}
暴力解法的思想十分朴素直接但是执行效率较低,还好AC了。
思路二:
思路一的子串检查是存在大量重复的,可以使用滑动窗口法。滑动窗口是一种常用于数组和字符串算法问题的抽象概念,滑动窗口是由 来限定,字符串检查重复可以使用HashSet。
public static int lengthOfLongestSubstring(String s) {
if(s.length()==0 || s==null)
return 0;
HashSet<Character> hs=new HashSet();
int i=0,j=1,ret=1,n=s.length();
while(i<n && j<n)
{
if(!hs.contains(s.charAt(j)))
{
j++;
hs.add(s.charAt(j));
}
else
{
hs.remove(s.charAt(i));
i++;
}
ret=Math.max(ret,j-i+1);
}
return ret;
}
思路三:
思路二中滑动窗口左侧i在每次j遇到重复只是向后移动一格,移动后的while检查和ret=Math.max(ret,j-i+1)这一步存在冗余,可以直接跳到下一个s[i]出现位置的下一个位置上
public static int lengthOfLongestSubstring2(String s) {
if(s.length()==0 || s==null)
return 0;
HashMap<Character,Integer> hm=new HashMap();
int i=0,j=0,n=s.length(),ret=1;
while(i<n && j<n)
{
if(hm.containsKey(s.charAt(j)))
{
i=Math.max(i,hm.get(s.charAt(j)));
}
ret=Math.max(ret,j-i+1);
hm.put(s.charAt(j),j+1);
j++;
}
return ret;
}
思路四:
因为字符集是ASCII集合,数量十分有限,所以采用思路一里面的检查子串重复的记录表的方法,index数组记录下一个s[i]出现的位置索引的下一个位置,这个方法是看的Solutions里面的解答,感觉吸收结合了思路一和思路三在存储和搜索上的优势,达到了效率的最大化。
我们以小写字母表为例手动运行代码感受一下其中的奥妙:以字符串“pwwkew”为例子:
public static int lengthOfLongestSubstring3(String s)
{
int[] index=new int[128];
int ret=1;
for(int i=0,j=0;j<s.length();j++)
{
i=Math.max(i,index[s.charAt(j)]);
ret = Math.max(ret,j-i+1);
index[s.charAt(j)]=j+1;
}
return ret;
}