LeetCode·每日一题·850.矩形面积 || ·【离散化+扫描线+数组】

题目

 

示例

 

思路

什么是扫描线?
扫描线就是用来解决矩形覆盖面积问题。

假如我们想要计算下图的覆盖面积,我们需要计算两个矩形的面积相加,然后减去中间蓝色交叉的部分。


若有n个矩形,我们需要计算他们两两交叉的面积,然后减去,时间复杂度非常高。 

假如我们可以对其进行分割,如图:

 

我们只需要计算矩形①②③的面积即可,于是问题转化为如何计算矩形①②③的面积。

矩形的宽很好计算,我们只需要把x轴的点按照从小到大的顺序排列即可。

难的是计算矩形的高。于是便有了扫描线。

定义:我们从左向右看,定义矩形的左侧边为入边,右侧边为出边,也就是我们扫描的方向,看起来就像用一个线从左向右来扫描所有的矩形

于是我们从左往右扫,遇到入边的线,则对入边区间进行+1操作,遇到出边的线,那么对出边区间进行-1操作。然后我们遍历整个区间,就得到了矩形的高。

我们举例计算:

 

当我们扫描到第一条边,是入边,范围是[1,3],于是我们给[1,3] + 1,此时整个范围只有[1,3]被覆盖,高为2,第一个矩形面积为 1 * 2 = 2

 当我们扫描到第二条边,是入边,范围是[2,4],于是我们给[2,4] + 1,此时整个范围只有[1,4]被覆盖,高为3,第一个矩形面积为 1 * 3 = 3

 

 

当我们扫描到第三条边,是出边,范围是[1,3],于是我们给[1,3] - 1,此时整个范围只有[2,4]被覆盖,高为2,第一个矩形面积为 3 * 2 = 6

 

当我们扫描到第四条边,是出边,范围是[2,4],于是我们给[2,4] - 1,此时整个范围没有覆盖,结束计算

 于是覆盖面积为2 + 3 + 6 = 11

计算高的方式就是区间问题,找所有区间内覆盖的长度,我们可以用线段树解决,线段树详解请移步。

为了方便,我们把所有的y坐标进行离散化处理

代码

typedef struct {
    int key;
    UT_hash_handle hh;
} HashItem; 

typedef struct Tuple {
    int val[3];
} Tuple;

HashItem *hashFindItem(HashItem **obj, int key) {
    HashItem *pEntry = NULL;
    HASH_FIND_INT(*obj, &key, pEntry);
    return pEntry;
}

bool hashAddItem(HashItem **obj, int key) {
    if (hashFindItem(obj, key)) {
        return false;
    }
    HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));
    pEntry->key = key;
    HASH_ADD_INT(*obj, key, pEntry);
    return true;
}

void hashFree(HashItem **obj) {
    HashItem *curr = NULL, *tmp = NULL;
    HASH_ITER(hh, *obj, curr, tmp) {
        HASH_DEL(*obj, curr);  
        free(curr);             
    }
}

static inline int cmp1(const void *pa, const void *pb) {
    return *(int *)pa - *(int *)pb;
}

static inline int cmp2(const void *pa, const void *pb) {
    Tuple *a = (Tuple *)pa, *b = (Tuple *)pb;
    if (a->val[0] != b->val[0]) {
        return a->val[0] - b->val[0];
    } else if (a->val[1] != b->val[1]) {
        return a->val[1] - b->val[1];
    } else {
        return a->val[2] - b->val[2];
    }
}

int rectangleArea(int** rectangles, int rectanglesSize, int* rectanglesColSize){
    int n = rectanglesSize;
    HashItem *set = NULL;
    for (int i = 0; i < n; i++) {
        // 下边界
        hashAddItem(&set, rectangles[i][1]);
        // 上边界
        hashAddItem(&set, rectangles[i][3]);
    }
    int hboundSize = HASH_COUNT(set);
    int hbound[hboundSize], pos = 0;
    for (HashItem *pEntry = set; pEntry != NULL; pEntry = pEntry->hh.next) {
        hbound[pos++] = pEntry->key;
    }
    hashFree(&set);

    qsort(hbound, hboundSize, sizeof(int), cmp1);
    // 「思路与算法部分」的 length 数组并不需要显式地存储下来
    // length[i] 可以通过 hbound[i+1] - hbound[i] 得到
    int seg[hboundSize - 1];
    int sweepSize = n * 2;
    Tuple sweep[sweepSize];
    pos = 0;
    memset(seg, 0, sizeof(seg));
    for (int i = 0; i < n; ++i) {
        // 左边界
        sweep[pos].val[0] = rectangles[i][0];
        sweep[pos].val[1] = i;
        sweep[pos].val[2] = 1;
        pos++;
        // 右边界
        sweep[pos].val[0] = rectangles[i][2];
        sweep[pos].val[1] = i;
        sweep[pos].val[2] = -1;
        pos++;
    }
    qsort(sweep, sweepSize, sizeof(Tuple), cmp2);
    long long ans = 0;
    for (int i = 0; i < n * 2; ++i) {
        int j = i;
        while (j + 1 < sweepSize && sweep[i].val[0] == sweep[j + 1].val[0]) {
            ++j;
        }
        if (j + 1 == sweepSize) {
            break;
        }
        // 一次性地处理掉一批横坐标相同的左右边界
        for (int k = i; k <= j; ++k) {
            int idx = sweep[k].val[1], diff = sweep[k].val[2];
            int left = rectangles[idx][1], right = rectangles[idx][3];
            for (int x = 0; x < hboundSize - 1; ++x) {
                if (left <= hbound[x] && hbound[x + 1] <= right) {
                    seg[x] += diff;
                }
            }
        }
        int cover = 0;
        for (int k = 0; k < hboundSize - 1; ++k) {
            if (seg[k] > 0) {
                cover += (hbound[k + 1] - hbound[k]);
            }
        }
        ans += (long long)cover * (sweep[j + 1].val[0] - sweep[j].val[0]);
        i = j;
    }
    return ans % (long long)(1e9 + 7);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值