题目分析

题目链接:https://leetcode.com/problems...

一开始,我尝试通过枚举所有左右边界,来依次计算夹在其中的最高矩形的面积,但是很快发现这种方式非常低效。由于左右边界没有提供关于高度的信息,每次枚举出一对左右边界以后,还要遍历两个边界之间的所有条形(O(n)),才能找出矩形可以达到的最大高度。并且,所有左右边界的组合达到n^2量级。总的时间复杂度将达到O(n^3)

题解1:维护信息树

按照矩形中的瓶颈点位置来枚举,比如,下图中4就是瓶颈点,组成矩形的所有柱体中它是最低的,它是矩形的高度的瓶颈所在。

枚举出瓶颈点的位置以后,向左、向右扩展边界,得到一个尽可能大的矩形。

这里还可以使用一个技巧,为了更快地找到左右边界,我们可以按照高度从小到大进行枚举。这样,当要计算高度为h的矩形的边界时,所有小于h的位置都在此之前枚举过了。
为了保存已经枚举过的柱体的面积,可以维护一颗二叉搜索树(C++中的Map一般是用红黑树实现的),以index为键,可以在O(logn)的时间内找到想要的index,并且插入时间可以缩短至O(1)。

class Solution {
 public:
  //  按照高度从小到大排序的比较函数
  static bool cmp_height(vector<int>& v1, vector<int>& v2) {
    return v1[1] < v2[1];
  }
  //  按照下标从小到大排序的比较函数
  struct cmp_index {
    bool operator()(const int& v1, const int& v2) const { return v1 < v2; }
  };
  int largestRectangleArea(vector<int>& heights) {
    int max_area = 0;
    vector<vector<int>> sorting;  // (index, height, count)
    int last = -1;
    for (int i = 0; i < heights.size(); ++i) {
      // 合并相邻、相同高度的柱体
      if (last != heights[i]) {
        sorting.push_back(vector<int>{i, heights[i], 1});
      } else {
        sorting.back()[2]++;
      }
    }
    // 按照柱体高度从小到大排序
    sort(sorting.begin(), sorting.end(), cmp_height);
    // m用来存储已经枚举出来的瓶颈点,按照下标从左到右排序
    map<int, vector<int>&, cmp_index> m;
    // 按照柱体高度从小到大枚举
    for (int i = 0; i < sorting.size(); ++i) {
      // m中已有的柱体都是高度小于sorting[i]的
      // 在这些柱体中找出index比它小一些和它大一些的,作为矩形的左右边界
      auto it = m.lower_bound(sorting[i][0]);   // O(logn)
      int left_boundary = -1, right_boundary = heights.size();
      if (it != m.end()) {
        right_boundary = (it->second)[0];
      }
      if (it != m.begin()) {
        auto it2 = it;
        it2--;
        left_boundary = (it2->second)[0] + (it2->second)[2] - 1;
      }
      int area = (right_boundary - left_boundary - 1) * sorting[i][1];
      if (area > max_area) max_area = area;
      m.insert(it, pair<int, vector<int>&>(sorting[i][0], sorting[i])); // O(1)
    }
    return max_area;
  }
};

该实现的时间复杂度为O(nlogn)。在leetcode的排名中仍属于末端,这是因为通过维护信息而不是树,能使时间复杂度降低至O(n)。

题解2:维护信息栈

class Solution {
 public:
  int largestRectangleArea(vector<int> &heights)
    {
        int max_res = 0;
        int n = heights.size();
        // 在i左边第一个比heights[i]小的元素
        vector<int> left_smaller(n, -1);
        // 在i右边第一个比heights[i]小的元素
        vector<int> right_smaller(n, n);
        stack<int> sta;
        for (int i = 0; i < n; ++i)
        {
            while (!sta.empty() && heights[sta.top()] >= heights[i])
            {
                sta.pop();
            }
            if (!sta.empty())
                left_smaller[i] = sta.top();
            sta.push(i);
        }

        stack<int> sta2;
        for (int i = n - 1; i >= 0; --i)
        {
            while (!sta2.empty() && heights[sta2.top()] >= heights[i])
            {
                sta2.pop();
            }
            if (!sta2.empty())
                right_smaller[i] = sta2.top();
            sta2.push(i);
        }

        for (int i = 0; i < n; ++i)
        {
            // 枚举每个i作为瓶颈点
            // 以i为瓶颈点,找到最大矩形的左右边界
            int l = left_smaller[i], r = right_smaller[i];
            int size = (r - l - 1) * heights[i];
            max_res = max(max_res, size);
        }
        return max_res;
    }
};

时间复杂度为O(n),虽然有嵌套循环,但是每个柱体只会被push/pop一次。


csRyan
1.1k 声望198 粉丝

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart doesn't find a perfect rhyme with the head, then your passion means nothing.