笔试题next_permutation & Largest Rectangle in Histogram

看了看去年有道的2013年10月北邮站的笔试题,第一题很简单但unicode字符的输出没实现成功(题目见http://www.cnblogs.com/dancingrain/p/3405186.html),后两道编程题都很经典,在leetcode上遇到过,但是还是记不清了,所以决定写一写,争取把思路说清楚,把方法变成自己的。

 

第二道,对于给定的正整数n,1至n个数的全排列有n!个,对于任意一个排列,编程给出它在字典序下的下一个排列。例如n=3时,所有6个全排列在字典序下为:123, 132, 213, 231, 312, 321,所以213的下一个排列是231;又如n=5时,23541的下一个排列是24135。

 

next_permutation的实现还是室友给我讲过的,自己曾经又推了一遍,看到这个题目还是感觉很熟悉的,但就是不能立即提笔写代码,还在草稿纸上画了画例子才把思路想明白。

 

题目中已经描述清楚了next permutation的功能,就是给出指定排列的下一个排列。

我们先来看看123的全排列的字典序:123, 132, 213, 231, 312, 321,相邻的两个排列尽量保持前部分的排列不变,改变后面部分的排列,只有当后半部分达到字典序的最大(数组上看就是降序排列)时才会去改变前面的元素。

假设给定的排列是a1,a2,a3,…,an,我们需要从后往前扫描,寻找可以改变的元素,即找到一个分隔点i,使得i之后的所有元素都是降序排列的。然后保证next permutation得到的排列中前i个元素不变,后面n-i个数字的排列是字典序大于a(i),a(i+1),a(i+2),…,an的最小字典序排列。

 

那么如何找到大于排列a(i),a(i+1),a(i+2),…,an的最小字典序排列?上面在找分隔点i时我们已经知道了,i之后的元素排列已经饱和(降序排列,不能更大了),所以第i个元素需要被比他更大的元素替换了,怎样的替换才是字典序上的紧的上界呢?肯定是大于第i个元素且离i最近的,再次强调后面n-i-1个数字是有序的,所以我们只需要从后往前扫描到第一个比第i个元素大的元素,交换两者即可。

 

稍等,采用我们能找到的最小的可以替换a(i)的元素替换它之后能够保证是上面序列的最紧上界吗?不是,我们还必须让a(i)之后的排列尽可能的字典序最小(在这里是递增有序),所以我们需要将第i个元素之后的元素逆序一下。至此,就找到了给定排列的下一个排列。

 

代码如下:

 

void nextPermutation(vector
   
   
    
     &arr) {
        if(1 >= arr.size()) return;
        int i = arr.size() - 2, j;
        while(i >= 0 && arr[i] >= arr[i+1])
        {
            --i;
        }
        if(i >= 0)
        {
            j = arr.size() - 1;
            while(j >= 0 && arr[j] <= arr[i])
            {
                --j;
            }
            swap(arr[i], arr[j]);
        }
        ++i;
        j = arr.size() - 1;
        while(i < j)
        {
            swap(arr[i++], arr[j--]);
        }
    }

   
   

 

第三道,给一组非负的整数来表示一个柱状图,设计一个算法获得柱状图中最大的矩形的面积。比如,输入2,1,4,5,1,3,3,应该输出8,其中每个柱状条的宽度为1。

 

很经典的老题目,没有思路的时候可以枚举矩形的左右边界,时间复杂度是O(n^3)的。稍微优化一下也可以找到O(n^2)的算法,这里还是直接介绍一下巧妙的O(n)的算法(但是需要额外的O(n)的空间)。

 

我们采用栈存储所有的高度非递减的值对应的下标,既然要保证栈中下标对应的元素值是非递减的,我们只有这样利用栈,顺序扫描数组,当遇到的元素大于或等于栈顶元素就压栈,当遇到的元素小于栈顶元素就依次出栈,直至栈顶元素小于等于它,再把它放进去。

 

我们需要计算的是以每个柱状条为高度的矩形的面积。

但是对于某个柱状条,如何找到以它为高度的矩形的两端位置呢?接下来就是见证这个辅助空间栈的奇迹的时候了。

我们在每个元素(方便叙述不妨设其值为h,下标为m)出栈的时候计算以h为高度的矩形的面积,那么h什么时候出栈呢?顺序扫描的过程中遇到了第一个比h小的元素,既然这个元素比h小,肯定不能在以h为高度的矩形中吧,这样我们就不费吹灰之力地找到了以h为高度的矩形的右边界,就是h出栈时碰到的这个元素(不包括它,不妨设其下标为r)。有了高度和右边界还是算不出矩形的面积,我们必须知道这个矩形的左边界。我们再来看看这个神奇的栈吧,栈中存放的是下标,这些下标对应的值是非递减的,也就是说栈顶元素m的下一个元素就是我们要找的矩形的左边界(不包括它)。

 

对于上面的例子,当栈中存放的是1,4,5对应的下标1,2,3时,当我们遇到元素1(下标4),需要考虑5的出栈,以5为高度的矩形只能是(2, 4)开区间,这样矩形的面积就是5*1=5。

等等,如果遇到两个元素相等呢?会不会出问题呢?现在加上栈中存放的是1,3,3对应的下标4,5,6,考虑第6号元素高度为3的矩形时,我们会以(5,7)为矩形的边界,但是该矩形明明可以再向左扩展一个柱状条的,难道不是我们的边界找错了吗?放心,下面不是还有一个高度3吗!当考虑第5号元素高度为3的矩形时,计算的矩形面积就是6了。

 

还有一个特例,如果栈空了怎么办?那只能说明我们正在考虑的h已经只目前遇到的最小高度了,其左边界就是-1。

 

Ok!几句话解释本题的基本思路:顺序扫描数组,利用栈存放非递减的元素对应的下标,当每个下标值出栈的时候考虑以其对应的元素值为高度的矩形,该矩形的右边界就是它出栈时我们扫描到的位置,而左边界就是栈中其下一个值。

 

扫描数组一遍,每个数字只进栈一次,出栈一次,出栈时只会看栈中下一个元素,所以时间效率是O(n)的。

 

代码如下:

int largestRect(vector
   
   
    
     &heights)
{
    int n = heights.size();
    if(0 == n) return 0;
    stack
    
    
     
      hist;
    int i = 0, ret = 0;
    int high, wide;
    while(i < n || !hist.empty())
    {
        if(hist.empty() || (i < n && heights[hist.top()] <= heights[i]) )
        {
            hist.push(i++);
        } else {
            high = heights[hist.top()];
            hist.pop();
            wide = hist.empty() ? i : i - 1 - hist.top();
            high *= wide;
            if(high > ret) ret = high;
        }
    }
    return ret;
}

    
    
   
   


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值