【leetcode】3. Longest Substring Without Repeating Characters 三种方法

34 篇文章 0 订阅
13 篇文章 0 订阅

查找给定字符串的最长不重复子串。

方法一

该方法很直接,依次从每一个字符开始,向后逐个遍历,直到遇到重复的字符为止。判断字符是否重复,用到了hash数组,可以在常数时间内判断当前字符在本次循环中是否访问过,由于针对的是ASCII字符集,故使用的hash数组大小为256.

int lengthOfLongestSubstring(string& s)
{
	int n = s.size();
	int maxLen = (0 == n)? 0:1;
	char visited[256];
	for(int i = 0; i < n; i++)
	{
		memset(visited, 0 ,256);
		visited[s[i]] = 1;
		int j = i + 1;
		if(maxLen > n - j)
				return maxLen;
		for(j = i + 1; j < n; j++)
		{
			if(0 == visited[s[j]])
			{
				visited[s[j]] = 1;
			}
			else
			{
				if(j - i > maxLen)
				{
					maxLen = j - i;
				}
				break;
			}
		}
		if(j - i > maxLen)
		{
			maxLen = j - i;
		}
	}
	return maxLen;
}
分析代码,可知时间复杂度为O( n2),空间复杂度为O(1)。

方法二

纯动态规化方法。对于给定的字符串s,用DP[i]来存储第i个字符所在的当前最长不重复子串的长度。

举个例子来说明一下,比如字符串str="abcdbd",易知从头开始的"abcd"构成一个不重复的子串,之后该考察str[4],依次与str[3]、str[2]、str[1]比较,当与str[1]比较时,第一次发现了重复。故此时DP[4] = 4 - 1 = 3; 同理,之后该考察str[5],依次与str[4]、str[3]比较,当与str[3]比较时,第一次发现了重复,故此时DP[5] = 5-3=2;

即: DP[0] = 1, DP[1] = 2, DP[2] = 3, DP[3] = 4, DP[4] = 4 - 1 = 3, DP[5] = 5 - 3 = 2.

总结该方法: 对于每个字符,都逐个向前去搜索 该字符是否出现过。如果没有出现过,那么DP[i] = DP[i-1] + 1,如果出现过,记从后向前比较中,第一次出现的位置为i,则DP[i] = j - i; 

注意,这里的向前搜索只搜索到新子串的开头即结束。

理解了该原理,就会发现该方法的时间复杂度为O(n2),空间复杂度为O(n)。

int lengthOfLongestSubstring(string& s)
{
	int n = s.size();
	int maxLen = (0 == n)? 0:1;
	unsigned short DP[31660];
	DP[0] = 1;
	int curStartIndex = 0;
	for(int i = 1; i < n; i++)
	{
		int j = i-1;
		for(; j >= curStartIndex; j--)
		{
			if(s[i] == s[j])
			{
				DP[i] = i - j;
				curStartIndex = j;
				break;
			}
		}
		if(curStartIndex - 1 == j)
			DP[i] = DP[i-1] + 1;
			
		if(DP[i] > maxLen)
			maxLen = DP[i];
	}
	return maxLen;
}

由空间复杂度为O(n)、时间复杂度为O( n 2)可以看出,这种方法很糟糕。

方法三

把动态规化与hash数组结合起来,应该是一种不错的组合。

如果我们要在O(n)的时间复杂度内解决问题,则必须要在常数时间内检测到重复,同时,在常数时间内确定重复发生时对应的最长不重复子串的长度,并在常数时间内确定好下一次新子串的起始位置。

因为我们每次考察的都是新的子串,所以用int curStartIndex来标记本次子串的起始位置。

我们用hash数组来存储 最新的字符的下标,用于在常数时间内检测重复及确定新子串的起始位置。

我们再用一个缓存数组DP[i]来存储第i位发生重复时,i之前的、距离i最近的最长不重复子串的长度。

这样我们就可以在O(n)时间复杂度内解决战斗。

int lengthOfLongestSubstring(string& s)
{
	int n = s.size();
	int maxLen = (0 == n)? 0:1;
	unsigned int DP[31660];
	int visited[256];
	//注意,memset是按字节来赋值的
	//由于-1的补码为0xFFFFFFFF,所以给每个字节赋值为0xFF
	//所以再次按整型读取的时候,依然是0xFFFFFFFF
	memset(visited, -1, sizeof(visited));
	DP[0] = 1;
	visited[s[0]] = 0;
	int curStartIndex = 0;
	for(int i = 1; i < n; i++)
	{
		//当前字符没有被访问过
		//或者,被访问过,但是是在新起点之前
		if(-1 == visited[s[i]] || curStartIndex > visited[s[i]])
		{
			//表示该字符在当前子串中没有重复出现
			DP[i] = DP[i-1] + 1;
			//hash数组存储字符对应的最新下标
			visited[s[i]] = i;
		}
		else
		{
			//当前字符在 子串中已经出现过
			//所以新不重复子串的长度
			//要从前面那个重复的字符的下一位开始计算
			DP[i] = i - visited[s[i]];
			curStartIndex = visited[s[i]] + 1;
			//更新字符下标
			visited[s[i]] = i;
		}
		if(DP[i] > maxLen)
		{
			maxLen = DP[i];
		}
	}
	return maxLen;
}

可以看到,这里动态规化的状态更新方程当前值只与上一个相邻值有关。

因为,没有必要保存 除了上一个值之外的状态值。

想到这一点,O(n)空间复杂度瞬间变O(1)。

这样,我们就实现了时间复杂度为O(n),空间复杂度为O(1)的算法。

int lengthOfLongestSubstring(string& s)
{
	int n = s.size(), curStartIndex = 0;
	int maxLen = (0 == n)? 0:1;
	unsigned int lastMaxLen = 1;
	int visited[256];
	memset(visited, -1, sizeof(visited));
	visited[s[0]] = 0;

	for(int i = 1; i < n; i++)
	{
		if(-1 == visited[s[i]] || curStartIndex > visited[s[i]])
		{
			lastMaxLen = lastMaxLen + 1;
			visited[s[i]] = i;
		}
		else
		{
			lastMaxLen = i - visited[s[i]];
			curStartIndex = visited[s[i]] + 1;
			visited[s[i]] = i;
		}
		if(lastMaxLen > maxLen)
		{
			maxLen = lastMaxLen;
		}
	}
	return maxLen;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值