LeetCode第3题思悟——无重复字符的最长子串(longest-substring-without-repeating-characters)

第三题

题目要求

Given a string, find the length of the longest substring without repeating characters.(给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。)

示例

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。

我的思路

遍历字符串,遇到字符时有两种情况:

  1. 该字符尚未出现过,所以子串继续扩展,长度加1,记录该字符出现的位置(下标),然后同已记录的最长子串长度比较,更新最长子串长度;
  2. 该字符出现过,此时也分两种情况:
    1. 该字符最近一次出现是在正在扩展的子串中,这样正在扩展的子串(xxxxTxxxxx)被分割为两部分,如果左边一部分的长度大于已记录的最长子串长度,则更新之;然后更新扩展子串的起点为T的位置
    2. 该字符曾经出现过,但是不在当前扩展的子串中,此时相当于该字符第一次出现,子串长度加1,更新最长子串长度;

代码如下:

//49ms
public static int lengthOfLongestSubstring(String s) {
    int length=s.length();
    int newStart=0,newEnd=0,newLength,lastLongestLength=0,lastAppearPosition;
    HashMap<Character,Integer> positions=new HashMap<>();
    char[] stringChars=s.toCharArray();
    for(int i=0;i<length;i++){
        if(positions.containsKey(stringChars[i])){//包含该字符
            lastAppearPosition=positions.get(stringChars[i]);
            if(lastAppearPosition>=newStart){//要发生切割啦
                if((newLength=lastAppearPosition-newStart+1)>lastLongestLength){//切割出去的部分大于已发现的最长字串长度
                    lastLongestLength=newLength;//更新最长长度
                }
                newStart=lastAppearPosition+1;
            }
        }
        newEnd=i;
        positions.put(stringChars[i],i);
        newLength=newEnd-newStart+1;
        lastLongestLength=lastLongestLength>newLength?lastLongestLength:newLength;
    }
    return lastLongestLength;

优秀解法

//26ms
public int lengthOfLongestSubstring(String s) {
	if (s == null || s.length() == 0) {
		return 0;
	}
	boolean[] chars = new boolean[256]; // 256 ASCII code
	int rst = 0;
	int start = 0;
	int end = 0;
	while (start < s.length()) {
		while (end < s.length() && !chars[s.charAt(end)]) {
			chars[s.charAt(end)] = true;
			rst = Math.max(rst, end - start + 1);
			end++;
		}
		chars[s.charAt(start)] = false;
		start++;
	}
	return rst;
}

//22ms
public int lengthOfLongestSubstring(String s) {
	int l=0;//left
	int r=-1;//right,窗口为[l,r]
	int res=0;
	int[] hash=new int[256];
	while(l<s.length()){
		if(((r+1<s.length())&&hash[s.charAt(r+1)]<1)){
			r++;
			hash[s.charAt(r)]++;
		}else{
			hash[s.charAt(l)]--;
			l++;
		}
		if(hash[s.charAt(r)]==1){
			res=Math.max(res, r-l+1);
		}
	}
	return res;
}

差距在哪里

26ms解法使用双重循环遍历字符串,使用boolean数组标记字符出现与否;在确定start的时候,向后移动end,如果end指示的字符没有出想过,更新最长子串长度,修改end的标记,向后移动;如果end重复了,那么向前移动start,修改start的标记;

实际上,我觉得这种解法存在的问题是,出现重复字符后,start每次移动一个长度,但是之所以会出现重复是因为end的移动,这样start需要移动的距离并不是1,而是要移动到重复的那个字符后面,虽然一步一步移动,也可以实现这个目标,但是在字符串长度很大的时候,直接跳转应该更好;另外,如果最长子串长度已经确定,而start距离末尾还有一段距离,那么该方法仍然会不断进入while循环,直到到达末尾,有一点无用功的感觉;当然,选择boolean数组的确比HashMap要轻巧一些;

22ms的解法实际上和26ms的解法思路一致,只是使用单层循环,并使用int数组标记字符出现的次数;如果因为窗口右边的移动而出现了重复,仍是选择移动左边边界;

自己的解法(实际上应该是在《程序员面试金典》上看到过这道题吧)使用单层循环,使用HashMap记录字符出现的位置,这里其实可以使用int数组,感觉代码会更简洁;因为只需要记录该字符最近出现的位置即可,而该值只有1个;遇到重复字符时,并不是移动窗口的左边,而是直接将窗口的左边移动到重复字符的下一个字符,以此消除重复;不过这样做的代价是需要判断一下因为移动窗口左边而跳过的子串长度是否是最长的子串;另外,这种做法实际上是在end到达字符串时就退出了,而22ms、26ms的解法是在start到达字符串时才退出;

问题是,为什么通过分析,自己的解法应该更快一些,而实际上却不是这样呢?问题有以下几点:

  1. 使用HashMap,查询虽然高效,但是相比数组直接访问,速度肯定慢了一些;
  2. 每次移动end,都更新一次最长子串长度;实际上,只需要在出现重复字符时,更新一次,然后作比较即可;

实际上,的确是这样~附图:

原来方案:
LeetCode第3题思悟——无重复字符的最长子串(longest-substring-without-repeating-characters)_第1张图片
改善后方案:
LeetCode第3题思悟——无重复字符的最长子串(longest-substring-without-repeating-characters)_第2张图片

你可能感兴趣的:(LeetCode思悟)