单调栈:平面上N个底部对齐紧靠在一起的矩形,宽高是任意整数,求其最大内切矩形的面积

//
//  acm-icpc-mono-stack.cpp

#include <iostream>
#include <cassert>
#include <vector>

struct Box { //Or name to Rect;
    int width;
    int height;
};

struct RangeItem {
    int start_index=-1;
    int end_index=-1;
    int width=-1;//处理高度递降的情况是必需的
    int height=-1;
    long area=-1;
    RangeItem(){}
    RangeItem(int s, int e, int w, int h, long a):start_index(s),end_index(e),width(w), height(h),area(a){}
    friend std::ostream& operator<<(std::ostream& out, const RangeItem& ri){
        out<<"RangeItem ["<<ri.start_index<<", "<<ri.end_index<<"] width="<<ri.width<<" height="<<ri.height<<" area="<<ri.area;
        return out;
    }
};

/**
 平面上N个矩形盒子底部水平对齐紧靠在一起放在地上,求其最大内切矩形的面积
 提示:利用单调栈,对于盒子宽度相同(不妨假设=1)的情况,是一道ACM/ICPC题目,也是一道Google面试题
 这里处理的是每个矩形的宽度高度任意的情况(面积累加使用long类型,没有考虑溢出的情况)
 */
long solve(Box boxes[], int N) {
    assert(N>=0);
    if (N==1)
        return boxes[0].width * (long)boxes[0].height;
    
    RangeItem max_ri_l2r(-1,-1,-1,-1,-1); //invalid
    std::vector<RangeItem> L;
        //stack不支持for-range语法,只好改用vector了
    //L中存储的项的height是按push_back顺序递增的
    for(int i=0; i<N; ++i) {
        const Box& box = boxes[i];
        int width = box.width;
        int height = box.height;
        long area = box.width * (long)height;
        std::cout<<"Box["<<i<<"] width="<<width<<" height="<<height<<" area="<<area<<std::endl;
        //反向扫描栈;
        RangeItem max_popped_ri;
        //注意:我们可以把水平线一排box视作一系列高度单调上升的range项,每次遇到一个高度下降的则产生一个max_popped_ri
        long max_popped_area=-1;
        if (L.size()>0) {
            RangeItem& ri = L.back();
            while (ri.height>height) {
                //注意!这里对简单的输入4,3,2,1(width=1)的情况会产生遗漏,解决方法就是从右往左再来一遍!
                if (ri.area>max_popped_area) {
                    max_popped_area = ri.area;
                    max_popped_ri = ri;
                }
                L.pop_back();//discard;
                if(L.size()==0)
                    break;
            }
        }
        if (max_popped_area>0) {
            if (max_popped_ri.area > max_ri_l2r.area) {
                max_ri_l2r = max_popped_ri;
                std::cout<<"[L2R]Set current max_max_popped_ri="<<max_ri_l2r<<std::endl;
            }
        }
        //现在当前box的height>=单调栈中所有的RangeItem的height,扩展栈中所有ri的end_index和area
        for(RangeItem& ri2 : L) {
            ri2.end_index = i;
            ri2.width += width;
            ri2.area += ri2.height * (long)width;
        }
        //如果当前box的height大于单调栈S中的top项,则它自己也扩展为一个新的RangeItem/start_index;
        if (L.empty() || box.height > L.back().height) {
            L.push_back(RangeItem(i, i, width, height, area));
        }
    }
    //OK, 现在再次遍历一遍单调S(注意,S中元素最多,当原始输入的boxes的height正好是全局递增的情况)
    //上面的代码跟下面的代码差别只在于一个是从左往右遍历,处理原始输入的递增子序列;一个是从右往左遍历,处理原始输入的递降子序列;
    {
        RangeItem& max_ri = max_ri_l2r; //rename
        for(RangeItem& ri : L) {
            if (ri.area > max_ri.area)
                max_ri = ri;
        }
        max_ri_l2r = max_ri;
    }
    
    //对于递减的情况,上面的代码会导致遗漏,这时候需要从右往左再算一遍,
    RangeItem max_ri_r2l(-1,-1,-1,-1,-1); //invalid
    std::vector<RangeItem> R;
    //stack不支持for-range语法,只好改用vector了
    //R中存储的项的height是按push_back顺序递增的
    for(int i=N-1; i>=0; --i) {
        const Box& box = boxes[i];
        int width = box.width;
        int height = box.height;
        long area = box.width * (long)height;
        std::cout<<"Box["<<i<<"] width="<<width<<" height="<<height<<" area="<<area<<std::endl;
        //反向扫描栈;
        RangeItem max_popped_ri;
        //注意:我们可以把水平线一排box视作一系列高度单调上升的range项,每次遇到一个高度下降的则产生一个max_popped_ri
        long max_popped_area=-1;
        if (R.size()>0) {
            RangeItem& ri = R.back();
            while (ri.height>height) {
                if (ri.area>max_popped_area) {
                    max_popped_area = ri.area;
                    max_popped_ri = ri;
                }
                R.pop_back();//discard;
                if(R.size()==0)
                    break;
            }
        }
        if (max_popped_area>0) {
            if (max_popped_ri.area > max_ri_r2l.area) {
                max_ri_r2l = max_popped_ri;
                std::cout<<"[R2L]Set current max_max_popped_ri="<<max_ri_r2l<<std::endl;
            }
        }
        //现在当前box的height>=单调栈中所有的RangeItem的height,扩展栈中所有ri的end_index和area
        for(RangeItem& ri2 : R) {
            ri2.start_index = i;
            ri2.width += width;
            ri2.area += ri2.height * (long)width;
        }
        //如果当前box的height大于单调栈S中的top项,则它自己也扩展为一个新的RangeItem/start_index;
        if (R.empty() || box.height > R.back().height) {
            R.push_back(RangeItem(i, i, width, height, area));
        }
    }
    //OK, 现在再次遍历一遍单调S(注意,S中元素最多,当原始输入的boxes的height正好是全局递增的情况)
    {
        RangeItem& max_ri = max_ri_r2l; //rename
        for(RangeItem& ri : R) {
            if (ri.area > max_ri.area)
                max_ri = ri;
        }
        max_ri_r2l = max_ri;
    }
    
    RangeItem max_ri = max_ri_l2r;
    if (max_ri.area < max_ri_r2l.area)
        max_ri = max_ri_r2l;
    
    std::cout<<"Max Range: ["<<max_ri.start_index<<", "<<max_ri.end_index<<"]"
    <<" width="<<max_ri.width
    <<" height="<<max_ri.height
    <<" area="<<max_ri.area
    <<"\n"<<std::endl;
    return max_ri.area;
}

int main(int argc, char** argv){
    //test:
    static Box boxes1[] = {{1,1}, {1,2}, {1,3}, {1,4}};
    solve(boxes1, 4);//6
    static Box boxes2[] = {{1,1}, {10,2}, {5,3}, {1,4}};
    solve(boxes2, 4);//32
    static Box boxes3[] = {{1,4}, {1,3}, {1,2}, {1,1}};
    solve(boxes3, 4);//6
    //TODO:  需要更多的测试数据
    return 0;
}
TODO: FIXME, 上面的代码有2段模式类似的部分,不同点在于循环的方向不一样,可以提取出一个单独的函数来吗?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值