一文看懂《子数组的最大乘积问题》

这道题出自《编程之美》第二章第 13 小节。

问题描述:给定一个长度为 N 的整数数组,只允许乘法,不能用除法。计算任意 N - 1 个数的组合中乘积最大的一组,并写出算法的时间复杂度。

这道题目和另外一个《连续数组的最大乘积》有点像,那道题我们可以通过记录全局最大值和负数最小值来完成。这道题则稍微有点不同,我们来看一下。

暴力法

最直观的解法是将全部组合找出来,一共是 N 个组合,分别计算他们的乘积, 然后计算最大值,一共有 N 个 N-1 个数字的组合,因此时间复杂度是O(N^2)

参考 JavaScript 代码:

function LSP(list) {
  let ret = 1;
  let max = -Number.MAX_VALUE;
  for (let i = 0; i < list.length; i++) {
    for (let j = 0; j < list.length; j++) {
      if (i !== j) ret *= list[j];
    }
    max = Math.max(max, ret);
    ret = 1;
  }
  return max;
}

这种方法略显暴力,显然不是一种好的方法,不过作为一种启发, 在面试中先提供一种普通的减法,然后提供思路慢慢优化,会让面试官看到你的 闪光点。

上面的解法产生了大量的重复计算,我们是否可以将重复计算存起来,以减少这种重复计算呢?我们来看下下面的解法。

空间换时间

我们计算 N-1 个元素的乘积,也就是说有一个元素被排除在外。我们假设被排除的 元素索引为 i(0 <= i < N, 且 i 为整数)。

我们用两个数组 l 和 r 分别记录从前和从后的子数组乘积。

我们用 l[i]表示原数组中从 0 开始到 i - 1(包括 i - 1)的乘积, r[i]表示原数组中从 i(包括 i)到 N - 1(包括 N - 1)的乘积。

640?wx_fmt=png

于是我们只需要一次循环l[i] * r[i + 1]计算出 N - 1 个数字的乘积。

由于只需要 从有到尾和从尾部到头扫描数组两次即可得到数组l和r,进而可以在线性的时间复杂度获取到所有的乘积,然后在这个过程中我们就可以取出最大值,因此这样做的时间复杂度为O(N)

参考 JavaScript 代码:

function LSP(list) {
  let ret = 1;
  let max = -Number.MAX_VALUE;
  const l = [];
  const r = [];
  l[0] = 1;
  r[0] = 1; // useless
  r[list.length] = 1;

  for (let i = 1; i &lt; list.length; i++) {
    l[i] = l[i - 1] * list[i - 1];
  }
  for (let i = list.length - 1; i &gt;= 1; i--) {
    r[i] = r[i + 1] * list[i];
  }
  for (let i = 0; i &lt; list.length; i++) {
    ret *= l[i] * r[i + 1];
    max = Math.max(max, ret);
    ret = 1;
  }

  return max;
}

这种时间复杂度已经很优秀了,接下来我们通过数学分析再来看一下这道题。

数学分析

实际上,总体的乘积一共只有三种情况:正,负和 0。

  • 如果是 0,我们进一步找有没有别的 0,有的话返回 0, 没有的话我们就算下除了这个 0 之外所有的乘积,然后取它和 0 的较大值即可。(然而这两个逻辑可以合并)

  • 如果是正,那么删除最小的正数即可

  • 如果是负数,则说明一定至少有一个负数存在,我们只要知道绝对值最小的负数删除即可

640?wx_fmt=png

通过上面的分析我们只要遍历一次找出这几个核心遍历,然后再来一次遍历算出乘积(乘积忽略前面计算出的需要忽略的索引)即可

参考 JavaScript 代码:

function LSP(list) {
  let ret = 1;
  let smallestPositive = Number.MAX_VALUE;
  let smallestPositiveI = -1;
  let largestNegative = -Number.MAX_VALUE;
  let largestNegativeI = -1;
  let firstZeroI = -1;

  let missingI = -1;

  let productAll = 1;

  for (let i = 0; i &lt; list.length; i++) {
    productAll *= list[i];
    if (list[i] === 0 &amp;&amp; firstZeroI === -1) {
      firstZeroI = i;
    }
    if (list[i] &gt; 0 &amp;&amp; list[i] &lt; smallestPositive) {
      smallestPositive = list[i];
      smallestPositiveI = i;
    }
    if (list[i] &lt; 0 &amp;&amp; list[i] &gt; largestNegative) {
      largestNegative = list[i];
      largestNegativeI = i;
    }
  }
  if (productAll &gt; 0) {
    missingI = smallestPositiveI;
  } else if (productAll &lt; 0) {
    missingI = largestNegativeI;
  } else {
    missingI = firstZeroI;
  }

  for (let i = 0; i &lt; list.length; i++) {
    if (i !== missingI) ret *= list[i];
  }

  return firstZeroI === -1 ? ret : Math.max(0, ret);
}

这个解法的时间复杂度同样也是O(N),但是空间复杂度降低到了O(1)

上面的解法我们判断正负直接粗暴的将所有数字乘了起来,这其实有溢出的风险, 其实我们可以使用别的方法来计算正负,聪明的你肯定已经想到了。

我们可以通过统计正数,负数和0的个数来判断乘积的正负。

总结

子数组乘积问题有很多变种问题,今天我们讲的就是其中一中类型, 我们先通过朴素的解法,然后一步步分析问题的本质,通过空间换时间的解法 进一步减少了时间复杂度。最后我们通过数学分析,进行分类讨论,通过常数的空间复杂度和 线性的时间复杂度解决了问题。

相信大家在面试中如果通过上面的思考过程,一步一步,循循渐进,不仅可以逐步减少自己的紧张, 还能让面试官看到你的思考过程,祝大家找到自己理想的工作。本文完~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 汽车电控制单元(ECU)是现代汽车中必不可少的电设备之一,它负责管理发动机、变速器、车辆稳定性控制系统等多个重要部件。而ECU中的bootloader则是支持ECU软件更新的重要组成部分,它具有一个特殊的启动程序,用于将新的软件加载到ECU中。 ECU bootloader的工作原理主要是将新的软件程序通过CAN总线或其他通信方式,发送到ECU中进行更新。具体步骤如下:首先,ECU bootloader会检查当前系统中的软件版本是否需要更新。如果需要更新,则它会在系统启动时自动进入bootloader模式,并等待接收新的软件程序。接下来,ECU bootloader将通过通信接口接收新的程序,并将其存储在特定的flash存储器中。然后,ECU bootloader会进行程序校验和解压缩等动作,以确保接收到的程序没有任何问题。最后,ECU bootloader将新的程序加载到内存中,并将控制权交给新程序,完成软件更新过程。 在进行ECU bootloader的开发时,需要注意以下几个要点:首先是要选择适当的存储器,并确定软件程序的大小。其次,需要实现通信接口,确定通信协议和数据传输方式。然后,需要设计和实现程序校验和解压缩等安全和稳定性相关的功能。最后,还需要进行一系列的测试和验证,以确保软件更新功能的正确性和可靠性。 总之,ECU bootloader是现代汽车中非常重要的一个电组件,它支持汽车ECU软件更新,保证了车辆的正常运行和安全。在进行ECU bootloader开发时,需要充分考虑软件大小、通信接口、安全性等方面的因素,确保实现出稳定可靠的功能。 ### 回答2: 汽车电ECU Bootloader是一种可以更新车辆控制器软件的重要工具,本文将介绍汽车电ECU Bootloader的工作原理和开发要点。 汽车电ECU Bootloader的工作原理主要是通过分区管理技术将存储器划分为Bootloader和应用程序两个区域,Bootloader负责车辆控制器的引导和固件更新,而应用程序则实现车辆控制器的各项功能。当车辆控制器出现故障或升级需要时,Bootloader通过CAN总线接受来自外部设备的控制命令,对存储器中的数据进行读写操作,完成软件更新等任务。 开发汽车电ECU Bootloader需要考虑以下几个要点: 1.安全性:保证Bootloader在更新过程中不会遭到外部攻击或出现故障,同时需要遵守汽车规范和安全标准,确保车辆控制器的安全性和稳定性。 2.可靠性:Bootloader需要对存储器中的数据进行正确的读写操作,避免数据丢失或损坏等可能出现的问题,同时需要实现固件校验技术,确保固件的完整性和正确性。 3.灵活性:Bootloader需要支持多种协议和接口,以适应不同车辆控制器的要求,同时需要支持多种固件格式,以避免与其他系统不兼容的问题。 4.可测试性:Bootloader需要为软件开发人员提供方便的测试和调试工具,以便快速迭代和改进,同时需要支持错误日志和故障诊断技术,以帮助分析和解决问题。 综上所述,汽车电ECU Bootloader是进行车辆控制器软件更新和管理的重要工具,其开发需要考虑多个要点,包括安全性、可靠性、灵活性和可测试性等,以确保车辆控制器的安全性、稳定性和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值