哈希表:无重复字符的最长子串

哈希表:无重复字符的最长子串

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

题目难度:中等

题目描述:给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

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

1、暴力解法

  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;
        }
    }
    
  2. 解法二:先找到所有不重复子串,再统计最长子串的长度,取最大值

    1. 查找不含重复字符的子串,通过索引计算其长度
    2. 每次计算与上次字符长度对比,只保留最大的数值
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码表字符数组中。

解题思路

  1. 定义哈希表,临时存储子串字符和查重
  2. 遍历字符串,通过双指针循环定位子串
    1. 右指针在hash表中是否存在:
      • 否,记录到哈希表,移动右指针,计算长度
      • 是,删除哈希表中 左指针元素,移动左指针,重复检查右指针元素是否还存在
  3. 每次计算子串长度,比较并保留最大值
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;
    }
}

优化解法:

  1. 哈希表作用变形

    字符ASCII码值 – > 字符

    字符ASCII码值 – > 字符最后出现的索引

  2. 遇到重复元素后,左指针移动优化

    逐个移动 到前一个相同字符出现后的位置

    一次性定位到前一个相同字符出现后的位置

解法

  1. 初始化哈希表,存入非ASCII码值作为默认值

  2. 遍历字符串,使用双指针定位子串索引

    字符已出现,取出哈希表中记录,左指针到记录+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;
    }

变形延伸

  1. k个不同整数的子数组

**题目连接:**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;
    }
}
  1. 至多包含两个不同字符的最长子串

**题目描述:**给定一个字符串 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;
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值