LeetCode 3453. Separate Squares I
biweekly contest 150。
以前沒仔細研究浮點數二分,這次吃了大虧,差點沒寫出來。
題目
https://leetcode.com/problems/separate-squares-i/description/
解法
所有矩形的面積和為 tot。
分割線位於 lim,位於 lim 以下的面積為 cnt。
需要滿足 cnt == tot - cnt。
試想 lim 從 0 開始逐漸變大過程中,對 cnt 產生的影響:lim 變大,cnt 也只增不減。
lim 越大,越能夠滿足條件。答案具有單調性,考慮二分答案。
維護函數 ok(lim),代表分割線 lim 是否滿足 cnt >= tot - cnt。
下界為 y 軸最小值 0;上界為 y 軸最大值,再加上矩形邊長,為 2e9。
找到第一個滿足的 lim 就是答案。
最後是浮點數二分的實現細節。
以往再找第一個大於等於的答案時,因為整數除法會使得中位數向下取整。
在上下界很接近時可能會造成會造成死循環,所以更新下界需要多加 1。
while lo < hi:
mid = (lo + hi) // 2
if not ok(mid):
lo = mid + 1
else:
hi = mid
但是浮點數沒有取整問題,不需要加 1。
取而代之的給定與答案的近似值,在此誤差內都算正確。
eps = 1e-5
while lo + eps < hi:
mid = (lo + hi) / 2
if not ok(mid):
lo = mid
else:
hi = mid
時間複雜度 O(N log (MX / eps)),其中 MX = 2e9,誤差 eps = 1e-5。
空間複雜度 O(1)。
class Solution:
def separateSquares(self, squares: List[List[int]]) -> float:
tot = sum(l*l for _, _, l in squares)
def ok(lim):
cnt = 0
for _, y, l in squares:
if lim > y:
cnt += (min(y+l, lim) - y) * l
return cnt >= tot - cnt
eps = 1e-5
lo = 0
hi = 2e9
while lo + eps < hi:
mid = (lo + hi) / 2
if not ok(mid):
lo = mid
else:
hi = mid
return lo
每次二分,使答案的可能區間 L = (hi - lo) 減半。需滿足:
L / 2^k <= 1e-5 移項得
k > log(2, L * 1e5)
代入本題區間 L = 2e9:
k > log(2, 1e15)
k = 48
至多只需二分 48 次。
以後乾脆直接 100 次算了,肯定夠。
class Solution:
def separateSquares(self, squares: List[List[int]]) -> float:
tot = sum(l*l for _, _, l in squares)
def ok(lim):
cnt = 0
for _, y, l in squares:
if lim > y:
cnt += (min(y+l, lim) - y) * l
return cnt >= tot - cnt
lo = 0
hi = 2e9
for _ in range(48):
mid = (lo + hi) / 2
if not ok(mid):
lo = mid
else:
hi = mid
return lo
想像這條分割線從下往上,面積 cnt 逐漸增加,這就是掃描線。
對於矩形左下角 (x, y) 來說,當掃描線移動到 y 時,x 的和增加 l;移動到 x+l 時,x 的和減少 l。
先把矩形轉換成差分,並在移動時以前綴和維護 x 的和,記做 x_ps。
每次移動到高度 y,與前一個高度差為 h = y - pre_y,本次移動所新增的面積為:
h * x_ps
在某個高度 y,滿足 cnt 超過總面積 tot 一半時,答案高度肯定不超過 y。
超出一半面積的部分為:
extra = cnt - half
以知當前 x 的和為 x_ps。
若要減少 extra 面積,則需使高度下降 (extra / x_ps)。
答案即 y - (extra / x_ps)。
時間複雜度 O(N log N),瓶頸為排序。
空間複雜度 O(N)。
class Solution:
def separateSquares(self, squares: List[List[int]]) -> float:
tot = sum(l * l for _, _, l in squares)
half = tot / 2
# turn squares into difference array
diff = Counter()
for _, y, l in squares:
diff[y] += l
diff[y + l] -= l
# sweep y-axis from low to high
# and maintain prefix sum of x
ys = sorted(diff)
cnt = 0
x_ps = 0
for i, y in enumerate(ys):
if i > 0:
h = y - ys[i - 1] # height between current and previous y
cnt += h * x_ps
if cnt >= half:
extra = cnt - half
return y - (extra / x_ps)
x_ps += diff[y]
return -1