850. 矩形面積 II : 掃描線模板題
題目描述
這是 LeetCode 上的 850. 矩形面積 II ,難度為 困難 。
Tag : 「掃描線」
我們給出了一個(軸對齊的)二維矩形列表 rectangles
。 對於 $rectangle[i] = [x_1, y_1, x_2, y_2]$,其中$(x_1, y_1)$ 是矩形 i
左下角的座標,$ (x_{i1}, y_{i1})$ 是該矩形 左下角 的座標,$ (x_{i2}, y_{i2})$ 是該矩形 右上角 的座標。
計算平面中所有 rectangles
所覆蓋的 總面積 。任何被兩個或多個矩形覆蓋的區域應只計算 一次 。
返回 總面積 。因為答案可能太大,返回 $10^9 + 7$ 的 模 。
示例 1:
輸入:rectangles = [[0,0,2,2],[1,0,2,3],[1,0,3,1]] 輸出:6 解釋:如圖所示,三個矩形覆蓋了總面積為6的區域。 從(1,1)到(2,2),綠色矩形和紅色矩形重疊。 從(1,0)到(2,3),三個矩形都重疊。
示例 2:
輸入:rectangles = [[0,0,1000000000,1000000000]] 輸出:49 解釋:答案是 1018 對 (109 + 7) 取模的結果, 即 49 。
提示:
掃描線
這是一道「掃描線」模板題。
將所有給定的矩形的左右邊對應的 x
端點提取出來並排序,每個端點可看作是一條豎直的線段(紅色),問題轉換為求解「由多條豎直線段分割開」的多個矩形的面積總和(黃色):
相鄰線段之間的寬度為單個矩形的「寬度」(通過 x
差值直接算得),問題轉換為求該區間內高度的並集(即矩形的高度)。
由於數據範圍只有 $200$,我們可以對給定的所有矩形進行遍歷,統計所有對該矩形有貢獻的 y
值線段(即有哪些 rs[i]
落在該矩形中),再對線段進行求交集(總長度),即可計算出該矩形的「高度」,從而計算出來該矩形的面積。
Java 代碼:
class Solution { int MOD = (int)1e9+7; public int rectangleArea(int[][] rs) { List<Integer> list = new ArrayList<>(); for (int[] info : rs) { list.add(info[0]); list.add(info[2]); } Collections.sort(list); long ans = 0; for (int i = 1; i < list.size(); i++) { int a = list.get(i - 1), b = list.get(i), len = b - a; if (len == 0) continue; List<int[]> lines = new ArrayList<>(); for (int[] info : rs) { if (info[0] <= a && b <= info[2]) lines.add(new int[]{info[1], info[3]}); } Collections.sort(lines, (l1, l2)->{ return l1[0] != l2[0] ? l1[0] - l2[0] : l1[1] - l2[1]; }); long tot = 0, l = -1, r = -1; for (int[] cur : lines) { if (cur[0] > r) { tot += r - l; l = cur[0]; r = cur[1]; } else if (cur[1] > r) { r = cur[1]; } } tot += r - l; ans += tot * len; ans %= MOD; } return (int) ans; } }
TypeScript 代碼:
const MOD = BigInt(1e9+7) function rectangleArea(rs: number[][]): number { const list = new Array<number>() for (let info of rs) { list.push(info[0]); list.push(info[2]); } list.sort((a,b)=>a-b) let ans = 0n for (let i = 1; i < list.length; i++) { const a = list[i - 1], b = list[i], len = b - a if (len == 0) continue const lines = new Array<number[]>() for (let info of rs) { if (info[0] <= a && b <= info[2]) lines.push([info[1], info[3]]) } lines.sort((l1,l2)=>{ return l1[0] != l2[0] ? l1[0] - l2[0] : l1[1] - l2[1] }) let tot = 0n, l = -1, r = -1 for (let cur of lines) { if (cur[0] > r) { tot += BigInt(r - l) l = cur[0]; r = cur[1] } else if (cur[1] > r) { r = cur[1] } } tot += BigInt(r - l) ans += tot * BigInt(len) ans %= MOD } return Number(ans) };
Python 代碼:
class Solution: def rectangleArea(self, rs: List[List[int]]) -> int: ps = [] for info in rs: ps.append(info[0]) ps.append(info[2]) ps.sort() ans = 0 for i in range(1, len(ps)): a, b = ps[i - 1], ps[i] width = b - a if width == 0: continue lines = [(info[1], info[3]) for info in rs if info[0] <= a and b <= info[2]] lines.sort() height, l, r = 0, -1, -1 for cur in lines: if cur[0] > r: height += r - l l, r = cur elif cur[1] > r: r = cur[1] height += r - l ans += height * width return ans % 1000000007
Go 代碼:
const MOD = int64(1e9 + 7) func rectangleArea(rectangles [][]int) int { list := []int{} for _, info := range rectangles { list = append(list, info[0]) list = append(list, info[2]) } sort.Ints(list) ans := int64(0) for i := 1; i < len(list); i++ { a, b, length := list[i - 1], list[i], list[i] - list[i - 1] if length == 0 { continue } lines := [][]int{} for _, info := range rectangles { if info[0] <= a && b <= info[2] { lines = append(lines, []int{info[1], info[3]}) } } sort.Slice(lines, func(i,j int) bool { if lines[i][0] != lines[j][0] { return lines[i][0] - lines[j][0] < 0 } return lines[i][1] - lines[j][1] < 0 }) total, l, r := int64(0), -1, -1 for _, cur := range lines { if cur[0] > r { total += int64(r - l) l, r = cur[0], cur[1] } else if cur[1] > r { r = cur[1] } } total += int64(r - l) ans += total * int64(length) ans %= MOD } return int(ans) }
- 時間複雜度:預處理所有掃描線的複雜度為 $O(n\log{n})$;處理所有相鄰的掃描線,並計算相鄰掃描線形成的矩形面積複雜度為 $O(n\log{n})$ 。整體複雜度為 $O(n^2\log{n})$
- 空間複雜度:$O(n)$
最後
這是我們「刷穿 LeetCode」系列文章的第 No.850
篇,系列開始於 2021/01/01,截止於起始日 LeetCode 上共有 1916 道題目,部分是有鎖題,我們將先把所有不帶鎖的題目刷完。
在這個系列文章裏面,除了講解解題思路以外,還會盡可能給出最為簡潔的代碼。如果涉及通解還會相應的代碼模板。
為了方便各位同學能夠電腦上進行調試和提交代碼,我建立了相關的倉庫: http://github.com/SharingSou... 。
在倉庫地址裏,你可以看到系列文章的題解鏈接、系列文章的相應代碼、LeetCode 原題鏈接和其他優選題解。
更多更全更熱門的「筆試/面試」相關資料可訪問排版精美的合集新基地 :tada::tada:
本文由mdnice多平台發佈
- 天翼雲全場景業務無縫替換至國產原生操作系統CTyunOS!
- 以羊了個羊為例,淺談小程序抓包與響應報文修改
- 這幾種常見的 JVM 調優場景,你知道嗎?
- 如此狂妄,自稱高性能隊列的Disruptor有啥來頭?
- 為什麼要學習GoF設計模式?
- 827. 最大人工島 : 簡單「並查集 枚舉」運用題
- 手把手教你如何使用 Timestream 實現物聯網時序數據存儲和分析
- 850. 矩形面積 II : 掃描線模板題
- Java 併發編程解析 | 基於JDK源碼解析Java領域中的併發鎖,我們可以從中學習到什麼內容?
- 【手把手】光説不練假把式,這篇全鏈路壓測實踐探索
- 大廠鍾愛的全鏈路壓測有什麼意義?四種壓測方案詳細對比分析
- 寫個續集,填坑來了!關於“Thread.sleep(0)這一行‘看似無用’的代碼”裏面留下的坑。
- 857. 僱傭 K 名工人的最低成本 : 枚舉 優先隊列(堆)運用題
- Vue3 實現一個自定義toast(小彈窗)
- 669. 修剪二叉搜索樹 : 常規樹的遍歷與二叉樹性質
- 讀完 RocketMQ 源碼,我學會了如何優雅的創建線程
- 性能調優——小小的log大大的坑
- 1582. 二進制矩陣中的特殊位置 : 簡單模擬題
- elementui源碼學習之仿寫一個el-switch
- 646. 最長數對鏈 : 常規貪心 DP 運用題