这段时间一直在研究算法题,产生了浓厚的兴趣,身边很多程序员不重视算法,可能感觉日常写代码用不到,实际上算法无处不在,百度搜索的结果,抖音推荐的视频,微博展示的话题。。。这个时代数据量达到了前所未有的程度,处理的信息量更是呈指数级的增长,越来越多的挑战需要靠算法来解决。
对于任何一个项目,正确地运行和正确且高效的运行是两个概念,我写了很多ERP财务统计相关模块,里面有大量表单需要计算与整合,同时和大区、地区、销售相关联,各种分组和计算方式错综复杂,这个时候我才意识到算法的意义。同样一个功能类,不同算法实现的时间和占用的内存是不一样的,实际上在 Facebook Google 这样的大企业,每天要处理大量计算,光靠服务器扩容肯定是不够的,算法上的效率提升对成本是巨大的优势。
下面是一些常见算法题和我自己的思路。
- 给定一个整数数组 nums 和一个目标值 target,在该数组中找出 和 为目标值的那 两个 整数,并返回他们的 数组下标。可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
思路
有人可能想到嵌套遍历,就和冒泡排序一样,但是效率很低,完全没有优化。试着这样想,和是固定的,每个value只有一种对应的value可以匹配,在第一次遍历把它们的key记录下来,第二次遍历去找当前key是否定义过,如果定义过且不是本身(value != 和 / 2),那么数组里存在另一个相对value。
我写两个循环是为了理解方便,实际上可以合并。
/**
1. @param Integer[] $nums
2. @param Integer $target
3. @return Integer[]
*/
function twoSum($nums, $target)
{
$match = [];
$list = [];
foreach($nums as $k => $v)
{
if(!isset($match[$target - $v]))
{
$match[ $target - $v] = $k;
}
}
foreach($nums as $k => $v)
{
if(isset($match[$v]) && $match[$v]!=$k)
{
$list[] = [$k,$match[$v]];
}
}
return $list;
}
- 给出一个 32 位的有符号整数,将这个整数中每位上的数字进行 反转。
假设我们的环境只能存储得下 32 位的有符号整数,如果反转后 整数溢出 那么就返回 0。
示例 1: 输入: 123 输出: 321
示例 2: 输入: -123 输出: -321
示例 3: 输入: 9000 输出: 0
思路1
首先提取符号,再将字符串转换为数组,遍历数组的前半部,将每个key和对应的反转key替换。
/**
* @param Integer $x
* @return Integer
*/
function reverse($x)
{
$sign = '';
if($x < 0)
{
$x = abs($x);
$sign = '-';
}
$list = str_split($x);
$len = count($list);
for($i = 0; $i < floor($len/2); $i++)
{
$temp = $list[$len - $i - 1];
$list[$len - $i - 1] = $list[$i];
$list[$i] = $temp;
}
$data = ltrim(implode($list), 0);
if($data > pow(2,31) - 1 || $data == '')
{
return 0;
}
return $sign.$data;
}
思路2
将字符串对10取模,这样就能依次得到最后一位,每次把末位乘10再拼合上一位,同时把原数除10去尾,就能反转字符串。中间的判断条件是为了当反转的字符串在下一个循环会溢出时跳出返回0,使用do-while这样只有一位数字也会执行一次而不会返回0,这种写法在其他语言用(x != 0)判断,但是PHP不行,所以用(abs($x) > 1)以执行到个位。
function reverse($x)
{
$max = pow(2,31) - 1;
$min = -1 * pow(2,31);
$rev = 0;
do{
$pop = $x % 10;
if(($rev > $max / 10) || ($rev == $max / 10 && $pop > $max % 10))
{
$rev = 0;
break;
}else if(($rev < $min / 10) || ($rev == $min / 10 && $pop < $min % 10)){
$rev = 0;
break;
}
$rev = $pop + $rev * 10;
$x = $x / 10;
}while(abs($x) >= 1);
return $rev;
}
- 给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水,返回最大值。
说明:你不能倾斜容器,且 n 的值至少为 2。
思路1
嵌套循环不解释了吧,把每种情况的面积都统计到数组,当$height足够大会超时。
/**
* @param Integer[] $height
* @return Integer
*/
function maxArea($height)
{
$len = count($height);
if($len <= 1){ return -1;}
$res = 0;
for($i = 0; $i < $len; $i++)
{
for($j = $i + 1; $j < $len; $j++)
{
$res = max($res, ($j - $i) * min($height[$i],$height[$j]));
}
}
return $res;
}
思路2
设定两个指针$i $j,分别从两边往中间走,左右两个边界移动矮的那个柱子肯定比高的那个损失的面积小,这样统计最大面积只需要循环一遍。
function maxArea($height)
{
$len = count($height);
if($len <= 1){ return -1;}
$i = 0;
$j = $len - 1;
$res = 0;
while($i < $j)
{
$res = max($res, ($j - $i) * min($height[$i],$height[$j]));
if($height[$i] < $height[$j])
{
$i++;
}else
{
$j--;
}
}
return $res;
}
题目来源:力扣(LeetCode)