暴力的美化——滑窗的进阶(最小覆盖子串)

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:

输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”

说明:

如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

首先,根据题目,首先最应该想到的就是暴力求解,使用最单纯的暴力思路去把题目给解出来。
思路:

  • 枚举输入字符串S的所有长度大于等于T的子串;
  • 逐个判断这些子串中,哪些子串覆盖了字符串T的所有字符;
  • 在枚举的过程中,记录符合条件的,长度最短的那个子串。

但是,如何判断S的子串包含了T中的所有字符?

参考做法:分别统计S的子串和T中每个字符出现的次数,然后逐个比对。

这样做答案肯定是对的,但是暴力解法的时间复杂度太高,达到了: O(|S|’^3+|T|),
这里|S|表示字符串S的长度,这里|T| 表示字符串T的长度;
空间复杂度有: 0(|S|+ |T|)。
所以,我们要想一个办法去逐步优化暴力解法——sliding window
采用双指针去找最小子串,定义winFreq,和tFreq两个字符频度数组去记录S,T 中的字符个数,
定义变量distance,

distance表示滑动窗口内部包含了T中字符的个数,窗口内单个字符个数等于T中对应的字符个数的时候不再增加
●当右边界向右滑动,且winFreq[s[right]] < tFreq[s[right]] 时,distance + 1
●当左边界向右滑动,且winFreq[s[left]] == tFreq[s[left]] 时,distance - 1

下面给出代码(代码里会有许多代码重用的地方以及还能优化的地方(例如可以不使用distance,而使用hashmap去检查是否包含全部子串),导致了可读性降低,小伙伴们可以自行优化),代码里会有详细注释(如有错,欢迎指正):

	public String minWindow(String s, String t) {
		int sLen = s.length();
		int tLen = t.length();
		if (sLen == 0 || tLen == 0 || sLen < tLen) {
			return "";
		}

		char[] charArrayS = s.toCharArray();
		char[] charArrayT = t.toCharArray();

		// 采用字符频度数组记录s子串是否包含t中所有字符
		int[] winFreq = new int[128];
		int[] tFreq = new int[128];
		for (char c : charArrayT) { //记录t中字符的个数
			tFreq[c]++;
		}

		// 滑动窗口内部包含多少T中的字符,对应字符频数超过不重复股计算
		//distance表示滑动窗口内部包含了T中字符的个数,窗口内单个字符个数等于T中对应的字符个数的时候不再增加
		int distance = 0;//利用distance计算winFreq中的子串是否包含了tFreq中的全部子串   
		int minLen = sLen + 1; //记录最小子串的长度
		int begin = 0;//记录最小子串的起始址

		int left = 0;//左右指针
		int right = 0;
		// [left, right)左开右闭使right-left恰好为长度
		while (right < sLen) {//起初右指针先移动,找到恰好满足子串全部包含t中的字符
			if (tFreq[charArrayS[right]] == 0) {//判断是否是t中的字符,不是则直接指针右移
				right++;
				continue;
			}
			if (winFreq[charArrayS[right]] < tFreq[charArrayS[right]]) {//判断子串中的t包含的字符是否超过t中的个数,使distance恰好等于t中的个数
				distance++;
			}
			winFreq[charArrayS[right]]++;
			right++;//右移
			while (distance == tLen) { //当s子串恰好包含t中的全部字符,左指针开始移动
				if (right - left < minLen) {//判断此时的字符串长度是否是最小,如果不是,则更新长度和起点
					minLen = right - left;
					begin = left;
				}

				if (tFreq[charArrayS[left]] == 0) {//同理判断左边界是否包含t中的字符
					left++;
					continue;
				}
				if (winFreq[charArrayS[left]] == tFreq[charArrayS[left]]) {//左边界字符恰好等于t中该字符的个数,则令distance-1,因为后面左指针会左移
					distance--;
				}
				winFreq[charArrayS[left]]--;//个数减一
				left++;//左移
			}
		}
		if (minLen == sLen + 1) { //没有找到最小长度
			return "";
		}

		return s.substring(begin, begin + minLen);

	}

最后,不经历风雨,怎能在计算机的大山之顶看见彩虹呢! 无论怎样,相信明天一定会更好!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值