无重复字符的最长子串 LeetCode刷题 - PHP
第一次刷leetcode,实在是受不了,但是好歹也撑下去了,这道题目是leetcode的第3题,通过率38%,难度中等。我有幸成为其中的38%,啊哈哈哈哈哈哈...
言归正传,首先先看看输入条件和输出结果示例:
* eg1: s = "abcabcbb"
* print: 3
*
* eg2: s = "bbbb"
* print: 1
*
* eg3: s = "pwwkew"
* print: 3
*
* eg4: s = " "
* print: 0
*
* eg5: s = "iqbtbscgdztpgfp"
* print: 8
*
* eg6: s = "wobgrovw"
* print: 6
*
* eg7: s = "tmmzuxt"
* print: 5
*
* eg8: s = "dvdf"
* print: 3
上述示例是我在提交代码时可能有的类型,每个类型都有各自的特点,一开始还看不出来,不过在慢慢写代码调试的时候才发现其中的问题,也用了很多种方法。
接下来就先拿第一个示例开刀吧:“abcabcbb”
思路一:
PHP中有可以把字符串转化成数组的内置函数str_split($string,$length),所以初始化数组是[a,b,c,a,b,c,b,b]
A.假设从字符的第一个字母开始进行foreach遍历,a-b-c可以组成一个不重复的字符串存储 temp[],但是到了c之后的a,在已组成的temp[]中是存在的,因此到了a这里,不能将a存储进temp[],并统计temp[]的数量count,此时遍历结束,应该break跳出循环,进入下一个遍历。
B.在进入下一个遍历的时候,肯定不能再从字符的第一个字母开始统计了,要从第二个字母b开始进行foreach遍历,由此,PHP中还有个内置函数叫array_shift($arr),它会将数组开头的单位移除数组,这里新的数组就是[b,c,a,b,c,b,b]
C.对新的数组,重复进行步骤A的操作,进行这种重复操作的前提,必然需要一个可以跳出循环重复的条件,这里的条件不难看出,是对组成新的数组为空的时候,强制跳出循环。
D.在对数组为空之后,整个字符串都走遍了。所有可能的最长字符长度也都有了,这时候只需要做最后的max()选出最大值。
代码如下:
function lengthOfLongestSubstring($s)
{
$len = strlen($s);
if ($len == 1 || $len == 0) return $len;
$arr = str_split($s,1); //拆分字符串转成数组
$max = [];//保存可能的最大值数组
while (!empty($arr)){
$temp = [];
foreach ($arr as $key => $item){
//如果temp不存在字母,入栈
if (!isset($temp[$item])){
$temp[$item] = 1;
}else{
//当有字母存在temp中,已有重复,统计当前temp的大小即为字符串的长度
break;
}
}
$max[] = count($temp);
array_shift($arr);//删除数组的开头,并偏移量向右
}
return max($max);
}
思路一的方法,属于暴力法,它所消耗的内存和执行时间都不是最优的。为了在进行优化,对原有的从左到右多次循环想办法改成只进行一次遍历。而根据题目的类型,可以想到,这是一种在给定的字符串或数组中找出特定长度的字符串或数组的筛查操作,因此窗口滑动算法可以完美的解决到这个问题。
窗口滑动算法的具体原理可以去查一下资料,不过在这里我可以简单说明了解一下,正如字面意思一样,窗口,具有一定长度的窗口win,滑动,对这个特定长度的窗口进行左右滑动,直到字符串或数组结束。窗口的长度是根据条件可以缩小,也可以扩大,总之不能超过整个字符长度。
思路二:
A.不用把字符串转化成数组了,直接统计字符串长度,对字符串进行for 从i=0开始循环,新建一个哈希值temp[],从第一个字母开始,如果哈希里没有字母,就把字母写进哈希里面,该字母对应的哈希值是它的下标所在的i值,在设置哈希的同时,窗口的创立显而易见,假设窗口长度max为0,字符串的初始化左指针为0,第一个字母开始统计,该窗口的形成,就是max和(当前下标i - 左指针left + 1)的最大值 1,此时窗口长度max=1.
B.以此类推,到b的时候,b不在哈希里面,窗口长度就是 前面的max=1 和 (当前下标i - 左指针left + 1)的最大值 2,此时窗口长度max = 2.
C.继续类推,到c的时候,c不在哈希里面,窗口长度变成了max=3.
D.继续类推,到a了,此时会发现,a在哈希里面,且对应的第一个a的哈希值是0,这时候,既然出现了重复的字母,窗口就不应该持续扩大,应保持max=3,但是后面还有很多没有统计的字母,所以就需要对该窗口进行向右滑动,而向右滑动的条件,就需要刚刚的左指针下标,left 和 (第一个a字母下标i + 1)的最大值就是左指针向右移动进1,此时left = 1.
E.下标left=1,窗口长度max=3,其可查的总长度即为4,是不能超过字符串的长度的,一旦超过就需要break强制退出循环。这时候的窗口就是所需的最大值。
代码如下:
function opLengthOfLongestSubstring($s)
{
$len = strlen($s);
if ($len == 1 || $len == 0) return $len;
$temp = [];
$left = $max = 0;
for ($i = 0;$i < $len;$i++){
$char = $s[$i];
if (isset($temp[$char])) {
$left = max($left,$temp[$char] + 1);
}
if ($left + $max >= $len){
break;
}
$temp[$char] = $i;
$max = max($max,$i - $left + 1);
}
return $max;
}
窗口滑动的时间复杂度是O(N)。
思路三:
思路三是我无聊随便改改的,本以为能够继续优化,结果还是差不多的消耗,因为运用了PHP的内置函数,在PHP语言中执行时间是缩短了些,可能是因为内置函数它是c语言开发的,比PHP更具有执行效率吧。
同样是直接用字符串for i=0开始循环,从i=0开始,但是在开始之前,我这里要设置两个参数,一个是存储可能的最大值数组max[],一个是对不重复出现的字符组成的新的字符串newS = ""(因为不重复字符串肯定没有重复的字符)
A.第一个字母是a,在整个字符串中查找出a的首个出现的下标位置,strpos($string,$char),将其下标设置为旧的左指针oldLeft,此时oldLeft的定义就是判断a是否在newS中,然后将第一个字母a放在另一个地方组成新的字符串newS = "a"
B.以此类推,第二个字母b,查出b首个出现的下标,oldLeft此时还是不存在,因为b不存在newS中,所以newS更新为"ab"
C.以此类推,第三个字母c,查出c首个出现的下标,oldLeft此时还是不存在,因为c不存在newS中,所以newS更新为"abc"
D.到了a,newS中存在a,此时查出来a的下标是0,strpos返回的值是true,所以,这个时候就要存储可能的max最大值,即newS的strlen长度,到了这一步,新的newS字符串就要把a前面的所有字符都剔除,不然到后面就会最大值一直保持在abc这里,造成搜索遗漏的情况。此时newS = "bc"
E.以此类推,在统计到所有可能的最大值中,用max()统计最大值。
注意:有些字符串存在全部都说不重复的字母,因此,在思路三中,新字符串可能到最后都无法找到其重复的字母下标,就无法存储最长值,所以在统计之前,还要做最后一次存储最终的新字符串长度,否则就会报null的错误
代码如下:
function opLengthOfLongestSubstring1($s)
{
$len = strlen($s);
if ($len == 1 || $len == 0) return $len;
$max = []; //
$newS = '';
for ($i = 0; $i < $len; $i++) {
$char = $s[$i];
$oldLeft = strpos($newS, $char);
if ($oldLeft !== false) {
//如果存在字符
$max[] = strlen($newS);
$newS = substr($newS, $oldLeft + 1);
}
$newS .= $char;
}
$max[] = strlen($newS); //循环结束后,最终的字串
return max($max);
}
以上三种方法各具思路,当然肯定也有其他的方法可以解决,刷leetcode是我锻炼逻辑思维的很大提升,尽管路途艰难,但是,只要坚持实践,就一定能行。
大家加油奥里给~~