我的LeetCode:https://leetcode-cn.com/u/ituring/
我的LeetCode刷题源码[GitHub]:https://github.com/izhoujie/Algorithmcii
LeetCode 3. 无重复字符的最长子串
题目
给定一个字符串,请你找出其中不含有重复字符的__最长子串__的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
显而易见,本题适用滑动窗口的思想来解决,具体的思路分为以下几种;
- 使用set存窗口内值,保证唯一;(较为简单,不做具体分析和代码实现)
- 使用indexOf(int ch, int fromIndex)方法特性,对字符首次出现的位置进行逻辑判断;
- 使用ASII存储字符出现的位置,实际与第二种方法思路相同,只不过改用数组存储记录字符截至当前窗口最后一次出现的位置;
思路1-使用indexOf(int ch, int fromIndex)方法特性
思路解析:定义窗口指针left和right,若[left,right)内的字符都是唯一的,那么s.indexOf(s.charAt(right), int left)的索引index只可能小于left或大于等于right,
否则,更新left=index+1以保证下一个窗口内[left,right+1)的字符仍然都是唯一的;
步骤:
- 定义窗口指针left,right和记录最大值max;
- right逐步右移,查找s.indexOf(s.charAt(right), left)得index,若index不在[left,right)区间内,则右侧窗口可+1,更新max;
- 若index在区间[left,right)内,则更新left=index+1,即舍弃相同字符的靠左侧位置保留当前的靠右的right位置,此过程为缩小窗口无需计算max;
- 右移窗口right++;
算法复杂度:
- 时间复杂度: $ {\color{Magenta}{\Omicron\left(n\right)}} $ n为s的长度
- 空间复杂度: $ {\color{Magenta}{\Omicron\left(1\right)}} $
思路2-使用ASII存储字符出现的位置;
思路解析:遍历并记录字符出现的最新位置(索引位置+1),若当前的字符是第二次出现,则可获得的最大无重复长度为当前位置索引减去之前字符出现的位置;
步骤:
- 创建256长度的数组,用以保存ASCII字符;
- 可获得的最大长度为当前位置减去当前遍历的字符在数组中的最新位置;
- 更新当前字符在数组中的位置;
算法复杂度:
- 时间复杂度: $ {\color{Magenta}{\Omicron\left(n\right)}} $ n为s的长度
- 空间复杂度: $ {\color{Magenta}{\Omicron\left(1\right)}} $
算法源码示例
package leetcode;
/**
* @author ZhouJie
* @date 2019年12月4日 下午1:56:17
* @Description: 3. 无重复字符的最长子串
*
*/
public class LeetCode_0003 {
}
class Solution_0003 {
/**
* @author ZhouJie
* @date 2019年12月5日 下午8:55:49
* @Description: TODO(方法简述)
* @return int
* @UpdateUser-UpdateDate:[ZhouJie]-[2020年2月3日 上午11:08:25]
* @UpdateRemark:滑动窗口思想,双指针
*
*/
public int lengthOfLongestSubstring(String s) {
int len = 0;
if (s == null || (len = s.length()) < 2) {
return len;
}
int left = 0, right = 0, max = 0;
while (right < len) {
int index = s.indexOf(s.charAt(right), left);
// 只要当前字符的首次出现位置index不在[left,right]区间内,则找到一个可能值,计算并记录,否则把left的位置重置为index+1
if (index < left || index >= right) {
max = Math.max(max, right - left + 1);
} else {
left = index + 1;
}
right++;
}
return max;
}
/**
* @author ZhouJie
* @date 2020年2月3日 上午11:35:15
* @Description: TODO(方法简述)
* @return int
* @UpdateUser-UpdateDate:[ZhouJie]-[2020年2月3日 上午11:35:15]
* @UpdateRemark:2-使用码表记录字符出现的最新位置,并计算无重复段的长度
*
*/
public int lengthOfLongestSubstring_1(String s) {
int len = 0;
if (s == null || (len = s.length()) < 2) {
return len;
}
char[] cs = s.toCharArray();
// 定义ascii的256字符码表
int[] ASCII = new int[256];
int start = 0, rst = 0;
for (int i = 0; i < len; i++) {
// start记录截止i位置无重复字符的起始位置,若ASCII[cs[i]]比start大,说明在[start,i]之间出现了重复字符cs[i],需要将start更新为ASCII[cs[i]]
start = Math.max(ASCII[cs[i]], start);
// 计算当前无重复段的长度
rst = Math.max(rst, i - start + 1);
// 记录 cs[i]字符的最新位置;
ASCII[cs[i]] = i + 1;
}
return rst;
}
}