0850. Rectangle Area I I

850. Rectangle Area II #

Problem #

We are given a list of (axis-aligned) rectangles. Each rectangle[i] = [x1, y1, x2, y2] , where (x1, y1) are the coordinates of the bottom-left corner, and (x2, y2) are the coordinates of the top-right corner of the ith rectangle.

Find the total area covered by all rectangles in the plane. Since the answer may be too large, return it modulo 10^9 + 7.

Example 1:

Input: [[0,0,2,2],[1,0,2,3],[1,0,3,1]]
Output: 6
Explanation: As illustrated in the picture.

Example 2:

Input: [[0,0,1000000000,1000000000]]
Output: 49
Explanation: The answer is 10^18 modulo (10^9 + 7), which is (10^9)^2 = (-7)^2 = 49.

Note:

  • 1 <= rectangles.length <= 200
  • rectanges[i].length = 4
  • 0 <= rectangles[i][j] <= 10^9
  • The total area covered by all rectangles will never exceed 2^63 - 1 and thus will fit in a 64-bit signed integer.

Problem Summary #

We are given a list of (axis-aligned) rectangles rectangles. For rectangle[i] = [x1, y1, x2, y2], where (x1, y1) are the coordinates of the bottom-left corner of rectangle i, and (x2, y2) are the coordinates of the top-right corner of that rectangle. Find the total area covered by all rectangles after overlapping in the plane. Since the answer may be too large, return it modulo 10 ^ 9 + 7.

Notes:

  • 1 <= rectangles.length <= 200
  • rectanges[i].length = 4
  • 0 <= rectangles[i][j] <= 10^9
  • The total area covered after the rectangles overlap will never exceed 2^63 - 1, which means the area result can be stored in a 64-bit signed integer.

Solution Approach #

  • Given some rectangles in a two-dimensional coordinate system, we need to find the area after these rectangles are merged. Since the rectangles overlap, the area after merging needs to be considered. The coordinate values of the rectangles can also be very large.

  • This problem feels very similar to Problem 218. The process of finding the skyline also involves buildings blocking buildings and overlapping cases. However, that problem only requires finding the turning points of the skyline, so we can process intervals by “subtracting one from the right boundary” to prevent incorrect results caused by two adjacent intervals sharing a point. But if we still use the same approach in this problem, it will be wrong, because after “subtracting one from the right boundary”, part of the area will be missing, and the final result will also be too small. So for this problem, the segment tree needs to be modified.

  • The idea is to first discretize the coordinates on the Y-axis and convert them into a segment tree. Convert the 2 edges of each rectangle into sweep lines: the left edge is an entering edge, and the right edge is a leaving edge.

  • Then traverse each sweep line from left to right, and update the segment tree on the Y-axis. For each coordinate interval on the X-axis, * the result of querying the total height of the segment tree = the interval area. Finally, adding up the area of each interval corresponding to the X-axis gives the final area after merging the rectangles. As shown in the middle diagram below.

    One thing to note is that the result of each query is not necessarily a continuous line segment. As shown in the rightmost diagram above, a hollow section may appear in the middle. This situation seems complicated, but it is actually very simple, because the height weights represented by each segment in the segment tree are different. Each time we query the maximum height, the result has already taken into account the possible hollow section in the middle.

  • The specific approach is to first discretize each rectangle in the Y-axis direction. Here, the leaf node of the segment tree is no longer a point, but an interval segment with interval length 1.

    Each leaf node no longer stores a single int value, but stores 2 values. One is the count value, which records the number of times this interval is covered. The other is the val value, which is used to map back to the length of this line segment. Because the Y-axis has been discretized, the interval coordinate spacing is 1, but the actual height on the Y-axis is not 1, so val is used to map back to the original height.

  • Initialize the segment tree. For leaf nodes, count = 0, and val is calculated according to the Y coordinates given in the problem.

  • Traverse each sweep line from left to right. Each sweep line updates the corresponding range down to the leaf nodes. During pushUp, the height val values of each interval segment need to be merged. If an interval is not covered, then the height val of this interval is 0, which also handles the possible “hollow in the middle” situation.

      func (sat *SegmentAreaTree) pushUp(treeIndex, leftTreeIndex, rightTreeIndex int) {
          newCount, newValue := sat.merge(sat.tree[leftTreeIndex].count, sat.tree[rightTreeIndex].count), 0
          if sat.tree[leftTreeIndex].count > 0 && sat.tree[rightTreeIndex].count > 0 {
              newValue = sat.merge(sat.tree[leftTreeIndex].val, sat.tree[rightTreeIndex].val)
          } else if sat.tree[leftTreeIndex].count > 0 && sat.tree[rightTreeIndex].count == 0 {
              newValue = sat.tree[leftTreeIndex].val
          } else if sat.tree[leftTreeIndex].count == 0 && sat.tree[rightTreeIndex].count > 0 {
              newValue = sat.tree[rightTreeIndex].val
          }
          sat.tree[treeIndex] = SegmentItem{count: newCount, val: newValue}
      }
    
  • Scan each sweep line, first pushDown to the leaf nodes, then pushUp to the root node.

  • The result can be obtained when traversing to the second-to-last sweep line. Because after the last sweep line is updated, the entire segment tree returns to its initial state.

  • This problem is a classic problem for the segment tree sweep line solution.

Code #


package leetcode

import (
	"sort"
)

func rectangleArea(rectangles [][]int) int {
	sat, res := SegmentAreaTree{}, 0
	posXMap, posX, posYMap, posY, lines := discretization850(rectangles)
	tmp := make([]int, len(posYMap))
	for i := 0; i < len(tmp)-1; i++ {
		tmp[i] = posY[i+1] - posY[i]
	}
	sat.Init(tmp, func(i, j int) int {
		return i + j
	})
	for i := 0; i < len(posY)-1; i++ {
		tmp[i] = posY[i+1] - posY[i]
	}
	for i := 0; i < len(posX)-1; i++ {
		for _, v := range lines[posXMap[posX[i]]] {
			sat.Update(posYMap[v.start], posYMap[v.end], v.state)
		}
		res += ((posX[i+1] - posX[i]) * sat.Query(0, len(posY)-1)) % 1000000007
	}
	return res % 1000000007
}

func discretization850(positions [][]int) (map[int]int, []int, map[int]int, []int, map[int][]LineItem) {
	tmpXMap, tmpYMap, posXArray, posXMap, posYArray, posYMap, lines := map[int]int{}, map[int]int{}, []int{}, map[int]int{}, []int{}, map[int]int{}, map[int][]LineItem{}
	for _, pos := range positions {
		tmpXMap[pos[0]]++
		tmpXMap[pos[2]]++
	}
	for k := range tmpXMap {
		posXArray = append(posXArray, k)
	}
	sort.Ints(posXArray)
	for i, pos := range posXArray {
		posXMap[pos] = i
	}

	for _, pos := range positions {
		tmpYMap[pos[1]]++
		tmpYMap[pos[3]]++
		tmp1 := lines[posXMap[pos[0]]]
		tmp1 = append(tmp1, LineItem{start: pos[1], end: pos[3], state: 1})
		lines[posXMap[pos[0]]] = tmp1
		tmp2 := lines[posXMap[pos[2]]]
		tmp2 = append(tmp2, LineItem{start: pos[1], end: pos[3], state: -1})
		lines[posXMap[pos[2]]] = tmp2
	}
	for k := range tmpYMap {
		posYArray = append(posYArray, k)
	}
	sort.Ints(posYArray)
	for i, pos := range posYArray {
		posYMap[pos] = i
	}
	return posXMap, posXArray, posYMap, posYArray, lines
}

// LineItem define
type LineItem struct { // line segment perpendicular to the x-axis
	start, end, state int // state = 1 means entering, -1 means leaving
}

// SegmentItem define
type SegmentItem struct {
	count int
	val   int
}

// SegmentAreaTree define
type SegmentAreaTree struct {
	data        []int
	tree        []SegmentItem
	left, right int
	merge       func(i, j int) int
}

// Init define
func (sat *SegmentAreaTree) Init(nums []int, oper func(i, j int) int) {
	sat.merge = oper
	data, tree := make([]int, len(nums)), make([]SegmentItem, 4*len(nums))
	for i := 0; i < len(nums); i++ {
		data[i] = nums[i]
	}
	sat.data, sat.tree = data, tree
	if len(nums) > 0 {
		sat.buildSegmentTree(0, 0, len(nums)-1)
	}
}

// Create a segment tree for the [left....right] interval at position treeIndex
func (sat *SegmentAreaTree) buildSegmentTree(treeIndex, left, right int) {
	if left == right-1 {
		sat.tree[treeIndex] = SegmentItem{count: 0, val: sat.data[left]}
		return
	}
	midTreeIndex, leftTreeIndex, rightTreeIndex := left+(right-left)>>1, sat.leftChild(treeIndex), sat.rightChild(treeIndex)
	sat.buildSegmentTree(leftTreeIndex, left, midTreeIndex)
	sat.buildSegmentTree(rightTreeIndex, midTreeIndex, right)
	sat.pushUp(treeIndex, leftTreeIndex, rightTreeIndex)
}

func (sat *SegmentAreaTree) pushUp(treeIndex, leftTreeIndex, rightTreeIndex int) {
	newCount, newValue := sat.merge(sat.tree[leftTreeIndex].count, sat.tree[rightTreeIndex].count), 0
	if sat.tree[leftTreeIndex].count > 0 && sat.tree[rightTreeIndex].count > 0 {
		newValue = sat.merge(sat.tree[leftTreeIndex].val, sat.tree[rightTreeIndex].val)
	} else if sat.tree[leftTreeIndex].count > 0 && sat.tree[rightTreeIndex].count == 0 {
		newValue = sat.tree[leftTreeIndex].val
	} else if sat.tree[leftTreeIndex].count == 0 && sat.tree[rightTreeIndex].count > 0 {
		newValue = sat.tree[rightTreeIndex].val
	}
	sat.tree[treeIndex] = SegmentItem{count: newCount, val: newValue}
}

func (sat *SegmentAreaTree) leftChild(index int) int {
	return 2*index + 1
}

func (sat *SegmentAreaTree) rightChild(index int) int {
	return 2*index + 2
}

// Query the value within the [left....right] interval

// Query define
func (sat *SegmentAreaTree) Query(left, right int) int {
	if len(sat.data) > 0 {
		return sat.queryInTree(0, 0, len(sat.data)-1, left, right)
	}
	return 0
}

func (sat *SegmentAreaTree) queryInTree(treeIndex, left, right, queryLeft, queryRight int) int {
	midTreeIndex, leftTreeIndex, rightTreeIndex := left+(right-left)>>1, sat.leftChild(treeIndex), sat.rightChild(treeIndex)
	if left > queryRight || right < queryLeft { // segment completely outside range
		return 0 // represents a null node
	}
	if queryLeft <= left && queryRight >= right { // segment completely inside range
		if sat.tree[treeIndex].count > 0 {
			return sat.tree[treeIndex].val
		}
		return 0
	}
	if queryLeft > midTreeIndex {
		return sat.queryInTree(rightTreeIndex, midTreeIndex, right, queryLeft, queryRight)
	} else if queryRight <= midTreeIndex {
		return sat.queryInTree(leftTreeIndex, left, midTreeIndex, queryLeft, queryRight)
	}
	// merge query results
	return sat.merge(sat.queryInTree(leftTreeIndex, left, midTreeIndex, queryLeft, midTreeIndex),
		sat.queryInTree(rightTreeIndex, midTreeIndex, right, midTreeIndex, queryRight))
}

// Update define
func (sat *SegmentAreaTree) Update(updateLeft, updateRight, val int) {
	if len(sat.data) > 0 {
		sat.updateInTree(0, 0, len(sat.data)-1, updateLeft, updateRight, val)
	}
}

func (sat *SegmentAreaTree) updateInTree(treeIndex, left, right, updateLeft, updateRight, val int) {
	midTreeIndex, leftTreeIndex, rightTreeIndex := left+(right-left)>>1, sat.leftChild(treeIndex), sat.rightChild(treeIndex)
	if left > right || left >= updateRight || right <= updateLeft { // Since the leaf node interval is no longer left == right, this check needs to include equality signs
		return // out of range. escape.
	}

	if updateLeft <= left && right <= updateRight { // segment is fully within update range
		if left == right-1 {
			sat.tree[treeIndex].count = sat.merge(sat.tree[treeIndex].count, val)
		}
		if left != right-1 { // update lazy[] for children
			sat.updateInTree(leftTreeIndex, left, midTreeIndex, updateLeft, updateRight, val)
			sat.updateInTree(rightTreeIndex, midTreeIndex, right, updateLeft, updateRight, val)
			sat.pushUp(treeIndex, leftTreeIndex, rightTreeIndex)
		}
		return
	}
	sat.updateInTree(leftTreeIndex, left, midTreeIndex, updateLeft, updateRight, val)
	sat.updateInTree(rightTreeIndex, midTreeIndex, right, updateLeft, updateRight, val)
	// merge updates
	sat.pushUp(treeIndex, leftTreeIndex, rightTreeIndex)
}


Calendar Jun 25, 2026
Edit Edit this page
Total visits:   You are visitor No.
中文