Leetcode 84:柱状图中最大矩形

问题

在这里插入图片描述

分析

共有两种方法解决这个问题,先说容易想到的一种方法

分治法:

在整数序列中找到当前序列中的最小值(记其下标为mid),则整个序列的最大矩形面积可能是以下三个值中的其中之一:
1)左边(不包括mid)序列的最大矩形;
2)右边(不包括mid)序列的最大矩形;
3)以mid位置的矩形高为高,以全部序列的宽度为宽的矩形。
其中1),2)可以同样的方式递归求解。
这样就可以得出解法一(分治法一),代码贴在下面。在该解法中,每次需要遍历当前序列,以找到当前序列中最小的值。
时间复杂度:O(nlogn)(震惊!我求不出来这个值),超时。

分治法改进:

解法一中,花费了大量的时间在找到序列中最小值上;如果我们能降低找到一个序列中最小值的复杂度为O(1),则分治法的时间复杂度可以降低到O(logn)。
(wait a moment 线段树 树高logn,那么找一个区间的最小值,复杂度也应该是O(logn)才对吧……啊)
这里用到的线段树的概念。首先简单介绍线段树的概念。

线段树

线段树是一种数据结构(不然呢);它是以树结构为支撑的数组。它的每个节点记录了某个区间,如[start,end]范围内的原数组的某个性质,如该区间的最大值/最小值;该区间的和等,可根据具体问题设置节点为不同的值。该树的叶子节点即为原数组的每个元素。
下面以本题为例,介绍用线段树求某个区间的最小值的方法。
求解这个问题主要包括以下几个步骤:
1)构造线段树;
2)在线段树中找某个区间的最小值。

构造线段树:
根节点存[0,heights.length-1]区间范围内的heights数组的最小值;要想获得该节点的取值,首先需要递归的构造其左右子树,获得左右子节点的取值。则根节点取值=heights[左节点值]<heights[右节点值])?左节点值:右节点值。
记mid=low+(high-low)/2;curr记录当前节点在线段树数组中的位置。
其左子树要构造的区间为[low,mid],curr=curr2+1;其右子树要构造的区间为[mid+1,high],curr=curr2+2。

在线段树中找某个区间的最小值:
要求某个区间内的最小值,如果待求区间[left,right]恰好与当前节点对应区间[ss,se]一致,则返回当前节点的取值;
否则,在左子树中获得该区间内的最小值;在右子树中农获得该区间内的最小值,比较返回结果。

栈方法:

如果该序列是递增序列的话,那该递增序列的最大矩形,只要从后向前遍历一遍该序列,每向前一个位置,更新currArea=遍历过的长度当前位置的高度;比较保存最大的矩形即可。
如[1,2,3,4,5,6],从后向前遍历,得到的currArea分别为:1
6=6,25=10,34=12,43=12,52=10,6*1=6,则最大矩形为12。
但本题中不能这样解,原因在于,该序列非递增序列(em废话)。或者说,可能其中存在递增序列,但会被后面的一个元素破坏掉,如[2,1,5,6,2,3]中,1,2(第二个2)值,就是破坏递增序列的元素。
这里采取这种方法:使用一个栈;

  • 从前向后遍历,如果后一个元素能保持递增特性,则该高度入栈;
  • 如果后一个元素小于当前栈顶元素,则出栈,直至栈顶元素值小于后一元素值;出栈的同时,记录栈内的递增序列组成的矩形面积值,比较保存最大的面积(同开始说的递增序列中的做法);
  • 计算出栈的元素个数,再入栈相同的元素个数,取值为后一元素的高度值;
  • (数组中所有的元素在遍历完后,以递增的形式保存在了栈内,这个递增序列组成的矩形面积还没有计算)栈内元素出栈,同时记录矩形面积值(同开始的计算过程),比较保存最大矩形面积。

BTW,这个非最简洁的栈实现方法,TBH,其实是很烂的实现方法;
时间复杂度:O(n)

代码

分治法一:

	public int largestRectangleArea(int[] heights) {
		if (heights == null || heights.length == 0)
			return 0;
		int len = heights.length;
		int ans = 0;
		ans = helper(heights, 0, len - 1);
		return ans;
	}

	private int helper(int[] heights, int left, int right) {
		// TODO Auto-generated method stub
		// 返回条件
		if (left > right)
			return 0;
		if (left == right)
			return heights[left];
		int pivot = minHeight(left, right, heights);
		int leftArea = helper(heights, 0, pivot - 1);
		int rightArea = helper(heights, pivot + 1, right);
		int merge = (right - left + 1) * heights[pivot];
		// 比较三者大小,返回max
		return Math.max(leftArea, Math.max(rightArea, merge));
	}

	private int minHeight(int left, int right, int[] heights) {
		// TODO Auto-generated method stub
		int index = left;
		if (left >= right)
			return left;
		int mid = left + (right - left) / 2;
		int leftMin = minHeight(left, mid, heights);
		int rightMin = minHeight(mid + 1, right, heights);
		return heights[leftMin] < heights[rightMin] ? leftMin : rightMin;
	}

分治法(+线段树)


	public int largestRectangleArea(int[] heights) {
		// 根据heights构造segment tree
		if (heights == null || heights.length == 0)
			return 0;
		int maxSize = getSize(heights);
		int[] tree = new int[maxSize];
		construct(heights, tree);
		// 构造完成
		return helper(heights, 0, heights.length - 1, tree);
	}

	private int helper(int[] heights, int left, int right, int[] tree) {
		// TODO Auto-generated method stub
		if (left > right)
			return 0;
		if (left == right)
			return heights[left];
		int pivot = getMinValue(left, right, tree, heights);
		int leftArea = helper(heights, left, pivot - 1, tree);
		int rightArea = helper(heights, pivot + 1, right, tree);
		int merge = (right - left + 1) * heights[pivot];
		return Math.max(rightArea, Math.max(leftArea, merge));
	}

	private int getMinValue(int left, int right, int[] tree, int[] heights) {
		// 获得left——right之前的最小值
		return getMinIndexUtil(left, right, tree, 0, heights.length - 1, 0, heights);
	}

	private int getMinIndexUtil(int left, int right, int[] tree, int ss, int se, int curr, int[] heights) {
		if (left <= ss && right >= se)
			return tree[curr];
		if (left > se || right < ss)
			return -1;//
		int mid = left + (right - left) / 2;
		// 这里为什么ss与se要变:因为树往下走,下面节点包括的区间变小,所以要变ss和se
		// 为什么left和right不变:已经通过上面的两个if限制了如果left——right大于ss——se应该怎么处理,所以这里不用变
		// 一知半解
		int leftMin = getMinIndexUtil(left, right, tree, ss, ss + (se - ss) / 2, curr * 2 + 1, heights);
		int rightMin = getMinIndexUtil(left, right, tree, ss + (se - ss) / 2 + 1, se, curr * 2 + 2, heights);
		return getMin(leftMin, rightMin, heights);
	}

	private void construct(int[] heights, int[] tree) {
		constructUtil(tree, 0, heights, 0, heights.length - 1);
	}

	private int constructUtil(int[] tree, int curr, int[] heights, int left, int right) {
		// 构造线段树
		if (left == right) {
			tree[curr] = left;
			return tree[curr];
		}
		if (left > right)
			return -1;
		int mid = left + (right - left) / 2;
		int leftMinIndex = constructUtil(tree, curr * 2 + 1, heights, left, mid);
		int rightMinIndex = constructUtil(tree, curr * 2 + 2, heights, mid + 1, right);
		int min = getMin(leftMinIndex, rightMinIndex, heights);
		tree[curr] = min;
		return tree[curr];
	}

	private int getMin(int leftMinIndex, int rightMinIndex, int[] heights) {
		if (leftMinIndex == -1)
			return rightMinIndex;
		if (rightMinIndex == -1)
			return leftMinIndex;
		return heights[leftMinIndex] < heights[rightMinIndex] ? leftMinIndex : rightMinIndex;
	}

	private int getSize(int[] heights) {
		int x = (int) (Math.ceil(Math.log(heights.length) / Math.log(2)));
		int max_size = 2 * (int) Math.pow(2, x) - 1;
		return max_size;
	}

栈方法

	public int largestRectangleArea2(int[] heights) {
		if (heights == null || heights.length == 0)
			return 0;
		LinkedList<Integer> stack = new LinkedList<Integer>();
		stack.push(heights[0]);
		int area = 0;
		int currArea = 0;
		int bottom = 0;
		for (int i = 1; i < heights.length; i++) {
			if (heights[i] >= stack.peek()) {
				stack.push(heights[i]);
			} else {
				int gap = i;
				while (!stack.isEmpty() && heights[i] < stack.peek()) {
					int curr = stack.pop();
					currArea = curr * (i - gap + 1);
					if (currArea > area)
						area = currArea;
					gap--;
				}
				for (int j = 0; j < i - gap + 1; j++) {
					stack.push(heights[i]);
				}
			}
		}
		int count = 1;
		int top = stack.pop();
		area = 1 * top > area ? top : area;
		while (!stack.isEmpty()) {
			count++;
			int curr = stack.pop();
			area = count * curr > area ? count * curr : area;
		}
		return area;
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值