这几天做的算法题,有难度,欢迎指正或者提出更好的解决方案。
- 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且 不重复 的三元组。注意:答案中不可以包含重复的三元组。
示例: 给定数组 nums = [-1, 0, 1, 2, -1, -4]
满足要求的三元组集合为:[ [-1, 0, 1],[-1, -1, 2]]
思路
第二遍算法笔记里,我提到一题是找出和为指定数的组合,但是这一题是三个数,不好直接用。暴力拆解就不写了,三个嵌套循环,时间复杂度高达O(n^3)。这里提一种新思路:
三个数和为0,则至少有一个是负数,去和两个正数中和,利用这一点,先对数组从小到大排序。遍历当前值,如果大于0 就不用看了,三个正数不可能和为0 ,如果和上次重复则跳过此次循环。
定义两个指针,从两边往中间走,如果和小于0,就把左指针+1(小的数增大),反之把右指针-1,
(大的数减小),如果得到和为0,就存起来,同时调整两边指针,因为只改一边肯定不平衡,注意要去掉重复的组合。
/**
1. @param Integer[] $nums
2. @return Integer[][]
*/
function threeSum($nums)
{
$len = count($nums);
if($len < 3){ return [];}
sort($nums); //排序
$list = [];
for($i = 0; $i < $len; $i ++)
{
if($nums[$i] > 0){ break;}
if($i > 0 && $nums[$i] == $nums[$i - 1]){ continue; } //去重
$left = $i + 1;
$right = $len - 1;
while($left < $right)
{
$sum = $nums[$i] + $nums[$left] + $nums[$right];
if($sum == 0)
{
$list[] = [$nums[$i], $nums[$left], $nums[$right]];
while($left < $right && $nums[$left + 1] == $nums[$left])
{
$left ++; //去重
}
while($left < $right && $nums[$right - 1] == $nums[$right])
{
$right -- ; //去重
}
$left ++;
$right --;
}else if($sum < 0)
{
$left ++;
}else
{
$right --;
}
}
}
return $list;
}
- 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例: 输入: [0,1,0,2,1,0,1,3,2,1,2,1] 输出: 6
思路1
分别计算每一列的雨水,因为单列的上限取决于左右两侧最大值里小的那个,对每一列分别计算左右的最大值,去小的减去当前列的高度再累加。
/**
* @param Integer[] $height
* @return Integer
*/
function trap($height)
{
$len = count($height);
$sum = 0;
for($i = 1; $i < $len; $i++)
{
$left_max = 0;
$right_max = 0;
for($j = 0; $j < $i; $j++)
{
$left_max = max($left_max, $height[$j]);
}
for($k = $i; $k < $len; $k++)
{
$right_max = max($right_max, $height[$k]);
}
$max = min($left_max, $right_max);
if($max > $height[$i])
{
$sum += $max - $height[$i];
}
}
return $sum;
}
思路2
上个算法每次都要计算两侧的最大值,对它进行优化,每右移一列, 左侧最大值只是多了一个当前列的比较,相当于它左边那列的$left_max和它左边那列两者的最大值,右侧同理,先遍历出两侧最大值放到数组里,这样就把嵌套循环变成了并列循环。
function trap($height)
{
$len = count($height);
$sum = 0;
$left_max = [];
$right_max = [];
$left_max[0] = $height[0];
for($i = 1; $i < $len; $i++)
{
$left_max[$i] = max($left_max[$i - 1], $height[$i - 1]);
}
$right_max[$len - 1] = $height[$len - 1];
for($i = $len - 2; $i >=0; $i--)
{
$right_max[$i] = max($right_max[$i + 1], $height[$i + 1]);
}
for($i = 1; $i < $len; $i++)
{
$max = min($left_max[$i], $right_max[$i]);
if($max > $height[$i])
{
$sum += $max - $height[$i];
}
}
return $sum;
}
思路3
上个算法至少要两个循环。考虑到雨水的积蓄量取决于两边矮的那个,定义两个指针,从两侧往中间遍历。雨水量决定于矮的那边,所以高的那边不要动,如果左侧矮,就移动左侧指针,同时和左侧最大值比较,比它大就作为新的最大值,比它低就累加积水量,这样一次循环就够了。
function trap($height)
{
$left = 0;
$right = count($height) - 1;
$left_max = 0;
$right_max = 0;
$sum = 0;
while($left < $right)
{
if($height[$left] < $height[$right])
{
if($height[$left] >= $left_max)
{
$left_max = $height[$left];
}
else
{
$sum += $left_max - $height[$left];
}
$left ++;
}else
{
if($height[$right] >= $right_max)
{
$right_max = $height[$right];
}
else
{
$sum += $right_max - $height[$right];
}
$right --;
}
}
return $sum;
}
思路4
这个理解有些难,我参考了一些Java的写法,但是PHP没有Stack类,就用数组代替。从左往右遍历,如果当前高度比前一个小就入栈,否则就出栈并计算,雨水量 = 距离 * 小的高度。
function trap($height)
{
$i = 0;
$stack = [];
$sum = 0;
while($i < count($height))
{
while(count($stack) > 0 && $height[$i] > $height[end($stack)])
{
$h = $height[end($stack)];
array_pop($stack);
if(count($stack) == 0)
{
break;
}
$distance = $i - end($stack) - 1;
$min = min($height[end($stack)], $height[$i]);
$sum += $distance * ($min - $h);
}
array_push($stack, $i);
$i++;
}
return $sum;
}
以上四种算法的时间和内存对比。
- 给定一个字符串,请你找出其中 不含有重复字符 的 最长子串 的长度。
示例 1: 输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2: 输入: “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3: 输入: “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
思路
之前看过一个题,一个很长无序字符串,取特定字符串,如 ‘abc’,在最短时间匹配位置。一般人肯定是从左到右,按位匹配,这样比较费时。有大神研究出KMP算法,利用最长相同的前缀和后缀,移动使它们重叠,跳过式匹配,可以自行百度。
类比那个思想,把字符放到匹配字符串里(当然也可以用数组,array_pop等方法),如果没有这个字符,不重复的字符串就增长了,如果已经存在,就从那一位往后截取,如‘abcd’又碰到一个‘a’,就截取‘bcda’,得到当前最长不重复的字符串,保存并比较。
/**
* @param String $s
* @return Integer
*/
function lengthOfLongestSubstring($s)
{
$res = ''; //匹配字符串
$len = 0;
for($i = 0; $i < strlen($s); $i ++)
{
$pos = strpos($res, $s[$i]); //检查是否存在
$res .= $s[$i];
if($pos !== false)
{
$res = substr($res, $pos + 1); //截取
}
$len = strlen($res) > $len ? strlen($res) : $len;
}
return $len;
}
PHP提供了很多数组,字符串的处理函数,缺少了基本的诸如Stack HashSet等类,这也是学PHP的人往往不重视算法的原因。但我觉得,写代码不能吃现成,还要知道原理是怎么实现的。
题目来源:力扣(LeetCode)