leetCode11. 盛最多水的容器——终于让我有了算法的思维

47 阅读3分钟

前面在刷leetCode时,更多的还是用以前的思维方式去解决问题。当然了,我也不知道以前的的思维方式叫什么名字,反正不好,让我写出一些糟糕的代码。而就是这个题目,在我的不断思考下,竟有了双指针的影子。

题目简介

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明: 你不能倾斜容器。

WX20240427-104637@2x.png

我的第一个回答

思路: 从左边第一个起,每一个柱子都和右边的其他柱子计算面积 ,最后找到最大的。}

1号和2号计算面积
高度:由于1号和2号的高度最小为1,高度为1\color{orange}{由于1号和2号的高度最小为1,高度为}\color{red}{1}
宽度:2号的索引1号的索引=10=1\color{orange}{2号的索引-1号的索引=1-0=}\color{red}{1}
面积:高度宽度=11=1\color{orange}{高度 * 宽度 = 1 * 1 = }\color{red}{1}

1号和2号对比.png

1号和3号计算面积
高度:由于1号和3号的高度最小为1,所以高度为1\color{orange}{由于1号和3号的高度最小为1,所以高度为}\color{red}{1}
宽度:3号的索引1号的索引=20=2\color{orange}{3号的索引-1号的索引=2-0=}\color{red}{2}
面积:高度宽度=12=2\color{orange}{高度 * 宽度 = 1 * 2 = }\color{red}{2}

1号和3号对比.png

... 省略中间

1号和9号计算面积
高度:由于1号和9号的高度最小为1,所以高度为1\color{orange}{由于1号和9号的高度最小为1,所以高度为}\color{red}{1}
宽度:9号的索引1号的索引=80=8\color{orange}{9号的索引-1号的索引=8-0=}\color{red}{8}
面积:高度宽度=18=8\color{orange}{高度 * 宽度 = 1 * 8 = }\color{red}{8}

1号和9号面积.png

2号和3号计算面积(由于2号和1号计算过,便不再计算)
高度:由于2号和3号的高度最小为6,所以高度为6\color{orange}{由于2号和3号的高度最小为6,所以高度为}\color{red}{6}
宽度:3号的索引2号的索引=21=1\color{orange}{3号的索引-2号的索引=2-1=}\color{red}{1}
面积:高度宽度=61=6\color{orange}{高度 * 宽度 = 6 * 1 = }\color{red}{6} 2号和3号面积.png

2号和4号计算面积
高度:由于2号和4号的高度最小为2,所以高度为2\color{orange}{由于2号和4号的高度最小为2,所以高度为}\color{red}{2}
宽度:4号的索引2号的索引=31=2\color{orange}{4号的索引-2号的索引=3-1=}\color{red}{2}
面积:高度宽度=22=4\color{orange}{高度 * 宽度 = 2 * 2 = }\color{red}{4}

2号和4号面积.png

2号和9号计算面积
高度:由于2号和9号的高度最小为7,所以高度为7\color{orange}{由于2号和9号的高度最小为7,所以高度为}\color{red}{7}
宽度:9号的索引2号的索引=81=7\color{orange}{9号的索引-2号的索引=8-1=}\color{red}{7}
面积:高度宽度=77=49\color{orange}{高度 * 宽度 = 7 * 7 = }\color{red}{49}

2号和9号对比.png

依次这样下去,使用一个变量记录面积最大值,即可。

时间复杂度: O(n2)\mathcal{O(n^2)}

function maxArea(height: number[]): number {
  interface ElementInterface {
    value: number;
    idx: number;
  }
  //max用于保存最大面积
  let max = 0;
  for (let i = 0; i < height.length; i++) {
    for (let j = i + 1; j < height.length; j++) {
      const left = {
          value: height[i],
          idx: i,
        },
        right = {
          value: height[j],
          idx: j,
        };
        
        //Math.max()  输入若干个数字,返回最大的
      max = Math.max(getArea(left, right), max);
    }
  }

//获取面积
  function getArea(left: ElementInterface, right: ElementInterface): number {
    const width = right.idx - left.idx;
    const height = left.value <= right.value ? left.value : right.value;
    return width * height;
  }
  return max;
}

提交到leetCode,说我时间用的太多了,好吧,我一看数据,数组里有7万多个数据,我这双层循环,执行次数高达上亿次。这确实不行}。

我的第二个回答

我重新思考了下,是否所有的计算都是有意义的,如果提前就能预知某些面积就是小于其他的面积,那就可以不用计算了。


我们可以再看下这张图,图中的2号和4号计算面积。由于高度为两个柱子最小高度决定\color{orange}{最小高度决定},所以高度就 = 4号的高度。而宽度是索引的差距。

2号和4号面积.png

这时候,当我们开始用3号依次和后面的计算面积时,其结果必然小于2号和后面计算的面积。因为3号的高度低于2号,而索引距离右边的柱子要比2号近1个,所以宽度也比2号小1。

之后的原理都如此,如果右边的柱子没有左边的高,则该柱子和右边其他柱子所形成的面积必然小于左侧(相同高度或更高)的柱子与右侧其他柱子形成的面积。

3号和4号.png

function maxArea(height: number[]): number {
  interface ElementInterface {
    value: number;
    idx: number;
  }
  let max = 0;    //保存着最大面积(存水量)
  let maxHeight = 0;  //保存着最高柱子的高度(值)
  for (let i = 0; i < height.length; i++) {
  //,如果后面的高度小于前面的高度,则没有必要计算,因为它的宽度没有前面的大
    if (height[i] <= maxHeight) {
      continue;
    }
    maxHeight = height[i];
    for (let j = i + 1; j < height.length; j++) {
      const left = {
          value: height[i],
          idx: i,
        },
        right = {
          value: height[j],
          idx: j,
        };
      max = Math.max(getArea(left, right), max);
    }
  }

  function getArea(left: ElementInterface, right: ElementInterface): number {
    const width = right.idx - left.idx;
    const height = left.value <= right.value ? left.value : right.value;
    return width * height;
  }
  return max;
}

最终回答——双指针

我们再看下最初的那张图,我们先创建两个指针,一个指向左侧柱子索引,一个指向右侧柱子索引\color{orange}{一个指向左侧柱子索引,一个指向右侧柱子索引}

2号和9号对比.png

当计算完2号和9号的面积后,我们将右侧指针向内移动。因为高度为最小高度决定,所以如果移动左侧的,其面积必然会减少。移动后如下。

2号和8号的面积.png

这时候,2号和8号的面积小于之前的,继续移动右侧指针。

2号和7号.png

之后,一直移动两指针中高度小的那个,直到相遇。

function maxArea(height: number[]): number {
  let max = 0; //最大面积
  let leftIdx = 0; //左指针
  let rightIdx = height.length - 1; //右指针

  while (leftIdx < rightIdx) {
    const w = rightIdx - leftIdx; //宽
    const h = Math.min(height[leftIdx], height[rightIdx]); //高
    const area = w * h;
    max = Math.max(max, area);

//移动指针
    height[leftIdx] > height[rightIdx] ? rightIdx-- : leftIdx++;
  }

  return max;
}