LeetCode003——无重复字符的最长子串

版权声明:版权所有,转载请注明原网址链接。 https://blog.csdn.net/qq_41231926/article/details/81784418

原题链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/description/

题目描述:

知识点:滑动窗口

思路一:暴力解法

用双重循环来求得所可能取得的所有的子串,再在内存设置一个循环来判断新加入的元素是否是子串中已有的元素,以此来判断每一子串是否重复。

对于时间复杂度来说,由于有三重循环,很多人以为会是O(n ^ 3)的复杂度,但其实外面两层循环其实本质上是一个滑动窗口,只不过我们以for循环的形式来呈现而已,我们在判断当前子串不满足条件后,不会再让子串右端右移,而是让子串左端右移再判断。而内部的一重循环循环次数是滑动窗口的长度,因此这个算法的时间复杂度其实是O(n)级别的。具体请见JAVA代码。对于空间复杂度,我们没有额外使用任何空间,故是O(1)级别的。

JAVA代码:

public class Solution {
	
	public int lengthOfLongestSubstring(String s) {
		if(s.length() == 0) {
			return 0;
		}
		int result = 1;
		for (int i = 0; i < s.length(); i++) {
			for (int j = i + 1; j < s.length(); j++) {
				//判断[i, 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;
	}
}

LeetCode解题报告:

思路二:滑动窗口法

本题中还要注意的一点是,题目并没有规定字符串中的字符全是小写字母,所以我们需要用一个长度为256的数组来保存出现的字符。为什么用长度为256的数组呢?因为虽然在Java语言中,char型变量占2个字节,可以表示的范围是0~65535(没有负数),但是Java使用的Unicode编码的方式表示的字符,将中文字符也包含了进去,而我们一般的ASCII码表就是256个字符。

如果说新扫描的元素还没有出现过,则滑动窗口的右指针持续右移,别忘记更新那个记录出现过元素的数组的值。如果已经出现过,则滑动窗口的左指针持续右移,也别忘记更新记录元素的数组值。

对于滑动窗口来说,我们可以看作是扫描了两次数组,因此时间复杂度可以看作是O(n)级别的,其中n表示数组的长度。而空间复杂度,我们只使用了一个长度为256的数组,因此空间复杂度为O(1)级别的。但是这里的空间复杂度显然是比思路一的空间复杂度要高的,这也体现了哈希表空间换时间的思想,只不过这里我们的哈希表用的是一个一维数组。

JAVA代码:

public class Solution {

	public int lengthOfLongestSubstring(String s) {
        char[] arr = s.toCharArray();
        int n = arr.length;
        int[] freq = new int[256];
        for (int i = 0; i < freq.length; i++) {
			System.out.println(freq[i]);
		}
        int left = 0;
        int right = -1;			//设置[left, right]为滑动窗口
        int len = 0;
        while(left < n) {
        	if(right + 1 < n && freq[arr[right + 1] - 'a' + 'a'] == 0) {
        		right++;
        		freq[arr[right] - 'a' + 'a'] = 1;
        	}else {
        		freq[arr[left] - 'a' + 'a'] = 0;
        		left++;
        	}
        	len = Math.max(len, right - left + 1);
        }
        return len;
    }
}

LeetCode解题报告:

思路三:改变思路二的循环终止条件

因为我们要找的是不包含重复字符的最长子串,当当前子串的右端移动到最右边之后,当前子串的左端向再右缩进只会减短子串的长度,因此就无需再循环判断。

这个改进操作是常数级别的时间复杂度的优化,因此此思路的时间复杂度和空间复杂度与思路二相同。

JAVA代码:

public class Solution {

	public int lengthOfLongestSubstring(String s) {

		int[] freq = new int[256];

		int l = 0, r = -1; //滑动窗口为s[l...r]
		int res = 0;

		while( r + 1 < s.length() ){

			if( r + 1 < s.length() && freq[s.charAt(r+1)] == 0 )
				freq[s.charAt(++r)] ++;
			else    //freq[s[r+1]] == 1
				freq[s.charAt(l++)] --;

			res = Math.max(res, r-l+1);
		}

		return res;
	}
}

LeetCode解题报告:

思路四:思路二的另一种实现方式

改用while循环里用另外两个while循环来判断滑动窗口左右两端的位置。

时间复杂度和空间复杂度与思路二相同。

JAVA代码:

public class Solution {

	public int lengthOfLongestSubstring(String s) {

        int[] freq = new int[256];

        int left = 0, right = -1; //滑动窗口为s[left...right]
        int res = 0;

        while(right + 1 < s.length()) {
        	while(right + 1 < s.length() && freq[s.charAt(right + 1)] == 0) {
        		freq[s.charAt(++right)]++;
        	}
        	res = Math.max(res, right - left + 1);
        	if(right + 1 < s.length()) {
        		freq[s.charAt(++right)]++;
        		while(left <= right && freq[s.charAt(right)] == 2) {
        			freq[s.charAt(left++)]--;
        		}
        	}
        }
        return res;
	}
}

LeetCode解题报告:

思路五:滑动窗口的左端口每次可以向前跳跃不仅仅是1的距离

我们的滑动窗口的左端口可以一直向右移动直到移动到我们记录的哈希表中没有重复元素为止。但我们为了找到那个使得我们的哈希表没有重复元素的第一个位置,我们就需要遍历整个窗口的字符串。事实上,你会发现这其实就是我们思路一的暴力解法的另一个实现而已。

时间复杂度和空间复杂度均和思路一相同。

JAVA代码:

public class Solution {

	public int lengthOfLongestSubstring(String s) {
		int left = 0, right = 0;		//滑动窗口为[left, right]
		int res = 0;
		while(right < s.length()) {
			int index = isDuplicateChar(s, left, right);
			//如果s[right]之前出现过
			//left可以直接跳到s[right]之前出现的位置+1的地方
			if(index != -1) {
				left = index + 1;
			}
			res = Math.max(res, right - left + 1);
			right++;
		}
		return res;
	}
	
	//查看s[left, right - 1]之间是否存在s[right],若存在,返回相应的索引,否则返回-1
	private int isDuplicateChar(String s, int left, int right) {
		for(int i = left ; i < right ; i ++)
            if(s.charAt(i) == s.charAt(right))
                return i;
        return -1;
	}
}

LeetCode解题报告:

思路六:对思路五的改进

由于我们在思路五中每次都要遍历一遍滑动窗口来确定重复的字符的位置,事实上我们完全可以用一个哈希表来记录该位置,这个哈希表的原理和思路二中的哈希表的原理相同,只不过思路二中的哈希表记录的是各个字符是否出现过,而本思路中的哈希表记录的是各个字符出现的位置。

这个改进是常数级别的改进,因此时间复杂度也是O(n)级别的,而空间复杂度是O(256),即O(1)级别的。这里再一次体现了哈希表的空间换时间的思想

JAVA代码:

public class Solution {

	public int lengthOfLongestSubstring(String s) {

        int[] last = new int[256];
        Arrays.fill(last, -1);
        int left = 0, right = -1; //滑动窗口为s[left, right]
        int res = 0;
        while(right + 1 < s.length()){
            right++;
            if(last[s.charAt(right)] != -1) {
            	left = Math.max(left, last[s.charAt(right)] + 1);
            }
            res = Math.max(res, right - left + 1);
            last[s.charAt(right)] = right;
        }
        return res;
	}
}

LeetCode解题报告:

猜你喜欢

转载自blog.csdn.net/qq_41231926/article/details/81784418