一、判断一个字符串(strOne)是否是另一个字符串(strTwo)的子串
思路:
- 从下标0开始遍历strTwo,一直遍历到strlen(strTwo) -strlen(strOne),
- 内嵌循环,依次和 strOne每一个字符是否相等,如果不等,strTwo右移,进行下一轮循环。如果相等返回 true。
function isChildStr($strOne, $strTwo) {
$lengthOne = strlen($strOne);
$lengthTwo = strlen($strTwo);
$j = 0;
for ($i = 0; $i <= $lengthTwo - $lengthOne; $i++) {
for ($j = 0; $j < $lengthOne; $j++) {
if ($strTwo[$i + $j] !== $strOne[$j]) break;
}
if ($j === strlen($strOne)) return true;
}
return false;
}
测试数据:
sss sdshss
输出 false
sss sdshsss
输出 true
二、一个字符串中最长不重复子串问题
思路:
4. 记录当前字符之前的最长不重复子串长度,记为L(i - 1),其中i为当前字符的位置。
5. 遍历字符串:
(1)若当前字符第一次出现,则MaxL = L(i - 1) + 1
(2)若不是第一次出现,计算当前字符与它上次出现位置之间的距离,记为d:
1> 若d > L(i - 1),说明前一个非重复子字符串中没有包含当前字符,则MaxL = L(i - 1) + 1。
2> 若d < L(i - 1),说明前一个非重复子字符串中已经包含当前字符,则MaxL = d
function findMaxStringLength($str){
if ($str == null || $str === "") return '不合法的字符串';
$maxLength = 0; //最大长度
$currentLength = 0; //当前字符前面的不重复子串长度
$position = array();
for ($i = 0; $i < 26; $i++) {
$position[$i] = -1; //初始化为-1,负数表示没出现过
}
for ($i = 0; $i < strlen($str); $i++) {
$char = ord($str[$i]) - ord('a');
$prePosition = $position[$char];
//计算当前字符与它上次出现位置之间的距离
$distance = $i - $prePosition;
//当前字符第一次出现,或者前一个非重复子字符串中没有包含当前字符
if ($prePosition < 0 || $distance > $currentLength) {
$currentLength++;
} else {
//如果当前长度大于最大长度,更新
if ($currentLength > $maxLength) {
$maxLength = $currentLength;
}
$currentLength = $distance;
}
//更新字符出现的位置
$position[$char] = $i;
}
if ($currentLength > $maxLength) {
$maxLength = $currentLength;
}
return $maxLength;
}
三、最长公共子串
这个问题的核心点在于,如果在两个字符串中找到连续的且公共部分最长的子串。
我们可以得到一个递推公式:
d p [ i ] [ j ] { d p [ i − 1 ] [ j − 1 ] + 1 , i f i , j > 0 a n d x i = y i 0 , i f i , j > 0 a n d x i ! = y i 1 , i f i = 0 ∣ ∣ j = 0 a n d x i = y i dp[i][j]\left\{ \begin{array}{c} dp[i - 1][j - 1] + 1, if i, j >0 and xi = yi\\ 0, if i, j >0 and xi != yi\\ 1, if i = 0 || j=0 and xi = yi \end{array}\right. dp[i][j]⎩⎨⎧dp[i−1][j−1]+1, if i,j>0 and xi=yi0, if i,j>0 and xi!=yi1, if i=0∣∣j=0 and xi=yi
从上面,我们可以看出,矩阵的每一个对角线上,连线最长的为最长公共子串,最后得出的数字,即为长度。
function getMaxLengthStr($strOne, $strTwo)
{
$dp = array(array());
$length = 0;
$maxEnd = 0;
for ($i = 0; $i < strlen($strOne); $i++) {
for ($j = 0; $j < strlen($strTwo); $j++) {
//如果相等
if ($strOne[$i] == $strTwo[$j]) {
$dp[$i][$j] = ($i > 0 && $j > 0) ? $dp[$i - 1][$j - 1] + 1 : 1;
if ($dp[$i][$j] > $length) {
$length = $dp[$i][$j];
$maxEnd = $j;
}
} else {
$dp[$i][$j] = 0;
}
}
}
echo mb_substr($strTwo, $maxEnd - $length + 1, $length);
}
测试用例:
123ABCD4567 ABE12345D6
输出:
123
四、最长公共子序列
这个问题和上一个很相似,唯一的不同在于,子序列可以不要求是连续的。因此,我们可以得到如下递推公式:
d p [ i ] [ j ] { 0 , i f i o r j > 0 d p [ i − 1 ] [ j − 1 ] + 1 , i f i , j > 0 a n d x i = y i m a x ( d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) , i f i , j > 0 a n d x i ! = y i dp[i][j]\left\{ \begin{array}{c} 0, if i or j >0\\ dp[i - 1][j - 1] + 1, if i, j >0 and xi = yi\\ max(dp[i][j - 1], dp[i - 1][j]), if i, j >0 and xi != yi \end{array}\right. dp[i][j]⎩⎨⎧0, if i or j>0dp[i−1][j−1]+1, if i,j>0 and xi=yimax(dp[i][j−1],dp[i−1][j]),if i,j>0 and xi!=yi
function findMaxLcs($strOne, $strTwo) {
$arrOne = array_fill(0, strlen($strOne) + 1, array_fill(0, strlen($strTwo) + 1, 0));
$arrTwo = LcsLength($strOne, $strTwo, $arrOne);
echo $arrTwo[strlen($strOne)][strlen($strTwo)] . "\n";
Lcs(strlen($strOne), strlen($strTwo), $strOne, $arrOne);
}
/**
* $arrTwo[i][j]存储Xi和Yj的最长公共子序列的长度
* $arrOne[i][j]记录$arrTwo[i][j]的值是由哪一个子问题的解得到的,在构造最长公共子序列时要用到
* @param $strOne
* @param $strTwo
* @param $arrOne
* @return array
*/
function LcsLength($strOne, $strTwo, &$arrOne) {
$arrTwo = array_fill(0, strlen($strOne) + 1, array_fill(0, strlen($strTwo) + 1, 0));
for ($i = 1; $i <= strlen($strOne); $i++) {
for ($j = 1; $j <= strlen($strTwo); $j++) {
if ($strOne[$i - 1] == $strTwo[$j - 1]) {
$arrTwo[$i][$j] = $arrTwo[$i - 1][$j - 1] + 1;
$arrOne[$i][$j] = 1;
} else {
if ($arrTwo[$i - 1][$j] >= $arrTwo[$i][$j - 1]) {
$arrTwo[$i][$j] = $arrTwo[$i - 1][$j];
$arrOne[$i][$j] = 2;
} else {
$arrTwo[$i][$j] = $arrTwo[$i][$j - 1];
$arrOne[$i][$j] = 3;
}
}
}
}
return $arrTwo;
}
function Lcs($i, $j, $strOne, $arrOne) {
if ($i == 0 || $j == 0) return ;
//判断$arrOne进入不同的分支
if ($arrOne[$i][$j] == 1) {
Lcs($i - 1, $j - 1, $strOne, $arrOne);
echo $strOne[$i - 1];
} else {
($arrOne[$i][$j] == 2) ? Lcs($i - 1, $j, $strOne, $arrOne) : Lcs($i, $j - 1, $strOne, $arrOne);
}
}
测试用例:
ABCBDA, BDCABA
输出:
4
BCBA