最小覆盖子串
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:
- 如果 S 中不存这样的子串,则返回空字符串 ““。
- 如果 S 中存在这样的子串,我们保证它是唯一的答案。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/minimum-window-substring
解题思路
1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0
,把索引左闭右开区间 [left, right)
称为一个「窗口」。
2、我们先不断地增加 right
指针扩大窗口 [left, right)
,直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
3、此时,我们停止增加 right
,转而不断增加 left
指针缩小窗口 [left, right)
,直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left
,我们都要更新一轮结果。
4、重复第 2 和第 3 步,直到 right
到达字符串 S 的尽头。
这个思路其实也不难,第 2 步相当于在寻找一个 「可行解」,然后第 3 步在优化这个可行解」,最终找到最优解,也就是最短的覆盖子串。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动,这就是 「滑动窗口」这个名字的来历。
PHP 实现
function minWindow($s, $t) {
$need = [];
$window = [];
$length = strlen($t);
// 测试用例里有一些奇葩的,这里加个判断
if ($length > strlen($s)) {
return '';
}
// 将寻找的字符串装入 $need 数组
for ($c = 0; $c < $length; $c++) {
$need[$t[$c]] = isset($need[$t[$c]]) ? $need[$t[$c]] + 1 : 1;
}
$left = 0;
$right = 0;
$valid = 0;
// 记录最小覆盖子串的起始索引及长度
$start = 0;
$len = PHP_INT_MAX;
while ($right < strlen($s)) {
// $c 是将移入窗口的字符
$c = $s[$right];
// 右移窗口
$right++;
// 进行窗口内数据的一系列更新
if (isset($need[$c])) {
$window[$c] = isset($window[$c]) ? $window[$c] + 1 : 1;
if ($window[$c] === $need[$c]) {
$valid++;
}
}
// 判断左侧窗口是否要收缩
while ($valid === count($need)) {
// 在这里更新最小覆盖子串
if ($right - $left < $len) {
$start = $left;
$len = $right - $left;
}
// d 是将移出窗口的字符
$d = $s[$left];
// 左移窗口
$left++;
// 进行窗口内数据的一系列更新
if (isset($need[$d])) {
if ($window[$d] === $need[$d]) {
$valid--;
}
$window[$d]--;
}
}
}
// 返回最小覆盖子串
return $len === PHP_INT_MAX ? '' : substr($s, $start, $len);
}
return minWindow($s = "ADOBECODEBANC", $t = "ABC"); // output: BANC
参考链接:
- 滑动窗口解题框架思路,强烈推荐这个网站的作者