PHP 经典算法面试题 附解答 (三)

这几天做的算法题,有难度,欢迎指正或者提出更好的解决方案。

  1. 给你一个包含 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;        
}
  1. 给定 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. 给定一个字符串,请你找出其中 不含有重复字符最长子串 的长度。

示例 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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值