leetcode题目:(3) - 无重复字符的最长子串

题目:

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

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

解题思路1:暴力法

比如有字符串abcdefgbcd,那么思路如下
ab -> 外层遍历i=0;内层遍历g=1;然后再将ab子字符串遍历一遍判断是否有重复字符
abc
abcd
abcde
abcdefg
abcdefgb
abcdefgbc
abcdefgbcd

按照这种方式,外层与内层遍历的时间复杂度为O(n2),然后每次内层还需要一次遍历判断子字符串是否有重复,那么时间复杂度为O(n3)

暴力法代码如下
public class Solution {
    public int lengthOfLongestSubstring(String s) {
    	int slen = s.length();
    	int ans = 0; 	/* 遍历过程中随时更新的最长子字符串的长度 */
    	for(int i=0;i<slen;i++){
    		for(int j=i+1;j<slen;j++){
    			if(unique(s,i,j)) result = Math.max(ans,j+1-i);		/* 每次内层遍历都要再对子字符串遍历一次,获取当前最长字串的长度 */
    		}
    	}
    	return ans;
    }
	public boolean unique(String s,int start,int end){
		/* 法作用是利用hashsat快速判断char是否已经存在 */
		Set<Character> set = new HashSet<>();
		for(int i=start;i<end;i++){
			char ch = s.charAt(i);
			if(set.contains(ch)) return false;
			set.add(ch);
		}
		return true;
	}
}

总结:暴力法思路比较容易理解,但是时间复杂度太差

解题思路2:滑动窗口

滑动窗口是一个字符串题目中经常出现的一个抽象逻辑,类似双指针,定义两个指针之间的子字符串为一个集合,动态调整两个指针获取子字符串的长度;
本题中动态窗口的解体关键点为:我们认为i作为子字符串字符的开始,j向后遍历,当发现 j 字符已经出现在了[ i , j-1 ]字符集合内,那么我们可以断定,在[ i , j+1 ]字符集合内,最长的子字符串一定是j+1-i,那么i就可以直接从[ i , j ]之间找到重复的字符x,再从x+1作为子字符串的start即可.

滑动窗口代码如下
class Solution {
    public int lengthOfLongestSubstring(String s) {
    	Set<Character> set = new HashSet<>();
    	int strlen = s.length();
    	int ans = 0 , i = 0 , j = 0;
    	while( i < strlen && j < strlen ){
    		if(!set.contains(s.charAt(j))){		/* 如果遍历的j字符在i开头的子字符串中不重复 */
    			set.add(s.charAt(j));
    			ans = Math.max(ans,j+1-i);
    			j++;
    		}
    		else{	/* 发现j已经和字符串中字符重复,则删掉i,直到子字符串内不再有j字符 */
    			set.remove(i);
    			i++;
    			/* 这个地方就是思路中说的:一旦发现j重复,那么[i , j]最大子字符串值一定就是[ i , j-1 ] ,所以i到子字符串中重复j字符之间,根本没有再判断长度的必要了 */
    		}
    	}
    	return ans;
    }
}

该方法最差情况就是每个字符都会被 i 和 j 访问,所以可以认为时间复杂度:O(2n) = O(n)

解题思路3:优化滑动窗口

方法2中,在发现 [ i , j ) 之间具有重复的字符 j’ 后,还是需要 i++ 遍历到 j’ 这个字符,其实这个遍历完全可以省略掉,那就是以空间换时间,增加HashMap,记录如果该字符出现重复,需要跳转的位置;

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>(); 
        for (int j = 0, i = 0; j < n; j++) {
            if (map.containsKey(s.charAt(j))) {     /* hashmap记录重复字符跳转的位置 */
                i = Math.max(map.get(s.charAt(j)),i);       /* 这个地方用max判断当前i和重复字符跳回索引地址大小,主要是为了防止出现i跳回到当前窗口之外的早期重复的字符跳转索引处 */
            }
            ans = Math.max(ans, j - i + 1);         /* 更新最大子字符串长度 */
            map.put(s.charAt(j), j + 1);            /* 更新hashmap,对应重复字符最新的跳转位置 */
        }
        return ans;
    }
}

方法3空间可能使用上略大于方法2的集合,不过能稍微更快一些,满足了时间复杂度:O(n)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值