Skip to content

最大容量问题

image.png

面对这个问题,我们最简单的方式就是做暴力,计算每两个柱子之间的容量和,我们可以直接用暴力破解。

tsx
function maxCapacity(ht: number[]): number {
  const n = ht.length;
  
  let max = 0;
  
  for (let i = 0; i < n - 1; i++) {
    for(let j = i + 1; j < n; j++) {
      const val = (j - i) * Math.min(ht[i], ht[j]);
      
      if(val > max) {
        max = val;
      }
    }
  }
  
  return max;
}

上方这里,我们可以看到时间复杂度为 O(n^2), 空间复杂度为 O(1)

但其实我们这里还可以使用贪心算法。

贪心策略确认

image.png

在过程中,我们一定会存在长板和短板。这个时候其实我们有两种选择,移动长板或移动短板

  • 移动长板:移动过后的高度一定不会大于之前的高度,并且宽度一定是缩小的。
  • 移动短板:高度不一定,宽度不一定,才有可能变大。

所以我们的策略是移动短板,去记录下最大值。

代码实现

代码循环最多 𝑛 轮,因此时间复杂度为 𝑂(𝑛) 。

变量 𝑖、𝑗、𝑟𝑒𝑠 使用常数大小的额外空间,因此空间复杂度为 𝑂(1) 。

tsx
function maxCapacity(ht: number[]): number {
  const n = ht.length;
  let i = 0, j = n - 1;
  let max = 0;
  
  
  while(i < j) {
    const cap = Math.min(ht[i], ht[j]) * (j - i);
    if(cap > max) {
      max = cap;
    }
    
    if(ht[j] < ht[i]) {
      j--;
    } else {
      i++;
    }
  }
  
  return max;
}

正确性证明

之所以贪心比穷举更快,是因为每轮的贪心选择都会“跳过”一些状态。

比如在状态 𝑐𝑎𝑝[𝑖,𝑗] 下,𝑖为短板、𝑗为长板。若贪心地将短板𝑖向内移动一格,会导致图 15-12 所示的状态被“跳过”。

这意味着之后无法验证这些状态的容量大小

但其实我们已经推论了这些被跳过的状态实际上就是将长板 𝑗 向内移动的所有状态。前面我们已经证明内移长板一定会导致容量变小。也就是说,被跳过的状态都不可能是最优解,跳过它们不会导致错过最优解

以上分析说明,移动短板的操作是“安全”的,贪心策略是有效的。

参考

https://www.hello-algo.com/chapter_greedy/max_capacity_problem/#3

Released under the MIT License.