哈希表:无重复字符的最长子串
题目链接: https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
题目难度:中等
题目描述:给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3
1、暴力解法
-
解法一:先找到所有不重复子串,再统计最长子串的长度,需要将这些子串存储到容器中,在计算容器中最长子串的长度
算法思维:遍历 + 双指针(双指针可以很好地计算范围问题)
外层 start 内存 end, 当 start 和 end位置的不相等则start 不变end向后移动,当 start 和end 相等时,截取子串,然后start 向后移动,end 从start 位置继续。
class Solution { public int lengthOfLongestSubstring(String s) { int length; if( s == null || (length =s.length()) ==0) { return 0; } List<String> list = new ArrayList<>(); for(int start=0; start < length; start++) { for(int end = start + 1;end<length;end++) { String subStr = s.substring(start,end); //subString 截取子串,包含start 不包含end //判断end字符是否在截取的子串中已经存在 if(subStr.indexOf(s.charAt(end)) != -1) { break; } list.add(s.substring(start,end+1)); } } int maxLength =1; for(String sub : list) { int subLen; if((subLen = sub.length()) > maxLength) { maxLength = subLen; } } return maxLength; } }
-
解法二:先找到所有不重复子串,再统计最长子串的长度,取最大值
- 查找不含重复字符的子串,通过索引计算其长度
- 每次计算与上次字符长度对比,只保留最大的数值
class Solution {
public int lengthOfLongestSubstring(String s) {
int length;
if( s == null || (length =s.length()) ==0) {
return 0;
}
int maxLength =1;
for(int start=0; start < length; start++) {
for(int end = start + 1;end<length;end++) {
String subStr = s.substring(start,end); //subString 截取子串,包含start 不包含end
//判断end字符是否在截取的子串中已经存在
if(subStr.indexOf(s.charAt(end)) != -1) {
break;
}
int subLen = (end +1) - start;
if(subLen > maxLength) {
maxLength = subLen;
}
}
}
return maxLength;
}
}
2、Hash表
Hash table :(线性数据结构)
把关键码值映射到表中的一个位置,以加快查找速度。
Hash算法:
散列值,把任意长度的输入通过算法变成固定长度的输出是一种压缩映射,直接取余操作
哈希冲突的解决:开放寻址;再散列;链地址法
通过 对当个字符的 ASCII码进行计算,插入到 一个 仅支持ASCII码表字符数组中。
解题思路
- 定义哈希表,临时存储子串字符和查重
- 遍历字符串,通过双指针循环定位子串
- 右指针在hash表中是否存在:
- 否,记录到哈希表,移动右指针,计算长度
- 是,删除哈希表中 左指针元素,移动左指针,重复检查右指针元素是否还存在
- 右指针在hash表中是否存在:
- 每次计算子串长度,比较并保留最大值
class Solution {
public int lengthOfLongestSubstring(String s) {
int len;
if (s==null || (len = s.length()) == 0) {
return 0;
}
int res = 0 ; //记录最长子串的长度
int left = 0; //子串最左端索引
int right = 0; //子串最右端索引
//1.定义一个hash表,临时存储子串字符和查重
char[] chars = new char[128];
// 对字符串进行遍历
while (right < len) {
//1.判断右指针记录的元素是否在hash表中存在
char rightChar = s.charAt(right);
char c = chars[hash(rightChar) & (chars.length - 1)];
if (rightChar != c) { // 未重复出现
// 记录到哈希表彰,移动右指针计算 子串长度
chars[hash(rightChar) & (chars.length - 1)] = rightChar;
//右指针右移
right++;
//计算子串长度
int size = right - left;
res = res > size ? res : size;
} else {
//重复出现:删除hash表中左指针元素,移动左指针,重复检查右指针元素是否还存在
char leftChar = s.charAt(left++);
chars[hash(leftChar) & (chars.length - 1)] = '\u0000';
}
}
return res;
}
public int hash(char key) {
return key;
}
}
优化解法:
-
哈希表作用变形
字符ASCII码值 – > 字符
字符ASCII码值 – > 字符最后出现的索引
-
遇到重复元素后,左指针移动优化
逐个移动 到前一个相同字符出现后的位置
一次性定位到前一个相同字符出现后的位置
解法
-
初始化哈希表,存入非ASCII码值作为默认值
-
遍历字符串,使用双指针定位子串索引
字符已出现,取出哈希表中记录,左指针到记录+1 如果是abba的情况可能出现 左指针重新回到右侧,所以要进行判断,使指针只往右侧移动
无论是否出现,将右指针记录到哈希表
public int lengthOfLongestSubstring(String s) {
int len;
if (s==null || (len = s.length()) == 0) {
return 0;
}
int res = 0 ; //记录最长子串的长度
int left = 0; //子串最左端索引
int right = 0; //子串最右端索引
//定义一个数组存储索引
int[] arr = new int[128];
//设置默认值为-1
for (int i = 0; i < arr.length; i++) {
arr[i] = -1;
}
while (right < len) {
int c = s.charAt(right);
if (arr[c] != -1) { //字符已经出现
//取出 重复 位置的索引 直接定位到 该索引位置 +1 的位置
int start = arr[c] + 1;
//将左指针直接进行定位
left = left > start ? left : start;
}
arr[c] = right;
//计算子串的长度
int size = right - left + 1;
res = res > size ? res : size;
right++;
}
return res;
}
变形延伸
**题目连接:**https://leetcode-cn.com/problems/subarrays-with-k-different-integers/
**题目描述:**给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定不同的子数组为好子数组。
(例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。)
返回 A 中好子数组的数目。
解题思路:
- 计算不同整数个数恰好为k 的数组个数可以转换为 最多为k 个的数组个数 - 最多为k-1 个的数组的个数
- 定义left right 两个指针 起始位置为0,count 为记录 不同元素出现的个数 ,数组 arr 记录元素的出现的个数
- 先判断right位置的元素在arr中是否存在,如果不存在则count + 1 ,同时记录数组中arr[right] 元素个数+1 ,right ++
- 用res 记录 最多为k个元素子数组的个数 res += right - left;
- 判断count是否 > k 如果,大于 则将left指针处在arr数组中的次数-1 ,如果左指针对应的arr数组中的数为0 则,count -1 。左指针向右移动,
代码实现
class Solution {
public int subarraysWithKDistinct(int[] nums, int k) {
//计算不同整数个数恰好为k 的数组个数可以转换为 最多为k 个的数组个数 - 最多为k-1 个的数组的个数
return mostKDistinct(nums,k) - mostKDistinct(nums, k-1);
}
private int mostKDistinct(int[] nums, int k) {
int len = nums.length;
int[] arr = new int[len + 1];
//定义left right 两个指针 起始位置为0,count 为记录 不同元素出现的个数 ,数组 arr 记录元素的出现的个数
int left = 0,
right = 0,
count = 0;
int res = 0;
while(right < len) {
//先判断right位置的元素在arr中是否存在,如果不存在则count + 1 ,同时记录数组中arr[right] 元素个数+1 ,right ++
if(arr[nums[right]] == 0) {
count++;
}
arr[nums[right]]++;
right++;
while(count > k) {
arr[nums[left]]--;
if(arr[nums[left]]==0){
count--;
}
left++;
}
// 用res 记录 最多为k个元素子数组的个数 res += right - left;
res += right -left;
}
return res;
}
}
**题目描述:**给定一个字符串 s ,找出 至多 包含两个不同字符的最长子串 t ,并返回该子串的长度。
示例 1:
输入: "eceba"
输出: 3
解释: t 是 "ece",长度为3。
示例 2:
输入: "ccaabbb"
输出: 5
解释: t 是 "aabbb",长度为5。
解题思路:
- 定义一个哈希数组,用于表示当前元素是否出现,记录出现次数
- 定义一个变量用于计算出现不同元素的个数count
- 定义左右两个指针
- 判断右指针对应的元素是否出现过,如果未出现过count ++。如果已经出现过,则右指针继续移动
- 如果count > 2 则对左指针进行移动,如果当前hash数组中的元素个数为0 则 count–;
- 用 right - left表示子串的长度.
class Solution {
public int lengthOfLongestSubstringTwoDistinct(String s) {
//在此处写入代码
int len;
if(s== null || (len=s.length()) ==0){
return 0;
}
int right =0,left=0,count=0;
int[] arr = new int[128];
while(right < len) {
if(arr[s.charAt(right)] == 0){
count++;
}
arr[s.charAt(right)]++;
right++;
//如果新字符进入窗口 不同字符数量大于2,则左指针向右移动
if(count > 2) {
if(--arr[s.charAt(left++)]== 0) {
count--;
}
}
}
return right - left;
}
}