普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月15日LeetCode 每日一题题解

每日一题-最大化网格图中正方形空洞的面积🟡

2026年1月15日 00:00

给你两个整数 nm,以及两个整数数组 hBarsvBars。网格由 n + 2 条水平线和 m + 2 条竖直线组成,形成 1x1 的单元格。网格中的线条从 1 开始编号。

你可以从 hBars 中 删除 一些水平线条,并从 vBars 中删除一些竖直线条。注意,其他线条是固定的,无法删除。

返回一个整数表示移除一些线条(可以不移除任何线条)后,网格中 正方形空洞的最大面积 

 

示例 1:

输入: n = 2, m = 1, hBars = [2,3], vBars = [2]

输出: 4

解释:

左侧图片展示了网格的初始状态。水平线是 [1,2,3,4],竖直线是 [1,2,3]

构造最大正方形空洞的一种方法是移除水平线 2 和竖直线 2。

示例 2:

输入: n = 1, m = 1, hBars = [2], vBars = [2]

输出: 4

解释:

移除水平线 2 和竖直线 2,可以得到最大正方形空洞。

示例 3:

输入: n = 2, m = 3, hBars = [2,3], vBars = [2,4]

输出: 4

解释:

构造最大正方形空洞的一种方法是移除水平线 3 和竖直线 4。

 

提示:

  • 1 <= n <= 109
  • 1 <= m <= 109
  • 1 <= hBars.length <= 100
  • 2 <= hBars[i] <= n + 1
  • 1 <= vBars.length <= 100
  • 2 <= vBars[i] <= m + 1
  • hBars 中所有值互不相同。
  • vBars 中所有值互不相同。

不排序,线性时间复杂度(Python/Java/C++/Go)

作者 endlesscheng
2023年11月26日 18:57

阅读理解题,难度在读题上。

贪心地,删的线段越多,面积越大,那就先把所有能删的线段都删掉,计算最大的矩形,长宽分别是多少。

取长宽的最小值,即为正方形的边长(多删的线段撤销删除)。

以 $\textit{hBars}$ 为例:

  • 不删,最长长度是 $1$。
  • 删除一条线段,最长长度是 $2$。
  • 删除两条编号相邻的线段,最长长度是 $3$。
  • 删除三条编号连续的线段(例如 $2,3,4$),最长长度是 $4$。
  • 依此类推。

所以本题要做的是,把数组排序后,求最长连续递增子数组的长度加一。

正方形的边长是长宽的最小值,其平方即为正方形的面积。

优化前

###py

class Solution:
    # 返回 a 排序后的最长连续递增子数组的长度
    def f(self, a: List[int]) -> int:
        a.sort()
        mx = cnt = 0
        for i, x in enumerate(a):
            if i > 0 and x == a[i - 1] + 1:
                cnt += 1
            else:
                cnt = 1  # 重新计数
            mx = max(mx, cnt)
        return mx

    def maximizeSquareHoleArea(self, n: int, m: int, hBars: List[int], vBars: List[int]) -> int:
        side = min(self.f(hBars), self.f(vBars)) + 1
        return side * side

###java

class Solution {
    public int maximizeSquareHoleArea(int n, int m, int[] hBars, int[] vBars) {
        int side = Math.min(f(hBars), f(vBars)) + 1;
        return side * side;
    }

    // 返回 a 排序后的最长连续递增子数组的长度
    private int f(int[] a) {
        Arrays.sort(a);
        int mx = 1;
        int cnt = 1;
        for (int i = 1; i < a.length; i++) {
            if (a[i] == a[i - 1] + 1) {
                cnt++;
                mx = Math.max(mx, cnt);
            } else {
                cnt = 1; // 重新计数
            }
        }
        return mx;
    }
}

###cpp

class Solution {
    // 返回 a 排序后的最长连续递增子数组的长度
    int f(vector<int>& a) {
        ranges::sort(a);
        int mx = 1, cnt = 1;
        for (int i = 1; i < a.size(); i++) {
            if (a[i] == a[i - 1] + 1) {
                cnt++;
                mx = max(mx, cnt);
            } else {
                cnt = 1; // 重新计数
            }
        }
        return mx;
    }

public:
    int maximizeSquareHoleArea(int, int, vector<int>& hBars, vector<int>& vBars) {
        int side = min(f(hBars), f(vBars)) + 1;
        return side * side;
    }
};

###go

// 返回 a 排序后的最长连续递增子数组的长度
func f(a []int) (mx int) {
slices.Sort(a)
cnt := 0
for i, x := range a {
if i > 0 && x == a[i-1]+1 {
cnt++
} else {
cnt = 1 // 重新计数
}
mx = max(mx, cnt)
}
return mx
}

func maximizeSquareHoleArea(_, _ int, hBars, vBars []int) int {
side := min(f(hBars), f(vBars)) + 1
return side * side
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(h\log h+v\log v)$,其中 $h$ 为 $\textit{hBars}$ 的长度,$v$ 为 $\textit{vBars}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。忽略排序的栈开销。

优化

128. 最长连续序列 的技巧优化,见 我的题解

###py

class Solution:
    # 128. 最长连续序列
    def longestConsecutive(self, nums: List[int]) -> int:
        st = set(nums)  # 把 nums 转成哈希集合
        ans = 0
        for x in st:  # 遍历哈希集合
            if x - 1 in st:  # 如果 x 不是序列的起点,直接跳过
                continue
            # x 是序列的起点
            y = x + 1
            while y in st:  # 不断查找下一个数是否在哈希集合中
                y += 1
            # 循环结束后,y-1 是最后一个在哈希集合中的数
            ans = max(ans, y - x)  # 从 x 到 y-1 一共 y-x 个数
        return ans

    def maximizeSquareHoleArea(self, n: int, m: int, hBars: List[int], vBars: List[int]) -> int:
        side = min(self.longestConsecutive(hBars), self.longestConsecutive(vBars)) + 1
        return side * side

###java

class Solution {
    public int maximizeSquareHoleArea(int n, int m, int[] hBars, int[] vBars) {
        int side = Math.min(longestConsecutive(hBars), longestConsecutive(vBars)) + 1;
        return side * side;
    }

    // 128. 最长连续序列
    private int longestConsecutive(int[] nums) {
        Set<Integer> st = new HashSet<>();
        for (int num : nums) {
            st.add(num); // 把 nums 转成哈希集合
        }

        int ans = 0;
        for (int x : st) { // 遍历哈希集合
            if (st.contains(x - 1)) { // 如果 x 不是序列的起点,直接跳过
                continue;
            }
            // x 是序列的起点
            int y = x + 1;
            while (st.contains(y)) { // 不断查找下一个数是否在哈希集合中
                y++;
            }
            // 循环结束后,y-1 是最后一个在哈希集合中的数
            ans = Math.max(ans, y - x); // 从 x 到 y-1 一共 y-x 个数
        }
        return ans;
    }
}

###cpp

class Solution {
    // 128. 最长连续序列
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> st(nums.begin(), nums.end()); // 把 nums 转成哈希集合
        int ans = 0;
        for (int x : st) { // 遍历哈希集合
            if (st.contains(x - 1)) { // 如果 x 不是序列的起点,直接跳过
                continue;
            }
            // x 是序列的起点
            int y = x + 1;
            while (st.contains(y)) { // 不断查找下一个数是否在哈希集合中
                y++;
            }
            // 循环结束后,y-1 是最后一个在哈希集合中的数
            ans = max(ans, y - x); // 从 x 到 y-1 一共 y-x 个数
        }
        return ans;
    }

public:
    int maximizeSquareHoleArea(int, int, vector<int>& hBars, vector<int>& vBars) {
        int side = min(longestConsecutive(hBars), longestConsecutive(vBars)) + 1;
        return side * side;
    }
};

###go

// 128. 最长连续序列
func longestConsecutive(nums []int) (ans int) {
has := map[int]bool{}
for _, num := range nums {
has[num] = true // 把 nums 转成哈希集合
}

for x := range has { // 遍历哈希集合
if has[x-1] { // 如果 x 不是序列的起点,直接跳过
continue
}
// x 是序列的起点
y := x + 1
for has[y] { // 不断查找下一个数是否在哈希集合中
y++
}
// 循环结束后,y-1 是最后一个在哈希集合中的数
ans = max(ans, y-x) // 从 x 到 y-1 一共 y-x 个数
}
return
}

func maximizeSquareHoleArea(_, _ int, hBars, vBars []int) int {
side := min(longestConsecutive(hBars), longestConsecutive(vBars)) + 1
return side * side
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(h+v)$,其中 $h$ 为 $\textit{hBars}$ 的长度,$v$ 为 $\textit{vBars}$ 的长度。
  • 空间复杂度:$\mathcal{O}(h+v)$。

相似题目

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

n、m参数不用考虑,只需对hBars、vBars排序遍历计算,0ms双百

2023年11月26日 08:40

Problem: 100138. 最大化网格图中正方形空洞的面积

[TOC]

思路

分析题意:

  • 抽掉一根线,则空出空间: 2

  • 若存在连续 l 根线,抽出后,空出空间为: l + 1

  • hBars.length、vBars.length 至少为1,即必有线被抽掉,至少有空间 2

因此,贪心思路来考虑:

  • 第一步、对 hBars 和 vBars 排序

  • 第二步、分别求出 hBars 和 vBars 的连续最长线段

  • 第三步、取 hBars 和 vBars 的连续最长线段两者的较小值,平方后返回

此题,n、m 两个参数可以不用到。

Code

执行用时分布0ms击败100.00%;消耗内存分布6.47MB击败100.00%

###C

int cmp(const void* a, const void* b) {
    return *(int*)a - *(int*)b;
}
int maxlen(int* lst, int len) {
    qsort(lst, len, sizeof(int), cmp);
    int ma = 2, l = 2;
    for (int i = 0; i < len - 1; ++ i)
        if (lst[i + 1] - lst[i] == 1) { if (++ l > ma) ma = l; }
        else l = 2;
    return ma;
}
int maximizeSquareHoleArea(int n, int m, int* hBars, int hBarsSize, int* vBars, int vBarsSize) {
    int h = maxlen(hBars, hBarsSize), v = maxlen(vBars, vBarsSize);
    return h < v ? h * h : v * v;
}

###Python3

class Solution:
    def maximizeSquareHoleArea(self, n: int, m: int, hBars: List[int], vBars: List[int]) -> int:
        def maxlen(lst):
            ma, l = 2, 2
            for x, y in pairwise(sorted(lst)):
                if y - x == 1: 
                    l += 1
                    ma = max(ma, l)
                else:
                    l = 2
            return ma
        return min(maxlen(hBars), maxlen(vBars)) ** 2

您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^

↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^

【 排序】java双百 - 枚举差值为1

Problem: 100138. 最大化网格图中正方形空洞的面积

[TOC]

1.png

解题方法

其实这个题没有看上去那么难

只需要排序之后取两个数组差值为1的最大个数即可

计算差值为一的元素个数是因为需要统计最大能切割出多大的正方形

(周赛wa三次真的要吐血了X_X)

复杂度

时间复杂度: $O(nlogn + mlogm)$ 主要取决于排序

Code

###Java

class Solution {
    public int maximizeSquareHoleArea(int n, int m, int[] hBars, int[] vBars) {
        int h = 1;
        int v = 1;
        Arrays.sort(hBars);
        Arrays.sort(vBars);
        int ht = 1;
        int vt = 1;
        for(int i = 1;i < hBars.length;i++){
            if(hBars[i] - hBars[i - 1] == 1) ht++;
            if(hBars[i] - hBars[i - 1] != 1){
                ht = 1;
            }
            h = Math.max(ht,h);
        }
        for(int i = 1;i < vBars.length;i++){
            if(vBars[i] - vBars[i - 1] == 1) vt++;
            if(vBars[i] - vBars[i - 1] != 1){
                vt = 1;
            }
            v = Math.max(vt,v);
        }
        int l = Math.min(v + 1,h + 1);
        return l * l;
    }
}
昨天 — 2026年1月14日LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:扫描线(清晰题解)

作者 lcbin
2026年1月14日 07:48

方法一:扫描线

本题可以使用扫描线算法来计算所有正方形的总面积。

我们将每个正方形的上下边界作为扫描线的事件点,按 $y$ 坐标从小到大排序。对于每个事件点,我们使用线段树来维护当前扫描线下方被覆盖的 $x$ 轴区间长度,从而计算出当前扫描线与上一个扫描线之间的面积增量。

具体步骤如下:

  1. 预处理事件点:对于每个正方形,计算其上下边界的 $y$ 坐标,并将其作为事件点加入事件列表中。每个事件点包含 $y$ 坐标、左边界 $x_1$、右边界 $x_2$ 以及一个标志(表示是上边界还是下边界)。
  2. 排序事件点:将所有事件点按 $y$ 坐标从小到大排序。
  3. 构建线段树:使用离散化后的 $x$ 坐标构建线段树,用于维护当前被覆盖的 $x$ 轴区间长度。
  4. 扫描事件点:遍历排序后的事件点列表,对于每个事件点:
    • 计算当前事件点与上一个事件点之间的面积增量,并累加到总面积中。
    • 根据当前事件点的类型(上边界或下边界),更新线段树,增加或减少对应的 $x$ 轴区间覆盖计数。
  5. 计算目标面积:总面积的一半即为目标面积。
  6. 再次扫描事件点:再次遍历事件点列表,计算累计面积,当累计面积达到目标面积时,计算并返回对应的 $y$ 坐标。

###python

class Node:
    __slots__ = ("l", "r", "cnt", "length")

    def __init__(self):
        self.l = self.r = 0
        self.cnt = self.length = 0


class SegmentTree:
    def __init__(self, nums):
        n = len(nums) - 1
        self.nums = nums
        self.tr = [Node() for _ in range(n << 2)]
        self.build(1, 0, n - 1)

    def build(self, u, l, r):
        self.tr[u].l, self.tr[u].r = l, r
        if l != r:
            mid = (l + r) >> 1
            self.build(u << 1, l, mid)
            self.build(u << 1 | 1, mid + 1, r)

    def modify(self, u, l, r, k):
        if self.tr[u].l >= l and self.tr[u].r <= r:
            self.tr[u].cnt += k
        else:
            mid = (self.tr[u].l + self.tr[u].r) >> 1
            if l <= mid:
                self.modify(u << 1, l, r, k)
            if r > mid:
                self.modify(u << 1 | 1, l, r, k)
        self.pushup(u)

    def pushup(self, u):
        if self.tr[u].cnt:
            self.tr[u].length = self.nums[self.tr[u].r + 1] - self.nums[self.tr[u].l]
        elif self.tr[u].l == self.tr[u].r:
            self.tr[u].length = 0
        else:
            self.tr[u].length = self.tr[u << 1].length + self.tr[u << 1 | 1].length

    @property
    def length(self):
        return self.tr[1].length


class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        xs = set()
        segs = []
        for x1, y1, l in squares:
            x2, y2 = x1 + l, y1 + l
            xs.update([x1, x2])
            segs.append((y1, x1, x2, 1))
            segs.append((y2, x1, x2, -1))
        segs.sort()
        st = sorted(xs)
        tree = SegmentTree(st)
        d = {x: i for i, x in enumerate(st)}
        area = 0
        y0 = 0
        for y, x1, x2, k in segs:
            area += (y - y0) * tree.length
            tree.modify(1, d[x1], d[x2] - 1, k)
            y0 = y

        target = area / 2
        area = 0
        y0 = 0
        for y, x1, x2, k in segs:
            t = (y - y0) * tree.length
            if area + t >= target:
                return y0 + (target - area) / tree.length
            area += t
            tree.modify(1, d[x1], d[x2] - 1, k)
            y0 = y
        return 0

###java

class Node {
    int l, r, cnt, length;
}

class SegmentTree {
    private Node[] tr;
    private int[] nums;

    public SegmentTree(int[] nums) {
        this.nums = nums;
        int n = nums.length - 1;
        tr = new Node[n << 2];
        for (int i = 0; i < tr.length; ++i) {
            tr[i] = new Node();
        }
        build(1, 0, n - 1);
    }

    private void build(int u, int l, int r) {
        tr[u].l = l;
        tr[u].r = r;
        if (l != r) {
            int mid = (l + r) >> 1;
            build(u << 1, l, mid);
            build(u << 1 | 1, mid + 1, r);
        }
    }

    public void modify(int u, int l, int r, int k) {
        if (tr[u].l >= l && tr[u].r <= r) {
            tr[u].cnt += k;
        } else {
            int mid = (tr[u].l + tr[u].r) >> 1;
            if (l <= mid) {
                modify(u << 1, l, r, k);
            }
            if (r > mid) {
                modify(u << 1 | 1, l, r, k);
            }
        }
        pushup(u);
    }

    private void pushup(int u) {
        if (tr[u].cnt > 0) {
            tr[u].length = nums[tr[u].r + 1] - nums[tr[u].l];
        } else if (tr[u].l == tr[u].r) {
            tr[u].length = 0;
        } else {
            tr[u].length = tr[u << 1].length + tr[u << 1 | 1].length;
        }
    }

    public int query() {
        return tr[1].length;
    }
}

class Solution {
    public double separateSquares(int[][] squares) {
        Set<Integer> xs = new HashSet<>();
        List<int[]> segs = new ArrayList<>();
        for (int[] sq : squares) {
            int x1 = sq[0], y1 = sq[1], l = sq[2];
            int x2 = x1 + l, y2 = y1 + l;
            xs.add(x1);
            xs.add(x2);
            segs.add(new int[] {y1, x1, x2, 1});
            segs.add(new int[] {y2, x1, x2, -1});
        }
        segs.sort(Comparator.comparingInt(a -> a[0]));
        int[] st = new int[xs.size()];
        int i = 0;
        for (int x : xs) {
            st[i++] = x;
        }
        Arrays.sort(st);
        SegmentTree tree = new SegmentTree(st);
        Map<Integer, Integer> d = new HashMap<>(st.length);
        for (i = 0; i < st.length; i++) {
            d.put(st[i], i);
        }
        double area = 0.0;
        int y0 = 0;
        for (int[] s : segs) {
            int y = s[0], x1 = s[1], x2 = s[2], k = s[3];
            area += (double) (y - y0) * tree.query();
            tree.modify(1, d.get(x1), d.get(x2) - 1, k);
            y0 = y;
        }
        double target = area / 2.0;
        area = 0.0;
        y0 = 0;
        for (int[] s : segs) {
            int y = s[0], x1 = s[1], x2 = s[2], k = s[3];
            double t = (double) (y - y0) * tree.query();
            if (area + t >= target) {
                return y0 + (target - area) / tree.query();
            }
            area += t;
            tree.modify(1, d.get(x1), d.get(x2) - 1, k);
            y0 = y;
        }
        return 0.0;
    }
}

###cpp

struct Node {
    int l = 0, r = 0, cnt = 0;
    int length = 0;
};

class SegmentTree {
private:
    vector<Node> tr;
    vector<int> nums;

    void build(int u, int l, int r) {
        tr[u].l = l;
        tr[u].r = r;
        if (l != r) {
            int mid = (l + r) >> 1;
            build(u << 1, l, mid);
            build(u << 1 | 1, mid + 1, r);
        }
    }

    void pushup(int u) {
        if (tr[u].cnt > 0) {
            tr[u].length = nums[tr[u].r + 1] - nums[tr[u].l];
        } else if (tr[u].l == tr[u].r) {
            tr[u].length = 0;
        } else {
            tr[u].length = tr[u << 1].length + tr[u << 1 | 1].length;
        }
    }

public:
    SegmentTree(const vector<int>& nums)
        : nums(nums) {
        int n = (int) nums.size() - 1;
        tr.assign(n << 2, Node());
        build(1, 0, n - 1);
    }

    void modify(int u, int l, int r, int k) {
        if (tr[u].l >= l && tr[u].r <= r) {
            tr[u].cnt += k;
        } else {
            int mid = (tr[u].l + tr[u].r) >> 1;
            if (l <= mid) modify(u << 1, l, r, k);
            if (r > mid) modify(u << 1 | 1, l, r, k);
        }
        pushup(u);
    }

    int query() const {
        return tr[1].length;
    }
};

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        set<int> xs;
        vector<array<int, 4>> segs;

        for (auto& sq : squares) {
            int x1 = sq[0], y1 = sq[1], l = sq[2];
            int x2 = x1 + l, y2 = y1 + l;
            xs.insert(x1);
            xs.insert(x2);
            segs.push_back({y1, x1, x2, 1});
            segs.push_back({y2, x1, x2, -1});
        }

        sort(segs.begin(), segs.end(), [](const auto& a, const auto& b) {
            return a[0] < b[0];
        });

        vector<int> st;
        st.reserve(xs.size());
        for (int x : xs) st.push_back(x);

        SegmentTree tree(st);

        unordered_map<int, int> d;
        d.reserve(st.size() * 2);
        for (int i = 0; i < (int) st.size(); i++) d[st[i]] = i;

        double area = 0.0;
        int y0 = 0;
        for (auto& s : segs) {
            int y = s[0], x1 = s[1], x2 = s[2], k = s[3];
            area += (double) (y - y0) * tree.query();
            tree.modify(1, d[x1], d[x2] - 1, k);
            y0 = y;
        }

        double target = area / 2.0;
        area = 0.0;
        y0 = 0;
        for (auto& s : segs) {
            int y = s[0], x1 = s[1], x2 = s[2], k = s[3];
            double t = (double) (y - y0) * tree.query();
            if (area + t >= target) {
                return y0 + (target - area) / tree.query();
            }
            area += t;
            tree.modify(1, d[x1], d[x2] - 1, k);
            y0 = y;
        }

        return 0.0;
    }
};

###go

type Node struct {
l, r   int
cnt    int
length int
}

type SegmentTree struct {
tr   []Node
nums []int
}

func NewSegmentTree(nums []int) *SegmentTree {
n := len(nums) - 1
tr := make([]Node, n<<2)
t := &SegmentTree{tr: tr, nums: nums}
t.build(1, 0, n-1)
return t
}

func (t *SegmentTree) build(u, l, r int) {
t.tr[u].l = l
t.tr[u].r = r
if l != r {
mid := (l + r) >> 1
t.build(u<<1, l, mid)
t.build(u<<1|1, mid+1, r)
}
}

func (t *SegmentTree) modify(u, l, r, k int) {
if l > r {
return
}
if t.tr[u].l >= l && t.tr[u].r <= r {
t.tr[u].cnt += k
} else {
mid := (t.tr[u].l + t.tr[u].r) >> 1
if l <= mid {
t.modify(u<<1, l, r, k)
}
if r > mid {
t.modify(u<<1|1, l, r, k)
}
}
t.pushup(u)
}

func (t *SegmentTree) pushup(u int) {
if t.tr[u].cnt > 0 {
t.tr[u].length = t.nums[t.tr[u].r+1] - t.nums[t.tr[u].l]
} else if t.tr[u].l == t.tr[u].r {
t.tr[u].length = 0
} else {
t.tr[u].length = t.tr[u<<1].length + t.tr[u<<1|1].length
}
}

func (t *SegmentTree) query() int {
return t.tr[1].length
}

func separateSquares(squares [][]int) float64 {
pos := make(map[int]bool)
xs := make([]int, 0)
segs := make([][]int, 0, len(squares)*2)
for _, sq := range squares {
x1, y1, l := sq[0], sq[1], sq[2]
x2, y2 := x1+l, y1+l
if !pos[x1] {
pos[x1] = true
xs = append(xs, x1)
}
if !pos[x2] {
pos[x2] = true
xs = append(xs, x2)
}
segs = append(segs, []int{y1, x1, x2, 1})
segs = append(segs, []int{y2, x1, x2, -1})
}
sort.Slice(segs, func(i, j int) bool { return segs[i][0] < segs[j][0] })
sort.Ints(xs)
tree := NewSegmentTree(xs)
d := make(map[int]int, len(xs))
for i, x := range xs {
d[x] = i
}
area := 0.0
y0 := 0
for _, s := range segs {
y, x1, x2, k := s[0], s[1], s[2], s[3]
area += float64(y-y0) * float64(tree.query())
tree.modify(1, d[x1], d[x2]-1, k)
y0 = y
}
target := area / 2.0
area = 0.0
y0 = 0
for _, s := range segs {
y, x1, x2, k := s[0], s[1], s[2], s[3]
curLen := tree.query()
t := float64(y-y0) * float64(curLen)
if area+t >= target {
return float64(y0) + (target-area)/float64(curLen)
}
area += t
tree.modify(1, d[x1], d[x2]-1, k)
y0 = y
}
return 0.0
}

###ts

class Node {
    l = 0;
    r = 0;
    cnt = 0;
    length = 0;
}

class SegmentTree {
    private tr: Node[];
    private nums: number[];
    constructor(nums: number[]) {
        this.nums = nums;
        const n = nums.length - 1;
        this.tr = Array.from({ length: n << 2 }, () => new Node());
        this.build(1, 0, n - 1);
    }

    private build(u: number, l: number, r: number): void {
        this.tr[u].l = l;
        this.tr[u].r = r;
        if (l !== r) {
            const mid = (l + r) >> 1;
            this.build(u << 1, l, mid);
            this.build((u << 1) | 1, mid + 1, r);
        }
    }

    modify(u: number, l: number, r: number, k: number): void {
        if (l > r) return;
        if (this.tr[u].l >= l && this.tr[u].r <= r) {
            this.tr[u].cnt += k;
        } else {
            const mid = (this.tr[u].l + this.tr[u].r) >> 1;
            if (l <= mid) this.modify(u << 1, l, r, k);
            if (r > mid) this.modify((u << 1) | 1, l, r, k);
        }
        this.pushup(u);
    }

    private pushup(u: number): void {
        if (this.tr[u].cnt > 0) {
            this.tr[u].length = this.nums[this.tr[u].r + 1] - this.nums[this.tr[u].l];
        } else if (this.tr[u].l === this.tr[u].r) {
            this.tr[u].length = 0;
        } else {
            this.tr[u].length = this.tr[u << 1].length + this.tr[(u << 1) | 1].length;
        }
    }

    query(): number {
        return this.tr[1].length;
    }
}

function separateSquares(squares: number[][]): number {
    const xsSet = new Set<number>();
    const segs: number[][] = [];
    for (const [x1, y1, l] of squares) {
        const [x2, y2] = [x1 + l, y1 + l];
        xsSet.add(x1);
        xsSet.add(x2);
        segs.push([y1, x1, x2, 1]);
        segs.push([y2, x1, x2, -1]);
    }
    segs.sort((a, b) => a[0] - b[0]);
    const xs = Array.from(xsSet);
    xs.sort((a, b) => a - b);
    const tree = new SegmentTree(xs);
    const d = new Map<number, number>();
    for (let i = 0; i < xs.length; i++) {
        d.set(xs[i], i);
    }
    let area = 0.0;
    let y0 = 0;
    for (const [y, x1, x2, k] of segs) {
        area += (y - y0) * tree.query();
        tree.modify(1, d.get(x1)!, d.get(x2)! - 1, k);
        y0 = y;
    }
    const target = area / 2.0;
    area = 0.0;
    y0 = 0;
    for (const [y, x1, x2, k] of segs) {
        const curLen = tree.query();
        const t = (y - y0) * curLen;
        if (area + t >= target) {
            return y0 + (target - area) / curLen;
        }
        area += t;
        tree.modify(1, d.get(x1)!, d.get(x2)! - 1, k);
        y0 = y;
    }
    return 0.0;
}

时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是正方形的数量。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈 😄~

每日一题-分割正方形 II🔴

2026年1月14日 00:00

给你一个二维整数数组 squares ,其中 squares[i] = [xi, yi, li] 表示一个与 x 轴平行的正方形的左下角坐标和正方形的边长。

找到一个最小的 y 坐标,它对应一条水平线,该线需要满足它以上正方形的总面积 等于 该线以下正方形的总面积。

答案如果与实际答案的误差在 10-5 以内,将视为正确答案。

注意:正方形 可能会 重叠。重叠区域只 统计一次 

 

示例 1:

输入: squares = [[0,0,1],[2,2,1]]

输出: 1.00000

解释:

任何在 y = 1y = 2 之间的水平线都会有 1 平方单位的面积在其上方,1 平方单位的面积在其下方。最小的 y 坐标是 1。

示例 2:

输入: squares = [[0,0,2],[1,1,1]]

输出: 1.00000

解释:

由于蓝色正方形和红色正方形有重叠区域且重叠区域只统计一次。所以直线 y = 1 将正方形分割成两部分且面积相等。

 

提示:

  • 1 <= squares.length <= 5 * 104
  • squares[i] = [xi, yi, li]
  • squares[i].length == 3
  • 0 <= xi, yi <= 109
  • 1 <= li <= 109
  • 所有正方形的总面积不超过 1015

Lazy 线段树 + 扫描线(Python/Java/C++/Go)

作者 endlesscheng
2025年2月17日 17:23

前置题目850. 矩形面积 II我的题解

首先用 850 题的扫描线方法,求出所有正方形的面积并 $\textit{totArea}$。

然后再次扫描,设扫描线下方的面积和为 $\textit{area}$,那么扫描线上方的面积和为 $\textit{totArea} - \textit{area}$。

题目要求

$$
\textit{area} = \textit{totArea} - \textit{area}
$$

$$
\textit{area}\cdot 2 = \textit{totArea}
$$

设当前扫描线的纵坐标为 $y$,下一个需要经过的正方形上/下边界的纵坐标为 $y'$,被至少一个正方形覆盖的底边长之和为 $\textit{sumLen}$,那么新的面积和为

$$
\textit{area} + \textit{sumLen} \cdot (y'-y)
$$

如果发现

$$
(\textit{area} + \textit{sumLen} \cdot (y'-y))\cdot 2 \ge \textit{totArea}
$$

取等号,解得

$$
y' = y + \dfrac{\textit{totalArea}/2 - \textit{area}}{\textit{sumL}} = y + \dfrac{\textit{totalArea} - \textit{area}\cdot 2}{\textit{sumL}\cdot 2}
$$

即为答案。

编程技巧:把第一次扫描过程中的关键数据 $\textit{area}$ 和 $\textit{sumLen}$ 记录到一个数组中,然后遍历数组(或者二分),这样可以避免跑两遍线段树(空间换时间)。

###py

class Node:
    __slots__ = 'l', 'r', 'min_cover_len', 'min_cover', 'todo'

    def __init__(self):
        self.l = 0
        self.r = 0
        self.min_cover_len = 0  # 区间内被矩形覆盖次数最少的底边长之和
        self.min_cover = 0      # 区间内被矩形覆盖的最小次数
        self.todo = 0           # 子树内的所有节点的 min_cover 需要增加的量,注意这可以是负数


class SegmentTree:
    def __init__(self, xs: List[int]):
        n = len(xs) - 1  # xs.size() 个横坐标有 xs.size()-1 个差值
        self.seg = [Node() for _ in range(2 << (n - 1).bit_length())]
        self.build(xs, 1, 0, n - 1)

    def get_uncovered_length(self) -> int:
        return 0 if self.seg[1].min_cover else self.seg[1].min_cover_len

    # 根据左右儿子的信息,更新当前节点的信息
    def maintain(self, o: int) -> None:
        lo = self.seg[o * 2]
        ro = self.seg[o * 2 + 1]
        mn = min(lo.min_cover, ro.min_cover)
        self.seg[o].min_cover = mn
        # 只统计等于 min_cover 的底边长之和
        self.seg[o].min_cover_len = (lo.min_cover_len if lo.min_cover == mn else 0) + \
                                    (ro.min_cover_len if ro.min_cover == mn else 0)

    # 仅更新节点信息,不下传懒标记 todo
    def do(self, o: int, v: int) -> None:
        self.seg[o].min_cover += v
        self.seg[o].todo += v

    # 下传懒标记 todo
    def spread(self, o: int) -> None:
        v = self.seg[o].todo
        if v:
            self.do(o * 2, v)
            self.do(o * 2 + 1, v)
            self.seg[o].todo = 0

    # 建树
    def build(self, xs: List[int], o: int, l: int, r: int) -> None:
        self.seg[o].l = l
        self.seg[o].r = r
        if l == r:
            self.seg[o].min_cover_len = xs[l + 1] - xs[l]
            return
        m = (l + r) // 2
        self.build(xs, o * 2, l, m)
        self.build(xs, o * 2 + 1, m + 1, r)
        self.maintain(o)

    # 区间更新
    def update(self, o: int, l: int, r: int, v: int) -> None:
        if l <= self.seg[o].l and self.seg[o].r <= r:
            self.do(o, v)
            return
        self.spread(o)
        m = (self.seg[o].l + self.seg[o].r) // 2
        if l <= m:
            self.update(o * 2, l, r, v)
        if m < r:
            self.update(o * 2 + 1, l, r, v)
        self.maintain(o)


# 代码逻辑同 850 题,增加一个 records 数组记录关键数据
class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        xs = []
        events = []
        for lx, y, l in squares:
            rx = lx + l
            xs.append(lx)
            xs.append(rx)
            events.append((y, lx, rx, 1))
            events.append((y + l, lx, rx, -1))

        # 排序,方便离散化
        xs = sorted(set(xs))

        # 初始化线段树
        t = SegmentTree(xs)

        # 模拟扫描线从下往上移动
        events.sort(key=lambda e: e[0])
        records = []
        tot_area = 0
        for (y, lx, rx, delta), e2 in pairwise(events):
            l = bisect_left(xs, lx)  # 离散化
            r = bisect_left(xs, rx) - 1  # r 对应着 xs[r] 与 xs[r+1]=rx 的差值
            t.update(1, l, r, delta)  # 更新被 [lx, rx] 覆盖的次数
            sum_len = xs[-1] - xs[0] - t.get_uncovered_length()  # 减去没被矩形覆盖的长度
            records.append((tot_area, sum_len))  # 记录关键数据
            tot_area += sum_len * (e2[0] - y)  # 新增面积 = 被至少一个矩形覆盖的底边长之和 * 矩形高度

        # 二分找最后一个 < tot_area / 2 的面积
        i = bisect_left(records, tot_area, key=lambda r: r[0] * 2) - 1
        area, sum_len = records[i]
        return events[i][0] + (tot_area - area * 2) / (sum_len * 2)

###java

class SegmentTree {
    private final int n;
    private final int[] minCoverLen; // 区间内被矩形覆盖次数最少的底边长之和
    private final int[] minCover;    // 区间内被矩形覆盖的最小次数
    private final int[] todo;        // 子树内的所有节点的 minCover 需要增加的量,注意这可以是负数

    public SegmentTree(int[] xs) {
        n = xs.length - 1; // xs.length 个横坐标有 xs.length-1 个差值
        int size = 2 << (32 - Integer.numberOfLeadingZeros(n - 1));
        minCoverLen = new int[size];
        minCover = new int[size];
        todo = new int[size];
        build(xs, 1, 0, n - 1);
    }

    public void update(int l, int r, int v) {
        update(1, 0, n - 1, l, r, v);
    }

    public int getUncoveredLength() {
        return minCover[1] == 0 ? minCoverLen[1] : 0;
    }

    // 根据左右儿子的信息,更新当前节点的信息
    private void maintain(int o) {
        int mn = Math.min(minCover[o * 2], minCover[o * 2 + 1]);
        minCover[o] = mn;
        // 只统计等于 mn 的底边长之和
        minCoverLen[o] = (minCover[o * 2] == mn ? minCoverLen[o * 2] : 0) +
                (minCover[o * 2 + 1] == mn ? minCoverLen[o * 2 + 1] : 0);
    }

    // 仅更新节点信息,不下传懒标记 todo
    private void do_(int o, int v) {
        minCover[o] += v;
        todo[o] += v;
    }

    // 下传懒标记 todo
    private void spread(int o) {
        if (todo[o] != 0) {
            do_(o * 2, todo[o]);
            do_(o * 2 + 1, todo[o]);
            todo[o] = 0;
        }
    }

    // 建树
    private void build(int[] xs, int o, int l, int r) {
        if (l == r) {
            minCoverLen[o] = xs[l + 1] - xs[l];
            return;
        }
        int m = (l + r) / 2;
        build(xs, o * 2, l, m);
        build(xs, o * 2 + 1, m + 1, r);
        maintain(o);
    }

    // 区间更新
    private void update(int o, int l, int r, int ql, int qr, int v) {
        if (ql <= l && r <= qr) {
            do_(o, v);
            return;
        }
        spread(o);
        int m = (l + r) / 2;
        if (ql <= m) {
            update(o * 2, l, m, ql, qr, v);
        }
        if (m < qr) {
            update(o * 2 + 1, m + 1, r, ql, qr, v);
        }
        maintain(o);
    }
}

// 代码逻辑同 850 题,增加一个 records 数组记录关键数据
class Solution {
    private record Event(int y, int lx, int rx, int delta) {
    }

    private record Record(long area, int sumLen) {
    }

    public double separateSquares(int[][] squares) {
        int n = squares.length * 2;
        int[] xs = new int[n];
        Event[] events = new Event[n];
        n = 0;
        for (int[] sq : squares) {
            int lx = sq[0];
            int y = sq[1];
            int l = sq[2];
            int rx = lx + l;
            xs[n] = lx;
            xs[n + 1] = rx;
            events[n++] = new Event(y, lx, rx, 1);
            events[n++] = new Event(y + l, lx, rx, -1);
        }

        // 排序,方便离散化
        Arrays.sort(xs);

        // 初始化线段树
        SegmentTree t = new SegmentTree(xs);

        // 模拟扫描线从下往上移动
        Arrays.sort(events, (a, b) -> a.y - b.y);
        Record records[] = new Record[n - 1];
        long totArea = 0;
        for (int i = 0; i < n - 1; i++) {
            Event e = events[i];
            int l = Arrays.binarySearch(xs, e.lx); // 离散化
            int r = Arrays.binarySearch(xs, e.rx) - 1; // r 对应着 xs[r] 与 xs[r+1]=rx 的差值
            t.update(l, r, e.delta); // 更新被 [lx, rx] 覆盖的次数
            int sumLen = xs[n - 1] - xs[0] - t.getUncoveredLength(); // 减去没被矩形覆盖的长度
            records[i] = new Record(totArea, sumLen);
            totArea += (long) sumLen * (events[i + 1].y - e.y); // 新增面积 = 被至少一个矩形覆盖的底边长之和 * 矩形高度
        }

        // 找最后一个 < totArea / 2 的面积
        int i = 0;
        while (i < n - 1 && records[i].area * 2 < totArea) {
            i++;
        }
        i--;
        return events[i].y + (totArea - records[i].area * 2) / (records[i].sumLen * 2.0);
    }
}

###cpp

class SegmentTree {
public:
    SegmentTree(vector<int>& xs) {
        unsigned n = xs.size() - 1; // xs.size() 个横坐标有 xs.size()-1 个差值
        seg.resize(2 << bit_width(n - 1));
        build(xs, 1, 0, n - 1);
    }

    void update(int l, int r, int v) {
        update(1, l, r, v);
    }

    int get_uncovered_length() {
        return seg[1].min_cover ? 0 : seg[1].min_cover_len;
    }

private:
    struct Node {
        int l, r;
        int min_cover_len = 0; // 区间内被矩形覆盖次数最少的底边长之和
        int min_cover = 0;     // 区间内被矩形覆盖的最小次数
        int todo = 0;          // 子树内的所有节点的 min_cover 需要增加的量,注意这可以是负数
    };

    vector<Node> seg;

    // 根据左右儿子的信息,更新当前节点的信息
    void maintain(int o) {
        Node& lo = seg[o * 2];
        Node& ro = seg[o * 2 + 1];
        int mn = min(lo.min_cover, ro.min_cover);
        seg[o].min_cover = mn;
        // 只统计等于 min_cover 的底边长之和
        seg[o].min_cover_len = (lo.min_cover == mn ? lo.min_cover_len : 0) +
                               (ro.min_cover == mn ? ro.min_cover_len : 0);
    }

    // 仅更新节点信息,不下传懒标记 todo
    void do_(int o, int v) {
        seg[o].min_cover += v;
        seg[o].todo += v;
    }

    // 下传懒标记 todo
    void spread(int o) {
        int& v = seg[o].todo;
        if (v != 0) {
            do_(o * 2, v);
            do_(o * 2 + 1, v);
            v = 0;
        }
    }

    // 建树
    void build(vector<int>& xs, int o, int l, int r) {
        seg[o].l = l;
        seg[o].r = r;
        if (l == r) {
            seg[o].min_cover_len = xs[l + 1] - xs[l];
            return;
        }
        int m = (l + r) / 2;
        build(xs, o * 2, l, m);
        build(xs, o * 2 + 1, m + 1, r);
        maintain(o);
    }

    // 区间更新
    void update(int o, int l, int r, int v) {
        if (l <= seg[o].l && seg[o].r <= r) {
            do_(o, v);
            return;
        }
        spread(o);
        int m = (seg[o].l + seg[o].r) / 2;
        if (l <= m) {
            update(o * 2, l, r, v);
        }
        if (m < r) {
            update(o * 2 + 1, l, r, v);
        }
        maintain(o);
    }
};

// 代码逻辑同 850 题,增加一个 records 数组记录关键数据
class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        vector<int> xs;
        struct Event { int y, lx, rx, delta; };
        vector<Event> events;
        for (auto& sq : squares) {
            int lx = sq[0], y = sq[1], l = sq[2];
            int rx = lx + l;
            xs.push_back(lx);
            xs.push_back(rx);
            events.emplace_back(y, lx, rx, 1);
            events.emplace_back(y + l, lx, rx, -1);
        }

        // 排序去重,方便离散化
        ranges::sort(xs);
        xs.erase(ranges::unique(xs).begin(), xs.end());

        // 初始化线段树
        SegmentTree t(xs);

        // 模拟扫描线从下往上移动
        ranges::sort(events, {}, &Event::y);
        vector<pair<long long, int>> records(events.size() - 1);
        long long tot_area = 0;
        for (int i = 0; i + 1 < events.size(); i++) {
            auto& [y, lx, rx, delta] = events[i];
            int l = ranges::lower_bound(xs, lx) - xs.begin(); // 离散化
            int r = ranges::lower_bound(xs, rx) - xs.begin() - 1; // r 对应着 xs[r] 与 xs[r+1]=rx 的差值
            t.update(l, r, delta); // 更新被 [lx, rx] 覆盖的次数
            int sum_len = xs.back() - xs[0] - t.get_uncovered_length(); // 减去没被矩形覆盖的长度
            records[i] = {tot_area, sum_len};
            tot_area += 1LL * sum_len * (events[i + 1].y - y); // 新增面积 = 被至少一个矩形覆盖的底边长之和 * 矩形高度
        }

        // 二分找最后一个 < tot_area / 2 的面积
        int i = ranges::lower_bound(records, tot_area, {}, [](auto& p) { return p.first * 2; }) - records.begin() - 1;
        auto [area, sum_len] = records[i];
        return events[i].y + (tot_area - area * 2) / (sum_len * 2.0);
    }
};

###go

// 线段树每个节点维护一段横坐标区间 [lx, rx]
type seg []struct {
    l, r        int
    minCoverLen int // 区间内被矩形覆盖次数最少的底边长之和
    minCover    int // 区间内被矩形覆盖的最小次数
    todo        int // 子树内的所有节点的 minCover 需要增加的量,注意这可以是负数
}

// 根据左右儿子的信息,更新当前节点的信息
func (t seg) maintain(o int) {
    lo, ro := &t[o<<1], &t[o<<1|1]
    mn := min(lo.minCover, ro.minCover)
    t[o].minCover = mn
    t[o].minCoverLen = 0
    if lo.minCover == mn { // 只统计等于 minCover 的底边长之和
        t[o].minCoverLen = lo.minCoverLen
    }
    if ro.minCover == mn {
        t[o].minCoverLen += ro.minCoverLen
    }
}

// 仅更新节点信息,不下传懒标记 todo
func (t seg) do(o, v int) {
    t[o].minCover += v
    t[o].todo += v
}

// 下传懒标记 todo
func (t seg) spread(o int) {
    v := t[o].todo
    if v != 0 {
        t.do(o<<1, v)
        t.do(o<<1|1, v)
        t[o].todo = 0
    }
}

// 建树
func (t seg) build(xs []int, o, l, r int) {
    t[o].l, t[o].r = l, r
    t[o].todo = 0
    if l == r {
        t[o].minCover = 0
        t[o].minCoverLen = xs[l+1] - xs[l]
        return
    }
    m := (l + r) >> 1
    t.build(xs, o<<1, l, m)
    t.build(xs, o<<1|1, m+1, r)
    t.maintain(o)
}

// 区间更新
func (t seg) update(o, l, r, v int) {
    if l <= t[o].l && t[o].r <= r {
        t.do(o, v)
        return
    }
    t.spread(o)
    m := (t[o].l + t[o].r) >> 1
    if l <= m {
        t.update(o<<1, l, r, v)
    }
    if m < r {
        t.update(o<<1|1, l, r, v)
    }
    t.maintain(o)
}

// 代码逻辑同 850 题,增加一个 records 数组记录关键数据
func separateSquares(squares [][]int) float64 {
    m := len(squares) * 2
    xs := make([]int, 0, m)
    type event struct{ y, lx, rx, delta int }
    events := make([]event, 0, m)
    for _, sq := range squares {
        lx, y, l := sq[0], sq[1], sq[2]
        rx := lx + l
        xs = append(xs, lx, rx)
        events = append(events, event{y, lx, rx, 1}, event{y + l, lx, rx, -1})
    }

    // 排序去重,方便离散化
    slices.Sort(xs)
    xs = slices.Compact(xs)

    // 初始化线段树
    n := len(xs) - 1 // len(xs) 个横坐标有 len(xs)-1 个差值
    t := make(seg, 2<<bits.Len(uint(n-1)))
    t.build(xs, 1, 0, n-1)

    // 模拟扫描线从下往上移动
    slices.SortFunc(events, func(a, b event) int { return a.y - b.y })
    type pair struct{ area, sumLen int }
    records := make([]pair, m-1)
    totArea := 0
    for i, e := range events[:m-1] {
        l := sort.SearchInts(xs, e.lx)
        r := sort.SearchInts(xs, e.rx) - 1 // 注意 r 对应着 xs[r] 与 xs[r+1]=e.rx 的差值
        t.update(1, l, r, e.delta)         // 更新被 [e.lx, e.rx] 覆盖的次数
        sumLen := xs[len(xs)-1] - xs[0]    // 总的底边长度
        if t[1].minCover == 0 {            // 需要去掉没被矩形覆盖的长度
            sumLen -= t[1].minCoverLen
        }
        records[i] = pair{totArea, sumLen} // 记录关键数据
        totArea += sumLen * (events[i+1].y - e.y) // 新增面积 = 被至少一个矩形覆盖的底边长之和 * 矩形高度
    }

    // 二分找最后一个 < totArea / 2 的面积
    i := sort.Search(m-1, func(i int) bool { return records[i].area*2 >= totArea }) - 1
    return float64(events[i].y) + float64(totArea-records[i].area*2)/float64(records[i].sumLen*2)
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log n)$,其中 $n$ 是 $\textit{squares}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

更多相似题目,见下面数据结构题单中的「§8.4 Lazy 线段树」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

扫描线 & 线段树,详解矩形面积并

作者 tsreaper
2025年2月16日 14:01

解法:扫描线 & 线段树

我们先把所有正方形并集的面积求出来。这是非常经典的扫描线模板题:矩形面积并。

graph.png

我们以上图的三个矩形为例,讲述如何求它们面积的并。我们对每个矩形的上边界和下边界都画一条水平线,可以看出,相邻水平线之间,矩形的横截长度都是一样的。因此,相邻水平线之间矩形面积的并,就等于横截长度乘以水平线的高度差。问题转为如何求矩形的横截长度。

我们来看相邻的水平线之间,矩形的横截长度发生了什么变化。

  • 在 $y = 1$ 时,横截长度只是区间 $[1, 6]$ 的长度,即 $5$。所以 $y = 1$ 到 $y = 3$ 之间的面积是 $(3 - 1) \times 5 = 10$。
  • 到了 $y = 3$ 时,由于新矩形的加入,区间数量增加了一个,变成 $[1, 6] \cup [9, 14]$ 的长度,即 $10$。所以 $y = 3$ 到 $y = 4$ 之间的面积是 $(4 - 3) \times 10 = 10$。
  • 到了 $y = 4$ 时,由于新矩形的加入,区间数量又增加了一个,变成 $[1, 6] \cup [4, 11] \cup [9, 14] = [1, 14]$ 的长度,即 $13$。所以 $y = 4$ 到 $y = 7$ 之间的面积是 $(7 - 4) \times 13 = 39$。
  • 到了 $y = 7$ 时,由于一个矩形的退出,区间的数量减少了一个,变成 $[1, 6] \cup [9, 14]$ 的长度,即 $10$。所以 $y = 7$ 到 $y = 8$ 之间的面积是 $(8 - 7) \times 10 = 10$。
  • 到了 $y = 8$ 时,由于一个矩形的退出,区间的数量又减少了一个,变成 $[1, 6]$ 的长度,即 $5$。所以 $y = 8$ 到 $y = 10$ 之间的面积是 $(10 - 8) \times 5 = 10$。

所以矩形面积并为 $10 + 10 + 39 + 10 + 10 = 79$。

从上面的例子可以看出,横截长度的变化,其实就是维护一个区间的集合。每次我要加入或删除一个区间,然后求区间并集的长度。

$n$ 个区间的端点只有 $2n$ 种,比如上面的例子中,$3$ 个区间的端点只有 $x = 1, 4, 6, 9, 11, 14$ 这 $6$ 种。因此我们可以把相邻端点看成一个小区间,这样任何的区间就只会由若干个不相交(不计端点相交,因为对长度不影响)的小区间构成。比如上面的例子中,我们有 $5$ 个小区间 $a_1 = [1, 4], a_2 = [4, 6], a_3 = [6, 9], a_4 = [9, 11], a_5 = [11, 14]$。那么 $[1, 6] = a_1 \cup a_2$,$[4, 11] = a_2 \cup a_3 \cup a_4$,$[9, 14] = a_4 \cup a_5$。

因为我们只对区间的长度有兴趣,所以我们直接把每个区间转为长度,例如 $a_1 = 3, a_2 = 2, \cdots$。这样我们就成功把区间问题转为了序列问题:给定 $(2n - 1)$ 个数 $a_1, a_2, \cdots, a_{2n - 1}$,其中第 $i$ 个数被覆盖了 $b_i$ 次,一开始 $b_i = 0$。每次操作会选择一个区间 $[l, r]$,让 $l \le i \le r$ 的 $b_i$ 都增加或减少 $1$。每次操作后,求 $\sum\limits_{b_i \ne 0} a_i$。

区间修改 + 区间查询,大家可能会很快想到用线段树来维护。可是对于一个节点,满足 $b_i \ne 0$ 的 $a_i$ 之和很难维护。比如一个节点代表了下标 $[1, 4]$,目前有 $b_1 = b_2 = b_4 = 1$,$b_3 = 2$。如果我对该节点进行了 $-1$ 操作,我怎么知道只有 $b_1$,$b_2$ 和 $b_4$ 变成了 $0$,而 $b_3$ 还不是 $0$ 呢?

我们发现,直接维护的困难在于:一次操作对于同一节点内的每个元素可能有不同的影响。所以我们要换一种维护方式,只维护能将所有元素一视同仁的值。

对于线段树的一个节点,我们维护:$b_i$ 的最小值 $x$,以及满足 $b_i = x$ 的 $a_i$ 之和 $s$。为什么这样维护可以呢?因为对节点进行 $+v$ 操作,只会让 $x$ 增加 $v$,而不影响 $s$ 的值。那 $\sum\limits_{b_i \ne 0} a_i$ 怎么求呢?设节点对应的元素总和为 $t$,那么当 $b_i = 0$ 时,答案就是 $t - s$,否则答案就是 $t$。

因为操作涉及到了区间修改 + 区间查询,我们可以用懒标记下推线段树完成操作的维护。当然,这种线段树的维护方式不是最高效的,事实上还可以利用每次添加区间的操作一定会被撤回的性质,写出一种不需要懒标记下推,非常特殊的线段树,见 leetcode 850. 矩形面积 II 的官方题解。这里只介绍懒标记下推线段树的维护方法,是因为懒标记下推是最为常用,最为“标准”的线段树,比较好理解。

这样我们就成功维护了横截长度。最后的问题是找到最下面的水平线,使得水平线下方的面积并减去水平线上方的面积并大等于 $0$。

假设我们枚举到水平线 $y = t_1$ 和 $y = t_2$ 之间时,横截长度为 $l$,且到了 $y = t_2$ 之后,这个差值 $d \ge 0$。此时如果水平线往下移 $\Delta t$,这个差值会减少 $2l\Delta t$,因此答案就是 $t - \frac{d}{2l}$。

复杂度 $\mathcal{O}(n\log n)$。

参考代码(c++)

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        int n = squares.size(), m = 0;
        map<int, int> mp;
        for (auto &sq : squares) mp[sq[0]] = mp[sq[0] + sq[2]] = 1;
        for (auto &p : mp) p.second = m++;
        int A[m];
        for (auto &p : mp) A[p.second] = p.first;

        struct Node {
            // mn:当前节点的最小覆盖数
            // len:满足覆盖数 = 最小覆盖数的 A[i] 之和
            // lazy:加法的懒标记
            int mn, len, lazy;

            // 对节点的覆盖数整个增加 qv,只影响 mn,不影响 len
            void add(int qv) {
                mn += qv;
                lazy += qv;
            }
        } tree[m * 4 + 5];

        // 线段树两个子节点合并
        auto merge = [&](Node &nl, Node &nr) {
            int mn = min(nl.mn, nr.mn);
            return Node {
                mn,
                (nl.mn == mn ? nl.len : 0) + (nr.mn == mn ? nr.len : 0),
                0
            };
        };

        // 线段树模板开始

        // 建树
        auto build = [&](this auto &&self, int id, int l, int r) -> void {
            if (l == r) tree[id] = { 0, A[r] - A[r - 1], 0 };
            else {
                int nxt = id << 1, mid = (l + r) >> 1;
                self(nxt, l, mid); self(nxt | 1, mid + 1, r);
                tree[id] = merge(tree[nxt], tree[nxt | 1]);
            }
        };

        // 懒标记下推
        auto down = [&](int id) {
            if (tree[id].lazy == 0) return;
            int nxt = id << 1;
            tree[nxt].add(tree[id].lazy);
            tree[nxt | 1].add(tree[id].lazy);
            tree[id].lazy = 0;
        };

        // 区间加减覆盖次数
        auto modify = [&](this auto &&self, int id, int l, int r, int ql, int qr, int qv) -> void {
            if (ql <= l && r <= qr) tree[id].add(qv);
            else {
                down(id);
                int nxt = id << 1, mid = (l + r) >> 1;
                if (ql <= mid) self(nxt, l, mid, ql, qr, qv);
                if (qr > mid) self(nxt | 1, mid + 1, r, ql, qr, qv);
                tree[id] = merge(tree[nxt], tree[nxt | 1]);
            }
        };

        // 线段树模板结束

        // 把正方形的上下边界取出来
        vector<array<int, 4>> vec;
        for (auto &sq : squares) {
            vec.push_back({sq[1], mp[sq[0]] + 1, mp[sq[0] + sq[2]], 1});
            vec.push_back({sq[1] + sq[2], mp[sq[0]] + 1, mp[sq[0] + sq[2]], -1});
        }
        sort(vec.begin(), vec.end());

        // 求总的面积并
        long long tot = 0;
        build(1, 1, m - 1);
        for (int i = 0; i + 1 < vec.size(); i++) {
            // 考虑水平线 y = vec[i][0] 和 y = vec[i + 1][0] 之间的情况
            modify(1, 1, m - 1, vec[i][1], vec[i][2], vec[i][3]);
            // 求横截长度
            int len = A[m - 1] - A[0];
            // 如果最小覆盖数是 0,那么扣掉相应的长度
            if (tree[1].mn == 0) len -= tree[1].len;
            // 面积 = 横截长度 * 高度差
            tot += 1LL * len * (vec[i + 1][0] - vec[i][0]);
        }

        long long now = 0;
        build(1, 1, m - 1);
        for (int i = 0; i + 1 < vec.size(); i++) {
            modify(1, 1, m - 1, vec[i][1], vec[i][2], vec[i][3]);
            int len = A[m - 1] - A[0];
            if (tree[1].mn == 0) len -= tree[1].len;
            now += 1LL * len * (vec[i + 1][0] - vec[i][0]);
            // delta 非负了,套公式
            long long det = now - (tot - now);
            if (det >= 0) return vec[i + 1][0] - 0.5 * det / len;
        };
        return -1;
    }
};

两次扫描线解法(跑两次850题)

作者 vclip
2025年2月16日 13:08

这里属于会 850. 矩形面积 II 题就能秒。

如果不动 850 题的代码,直接调用 850 题的话,可以采用二分 $y$ 坐标,然后截断正方形,去除在当前 $y$ 值以上的部分,再用 850 题的方法求面积判断是否达到总面积一半解决。

但实际上也可以两次扫描线解决:先用 850 题的方法求出总面积 $S$,然后用垂直 $y$ 轴的扫描线扫,扫到在 $y_1$ 以下的正方形面积 $S_1$ 小于一半,在 $y_2$ 以下的正方形面积 $S_2$ 大于等于一半时,答案即为 $y_1+\dfrac{S/2-S_1}{w}$,其中 $w$ 为在 $y_1$ 和 $y_2$ 之间的正方形截面长度(即 850 题中线段树维护的长度)。

代码

###C++

class Solution {
public:
    struct Edge {
        int y;
        bool state : 1;
        int x0 : 31, x1;
        auto operator<=>(Edge other) const {
            return y <=> other.y;
        }
    };

    struct Node {
        int cnt, len, sum;
    };

    double separateSquares(const vector<vector<int>>& squares) {
        const int n = squares.size();
        vector<int> ord;
        vector<Edge> edges;
        for (const auto& e : squares) {
            ord.push_back(e[0]);
            ord.push_back(e[0] + e[2]);
            edges.push_back({e[1], true, e[0], e[0] + e[2]});
            edges.push_back({e[1] + e[2], false, e[0], e[0] + e[2]});
        }
        ranges::sort(ord);
        ord.erase(unique(ord.begin(), ord.end()), ord.end());
        const int c = ord.size() - 1;
        for (auto& e : edges) {
            e.x0 = ranges::lower_bound(ord, e.x0) - ord.begin();
            e.x1 = ranges::lower_bound(ord, e.x1) - ord.begin();
        }
        sort(edges.begin(), edges.end());
        const int h = c > 1 ? 32 - __builtin_clz(c - 1) : 0;
        const int m = 1 << h, M = m + c;
        vector<Node> tree(M);
        for (int i = 0;i < c;++i)
            tree[m + i] = {0, ord[i + 1] - ord[i], 0};
        for (int i = 1;i <= h;++i)
            for (int j = 0;j < (c >> i);++j)
                tree[(m >> i) + j] = {0, ord[(j + 1) << i] - ord[j << i], 0};
        const auto update_leaf = [&] (int p, int d) {
            tree[p].sum = (tree[p].cnt += d) > 0 ? tree[p].len : 0;
        };
        const auto update_branch = [&] (int p, int d) {
            tree[p].sum = (tree[p].cnt += d) > 0 ? tree[p].len : tree[2 * p].sum + tree[2 * p + 1].sum;
        };
        const auto update = [&] (int l, int r, int d) {
            const int L = l += m, R = r += m;
            if (l & 1) update_leaf(l++, d);
            if (r & 1) update_leaf(--r, d);
            for (l >>= 1, r >>= 1;l < r;l >>= 1, r >>= 1) {
                if (l & 1) update_branch(l++, d);
                if (r & 1) update_branch(--r, d);
            }
            for (int i = L >> 1, j = M >> 1;i < j;i >>= 1, j >>= 1)
                update_branch(i, 0);
            for (int i = R >> 1, j = M >> 1;i < j;i >>= 1, j >>= 1)
                update_branch(i, 0);
        };
        const auto query = [&] {
            int ans = 0;
            for (int i = m, j = M;i < j;i >>= 1, j >>= 1) {
                if (i & 1) ans += tree[i++].sum;
                if (j & 1) ans += tree[--j].sum;
            }
            return ans;
        };
        long long total = 0;
        {
            int pre = 0;
            for (const auto [x, state, y0, y1] : edges) {
                total += 1ll * query() * (x - pre);
                update(y0, y1, 2 * state - 1);
                pre = x;
            }
        }
        int pre = 0;
        long long sum = 0;
        for (const auto [x, state, y0, y1] : edges) {
            const int w = query();
            const long long nsum = sum + 1ll * w * (x - pre);
            if (2 * nsum >= total) return pre + (total - 2 * sum) / (2.0 * w);
            update(y0, y1, 2 * state - 1);
            sum = nsum;
            pre = x;
        }
        return -1;
    }
};
昨天以前LeetCode 每日一题题解

分割正方形 I

2026年1月8日 12:04

方法一:二分查找

思路与算法

设数组 $\textit{squares}$ 的长度为 $n$,数组中的每个元素为 $(x_i,y_i,l_i)$,此时所有正方形的面积之和则为:

$$
\textit{totalArea} = \sum_{i=0}^{n-1} l_i^2
$$

根据题目要求,我们需要找到一个分割线 $y$,使得所有在 $y$ 以上的正方形的面积之和等于所有在 $y$ 以下的正方形的面积之和。设 $y$ 以下的正方形面积为 $\textit{area}_y$,此时需要满足:

$$
\textit{area}_y \cdot 2= \textit{totalArea}
$$

随着分割线 $y$ 的增大,$\textit{area}_y$ 单调不减,因此可以使用二分查找。具体地,我们可以二分查找找到最小的 $y$ 的值,使得在 $y$ 以下的正方形的面积满足:

$$
\textit{area}_y \cdot 2 \ge \textit{totalArea}
$$

假设给定的 $y$ 的值,如果给定正方形 $(x_i,y_i,l_i)$,满足 $y_i < y$,那么这个正方形在 $y$ 以下,否则在 $y$ 以上。此时该正方形在 $y$ 以下的面积则为:

$$
\textit{area} = l_i \cdot \min(y - y_i, l_i)
$$

我们可以根据这个性质来计算在 $y$ 以下的所有正方形的面积之和:

$$
\textit{area}y = \sum{i=0}^{n-1} l_i \cdot \max(0,\min(y - y_i, l_i))
$$

由于计算面积时存在精度问题,题目要求与实际答案的误差在 $10^{-5}$ 以内。我们需要在二分查找时使用 $10^{-5}$ 作为精度,即可以使用上限与下限的差距不超过 $\text{10}^{-5}$ 作为二分查找的终止条件:

$$
\textit{hi} - \textit{lo} \le 10^{-5}
$$

我们通过二分查找找到最小的 $y$ 值即为答案。

细节

我们可以分析二分查找的次数上限,设初始二分区间长度为 $L$,每二分一次,二分区间长度减半。要至少减半到 $10^{-5}$ 才能满足题目的误差要求。设循环次数为 $k$,我们有:

$$
\dfrac{L}{2^k} \le 10^{-5}
$$

解得:

$$
k \ge \log_2 (L \cdot 10^5)
$$

在本题的数据范围下,$0 \le L \le 10^9$,此时 $k \ge \log_2 (L \cdot 10^5) \ge \log_2 (10^{14}) = 14 \log_2 (10) \approx 46.506993328423076$。二分查找的次数上限为 $47$ 次。

代码

###C++

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        double max_y = 0, total_area = 0;
        for (auto& sq : squares) {
            int y = sq[1], l = sq[2];
            total_area += (double)l * l;
            max_y = max(max_y, (double)(y + l));
        }
        
        auto check = [&](double limit_y) -> bool {
            double area = 0;
            for (auto& sq : squares) {
                int y = sq[1], l = sq[2];
                if (y < limit_y) {
                    area += l * min(limit_y - y, (double)l);
                }
            }
            return area >= total_area / 2;
        };
        
        double lo = 0, hi = max_y;
        double eps = 1e-5;
        while (abs(hi - lo) > eps) {
            double mid = (hi + lo) / 2;
            if (check(mid)) {
                hi = mid;
            } else {
                lo = mid;
            }
        }
        return hi;
    }
};

###Java

class Solution {
    public double separateSquares(int[][] squares) {
        double max_y = 0, total_area = 0;
        for (int[] sq : squares) {
            int y = sq[1], l = sq[2];
            total_area += (double)l * l;
            max_y = Math.max(max_y, (double)(y + l));
        }
        
        double lo = 0, hi = max_y;
        double eps = 1e-5;
        while (Math.abs(hi - lo) > eps) {
            double mid = (hi + lo) / 2;
            if (check(mid, squares, total_area)) {
                hi = mid;
            } else {
                lo = mid;
            }
        }

        return hi;
    }

    private Boolean check(double limit_y, int[][] squares, double total_area) {
        double area = 0;
        for (int[] sq : squares) {
            int y = sq[1], l = sq[2];
            if (y < limit_y) {
                area += (double)l * Math.min(limit_y - y, (double)l);
            }
        }
        return area >= total_area / 2;
    }
}

###C#

public class Solution {
    public double SeparateSquares(int[][] squares) {
        double max_y = 0, total_area = 0;
        foreach (int[] sq in squares) {
            int y = sq[1], l = sq[2];
            total_area += (double)l * l;
            max_y = Math.Max(max_y, (double)(y + l));
        }
        
        double lo = 0, hi = max_y;
        double eps = 1e-5;
        while (Math.Abs(hi - lo) > eps) {
            double mid = (hi + lo) / 2;
            if (Check(mid, squares, total_area)) {
                hi = mid;
            } else {
                lo = mid;
            }
        }

        return hi;
    }

    private bool Check(double limit_y, int[][] squares, double total_area) {
        double area = 0;
        foreach (int[] sq in squares) {
            int y = sq[1], l = sq[2];
            if (y < limit_y) {
                area += (double)l * Math.Min(limit_y - y, (double)l);
            }
        }
        return area >= total_area / 2;
    }
}

###Go

func separateSquares(squares [][]int) float64 {
    max_y, total_area := 0.0, 0.0
    for _, sq := range squares {
        y, l := sq[1], sq[2]
        total_area += float64(l * l)
        if float64(y + l) > max_y {
            max_y = float64(y + l)
        }
    }
    
    check := func(limit_y float64) bool {
        area := 0.0
        for _, sq := range squares {
            y, l := sq[1], sq[2]
            if float64(y) < limit_y {
                overlap := math.Min(limit_y-float64(y), float64(l))
                area += float64(l) * overlap
            }
        }
        
        return area >= total_area / 2.0
    }
    
    lo, hi := 0.0, max_y
    eps := 1e-5
    for math.Abs(hi-lo) > eps {
        mid := (hi + lo) / 2.0
        if check(mid) {
            hi = mid
        } else {
            lo = mid
        }
    }
    return hi
}

###Python

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        max_y, total_area = 0, 0
        for x, y, l in squares:
            total_area += l ** 2
            max_y = max(max_y, y + l)
        
        def check(limit_y):
            area = 0
            for x, y, l in squares:
                if y < limit_y:
                    area += l * min(limit_y - y, l)
            return area >= total_area / 2
        
        lo, hi = 0, max_y
        eps = 1e-5
        while abs(hi - lo) > eps:
            mid = (hi + lo) / 2
            if check(mid):
                hi = mid
            else:
                lo = mid

        return hi

###C

bool check(double limit_y, int** squares, int squaresSize, double total_area) {
    double area = 0.0;

    for (int i = 0; i < squaresSize; i++) {
        int y = squares[i][1];
        int l = squares[i][2];
        if (y < limit_y) {
            area += (double)l * fmin(l, limit_y - y);
        }
    }

    return area >= total_area / 2.0;
}

double separateSquares(int** squares, int squaresSize, int* squaresColSize) {
    double max_y = 0.0, total_area = 0.0;
    for (int i = 0; i < squaresSize; i++) {
        int y = squares[i][1];
        int l = squares[i][2];
        total_area += (double)l * l;
        if (y + l > max_y) {
            max_y = y + l;
        }
    }
    
    double lo = 0.0, hi = max_y;
    double eps = 1e-5;
    while (fabs(hi - lo) > eps) {
        double mid = (hi + lo) / 2.0;
        if (check(mid, squares, squaresSize, total_area)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    return hi;
}

###JavaScript

var separateSquares = function(squares) {
    let max_y = 0, total_area = 0;
    for (const [x, y, l] of squares) {
        total_area += l * l;
        max_y = Math.max(max_y, y + l);
    }
    
    const check = (limit_y) => {
        let area = 0;
        for (const [x, y, l] of squares) {
            if (y < limit_y) {
                area += l * Math.min(limit_y - y, l);
            }
        }
        return area >= total_area / 2;
    };
    
    let lo = 0, hi = max_y;
    const eps = 1e-5;
    while (Math.abs(hi - lo) > eps) {
        const mid = (hi + lo) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    return hi;
};

###TypeScript

function separateSquares(squares: number[][]): number {
    let max_y = 0, total_area = 0;
    for (const [x, y, l] of squares) {
        total_area += l * l;
        max_y = Math.max(max_y, y + l);
    }
    
    const check = (limit_y: number): boolean => {
        let area = 0;
        for (const [x, y, l] of squares) {
            if (y < limit_y) {
                area += l * Math.min(limit_y - y, l);
            }
        }
        return area >= total_area / 2;
    };
    
    let lo = 0, hi = max_y;
    const eps = 1e-5;
    while (Math.abs(hi - lo) > eps) {
        const mid = (hi + lo) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    return hi;
}

###Rust

impl Solution {
    pub fn separate_squares(squares: Vec<Vec<i32>>) -> f64 {
        let mut max_y: f64 = 0.0;
        let mut total_area: f64 = 0.0;
        
        for sq in &squares {
            let l = sq[2] as f64;
            total_area += l * l;
            max_y = max_y.max((sq[1] + sq[2]) as f64);
        }
        
        let mut lo = 0.0;
        let mut hi = max_y;
        let eps = 1e-5;
        while (hi - lo).abs() > eps {
            let mid = (hi + lo) / 2.0;
            if Self::check(mid, &squares, total_area) {
                hi = mid;
            } else {
                lo = mid;
            }
        }
        
        hi
    }
    
    fn check(limit_y: f64, squares: &Vec<Vec<i32>>, total_area: f64) -> bool {
        let mut area = 0.0;
        
        for sq in squares {
            let y = sq[1] as f64;
            let l = sq[2] as f64;
            if y < limit_y {
                let overlap = (limit_y - y).min(l);
                area += l * overlap;
            }
        }
        
        area >= total_area / 2.0
    }
}

复杂度分析

  • 时间复杂度:$O(n \log (LU))$,其中 $n$ 是数组 $\textit{squares}$ 的长度,设数组中的每个元素为 $(x_i,y_i,l_i)$,此时 $U = \max(y_i + l_i)$,$L = 10^5$。二分查找每次校验的时间复杂度度为 $O(n)$,二分查找的次数为 $O(\log (LU))$,因此总时间复杂度为 $O(n \log (LU))$。

  • 空间复杂度:$O(1)$。

方法二:扫描线

思路与算法

我们可以参考「扫描线」的解法。首先可以计算出所有正方形的总面积 $\textit{totalArea}$,接着我们从下往上进行扫描,设扫描线 $y = y^{'}$ 下方的覆盖的面积和为 $\textit{area}$,那么扫描线上方的面积和为 $\textit{totalArea}−\textit{area}$。

题目要求 $y = y^{'}$ 下面的面积与上方的面积相等,即:

$$
\textit{area} = \textit{totalArea}− \textit{area}
$$

即:

$$
\textit{area} = \dfrac{\textit{totalArea}}{2}
$$

设当前经过正方形上/下边界的扫描线为 $y = y^{'}$,此时扫面线以下的覆盖面积为 $\textit{area}$;向上移动时下一个需要经过的正方形上/下边界的扫描线为 $y = y^{''}$,此时被正方形覆盖的底边长之和为 $\textit{width}$,则此时在扫面线 $y = y^{''}$ 以下覆盖的面积之和为:

$$
\textit{area} + \textit{width} \cdot (y^{''} - y^{'})
$$

此时当满足:

$$
\textit{area} < \dfrac{\textit{totalArea}}{2} \
\textit{area} + \textit{width} \cdot (y^{''} - y^{'}) \ge \dfrac{\textit{totalArea}}{2}
$$

时,则可以知道目标值 $y$ 一定处于区间 $[y^{'},y^{''}]$。
由于两个扫面线之间的被覆盖区域中所有的矩形的高度相同,扫面线在区间 $[y^{'},y^{''}]$ 移动长度为 $\Delta$ 时,此时被覆盖区域的面积变化即为 $\Delta \cdot \textit{width}$,此时被覆盖的面积只需增加 $\dfrac{\textit{totalArea}}{2} - \textit{area}$,即可满足上下面积相等,此时我们可以直接求出目标值 $y$ 即为:

$$
y = y^{'} + \dfrac{\dfrac{\textit{totalArea}}{2} - \textit{area}}{\textit{width}} = y^{'} + \dfrac{\textit{totalArea} - 2\cdot \textit{area}}{2\cdot\textit{width}}
$$

我们依次遍历每个正方形上/下边界的扫面线,找到目标值返回即可。

代码

###C++

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        long long total_area = 0;
        vector<tuple<int, int, int>> events; 
        for (const auto& sq : squares) {
            int y = sq[1], l = sq[2];
            total_area += (long long)l * l;
            events.emplace_back(y, l, 1);
            events.emplace_back(y + l, l, -1);
        }
        // 按照 y 坐标进行排序
        sort(events.begin(), events.end(), [](const auto& a, const auto& b) {
            return get<0>(a) < get<0>(b);
        });
        
        double covered_width = 0;  // 当前扫描线下所有底边之和
        double curr_area = 0;       // 当前累计面积
        double prev_height = 0;     // 前一个扫描线的高度
        for (const auto &[y, l, delta] : events) {
            int diff = y - prev_height;
            // 两条扫面线之间新增的面积
            double area = covered_width * diff;
            // 如果加上这部分面积超过总面积的一半
            if (2LL * (curr_area + area) >= total_area) {
                return prev_height + (total_area - 2.0 * curr_area) / (2.0 * covered_width);
            }
            // 更新宽度:开始事件加宽度,结束事件减宽度
            covered_width += delta * l;
            curr_area += area;
            prev_height = y;
        }
        
        return 0.0;
    }
};

###Java

class Solution {
    public double separateSquares(int[][] squares) {
        long totalArea = 0;
        List<int[]> events = new ArrayList<>();
        
        for (int[] sq : squares) {
            int y = sq[1], l = sq[2];
            totalArea += (long) l * l;
            events.add(new int[]{y, l, 1});
            events.add(new int[]{y + l, l, -1});
        }
        
        // 按y坐标排序
        events.sort((a, b) -> Integer.compare(a[0], b[0]));
        double coveredWidth = 0;  // 当前扫描线下所有底边之和
        double currArea = 0;      // 当前累计面积
        double prevHeight = 0;    // 前一个扫描线的高度
        
        for (int[] event : events) {
            int y = event[0];
            int l = event[1];
            int delta = event[2];
            
            int diff = y - (int) prevHeight;
            // 两条扫描线之间新增的面积
            double area = coveredWidth * diff;
            // 如果加上这部分面积超过总面积的一半
            if (2L * (currArea + area) >= totalArea) {
                return prevHeight + (totalArea - 2.0 * currArea) / (2.0 * coveredWidth);
            }
            // 更新宽度:开始事件加宽度,结束事件减宽度
            coveredWidth += delta * l;
            currArea += area;
            prevHeight = y;
        }
        
        return 0.0;
    }
}

###C#

public class Solution {
    public double SeparateSquares(int[][] squares) {
        long totalArea = 0;
        List<int[]> events = new List<int[]>();
        
        foreach (var sq in squares) {
            int y = sq[1], l = sq[2];
            totalArea += (long)l * l;
            events.Add(new int[] { y, l, 1 });
            events.Add(new int[] { y + l, l, -1 });
        }
        
        // 按y坐标排序
        events.Sort((a, b) => a[0].CompareTo(b[0]));
        
        double coveredWidth = 0;  // 当前扫描线下所有底边之和
        double currArea = 0;      // 当前累计面积
        double prevHeight = 0;    // 前一个扫描线的高度
        
        foreach (var eventItem in events) {
            int y = eventItem[0];
            int l = eventItem[1];
            int delta = eventItem[2];
            
            int diff = y - (int)prevHeight;
            // 两条扫描线之间新增的面积
            double area = coveredWidth * diff;
            // 如果加上这部分面积超过总面积的一半
            if (2L * (currArea + area) >= totalArea) {
                return prevHeight + (totalArea - 2.0 * currArea) / (2.0 * coveredWidth);
            }
            // 更新宽度:开始事件加宽度,结束事件减宽度
            coveredWidth += delta * l;
            currArea += area;
            prevHeight = y;
        }
        
        return 0.0;
    }
}

###Go

func separateSquares(squares [][]int) float64 {
    var totalArea int64 = 0
    type Event struct {
        y     int
        l     int
        delta int
    }
    events := make([]Event, 0, len(squares)*2)
    
    for _, sq := range squares {
        y, l := sq[1], sq[2]
        totalArea += int64(l) * int64(l)
        events = append(events, Event{y, l, 1})
        events = append(events, Event{y + l, l, -1})
    }
    
    // 按y坐标排序
    sort.Slice(events, func(i, j int) bool {
        return events[i].y < events[j].y
    })
    
    coveredWidth := 0.0  // 当前扫描线下所有底边之和
    currArea := 0.0      // 当前累计面积
    prevHeight := 0.0    // 前一个扫描线的高度
    
    for _, event := range events {
        y, l, delta := event.y, event.l, event.delta
        diff := float64(y) - prevHeight
        // 两条扫描线之间新增的面积
        area := coveredWidth * diff
        // 如果加上这部分面积超过总面积的一半
        if 2.0*(currArea+area) >= float64(totalArea) {
            return prevHeight + (float64(totalArea) - 2.0*currArea) / (2.0 * coveredWidth)
        }
        // 更新宽度:开始事件加宽度,结束事件减宽度
        coveredWidth += float64(delta * l)
        currArea += area
        prevHeight = float64(y)
    }
    
    return 0.0
}

###Python

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        total_area = 0
        events = []
        
        for sq in squares:
            y, l = sq[1], sq[2]
            total_area += l * l
            events.append((y, l, 1))
            events.append((y + l, l, -1))
        
        # 按y坐标排序
        events.sort(key=lambda x: x[0])
        
        covered_width = 0.0  # 当前扫描线下所有底边之和
        curr_area = 0.0      # 当前累计面积
        prev_height = 0.0    # 前一个扫描线的高度
        
        for y, l, delta in events:
            diff = y - prev_height
            # 两条扫描线之间新增的面积
            area = covered_width * diff
            # 如果加上这部分面积超过总面积的一半
            if 2 * (curr_area + area) >= total_area:
                return prev_height + (total_area - 2 * curr_area) / (2 * covered_width)
            # 更新宽度:开始事件加宽度,结束事件减宽度
            covered_width += delta * l
            curr_area += area
            prev_height = y
        
        return 0.0

###C

typedef struct {
    int y;
    int l;
    int delta;
} Event;

int compareEvents(const void* a, const void* b) {
    Event* e1 = (Event*)a;
    Event* e2 = (Event*)b;
    return e1->y - e2->y;
}

double separateSquares(int** squares, int squaresSize, int* squaresColSize) {
    long long totalArea = 0;
    Event* events = malloc(2 * squaresSize * sizeof(Event));
    int eventCount = 0;
    
    for (int i = 0; i < squaresSize; i++) {
        int y = squares[i][1];
        int l = squares[i][2];
        totalArea += (long long)l * l;
        events[eventCount++] = (Event){y, l, 1};
        events[eventCount++] = (Event){y + l, l, -1};
    }
    
    // 按y坐标排序
    qsort(events, eventCount, sizeof(Event), compareEvents);
    
    double coveredWidth = 0.0;  // 当前扫描线下所有底边之和
    double currArea = 0.0;      // 当前累计面积
    double prevHeight = 0.0;    // 前一个扫描线的高度
    
    for (int i = 0; i < eventCount; i++) {
        int y = events[i].y;
        int l = events[i].l;
        int delta = events[i].delta;
        
        int diff = y - (int)prevHeight;
        // 两条扫描线之间新增的面积
        double area = coveredWidth * diff;
        // 如果加上这部分面积超过总面积的一半
        if (2LL * (currArea + area) >= totalArea) {
            double result = prevHeight + (totalArea - 2.0 * currArea) / (2.0 * coveredWidth);
            free(events);
            return result;
        }
        // 更新宽度:开始事件加宽度,结束事件减宽度
        coveredWidth += delta * l;
        currArea += area;
        prevHeight = y;
    }
    
    free(events);
    return 0.0;
}

###JavaScript

var separateSquares = function(squares) {
    let totalArea = 0n;
    const events = [];
    
    for (const sq of squares) {
        const y = sq[1], l = sq[2];
        totalArea += BigInt(l) * BigInt(l);
        events.push([y, l, 1]);
        events.push([y + l, l, -1]);
    }
    
    // 按y坐标排序
    events.sort((a, b) => a[0] - b[0]);
    
    let coveredWidth = 0;  // 当前扫描线下所有底边之和
    let currArea = 0;      // 当前累计面积
    let prevHeight = 0;    // 前一个扫描线的高度
    
    for (const [y, l, delta] of events) {
        const diff = y - prevHeight;
        // 两条扫描线之间新增的面积
        const area = coveredWidth * diff;
        // 如果加上这部分面积超过总面积的一半
        if (2n * BigInt(Math.ceil(currArea + area)) >= totalArea) {
            return prevHeight + (Number(totalArea) - 2.0 * currArea) / (2.0 * coveredWidth);
        }
        // 更新宽度:开始事件加宽度,结束事件减宽度
        coveredWidth += delta * l;
        currArea += area;
        prevHeight = y;
    }
    
    return 0.0;
};

###TypeScript

function separateSquares(squares: number[][]): number {
    let totalArea: bigint = 0n;
    const events: [number, number, number][] = [];
    
    for (const sq of squares) {
        const y = sq[1], l = sq[2];
        totalArea += BigInt(l) * BigInt(l);
        events.push([y, l, 1]);
        events.push([y + l, l, -1]);
    }
    
    // 按y坐标排序
    events.sort((a, b) => a[0] - b[0]);
    
    let coveredWidth: number = 0;  // 当前扫描线下所有底边之和
    let currArea: number = 0;      // 当前累计面积
    let prevHeight: number = 0;    // 前一个扫描线的高度
    
    for (const [y, l, delta] of events) {
        const diff: number = y - prevHeight;
        // 两条扫描线之间新增的面积
        const area: number = coveredWidth * diff;
        // 如果加上这部分面积超过总面积的一半
        if (2n * BigInt(Math.ceil(currArea + area)) >= totalArea) {
            return prevHeight + (Number(totalArea) - 2.0 * currArea) / (2.0 * coveredWidth);
        }
        // 更新宽度:开始事件加宽度,结束事件减宽度
        coveredWidth += delta * l;
        currArea += area;
        prevHeight = y;
    }
    
    return 0.0;
}

###Rust

impl Solution {
    pub fn separate_squares(squares: Vec<Vec<i32>>) -> f64 {
        let mut total_area: i64 = 0;
        let mut events: Vec<(i32, i32, i32)> = Vec::new();
        
        for sq in &squares {
            let y = sq[1];
            let l = sq[2];
            total_area += l as i64 * l as i64;
            events.push((y, l, 1));
            events.push((y + l, l, -1));
        }
        
        // 按y坐标排序
        events.sort_by_key(|&(y, _, _)| y);
        
        let mut covered_width: f64 = 0.0;  // 当前扫描线下所有底边之和
        let mut curr_area: f64 = 0.0;      // 当前累计面积
        let mut prev_height: f64 = 0.0;    // 前一个扫描线的高度
        
        for (y, l, delta) in events {
            let diff = y as f64 - prev_height;
            // 两条扫描线之间新增的面积
            let area = covered_width * diff;
            // 如果加上这部分面积超过总面积的一半
            if 2.0 * (curr_area + area) >= total_area as f64 {
                return prev_height + (total_area as f64 - 2.0 * curr_area) / (2.0 * covered_width);
            }
            // 更新宽度:开始事件加宽度,结束事件减宽度
            covered_width += (delta * l) as f64;
            curr_area += area;
            prev_height = y as f64;
        }
        
        0.0
    }
}

复杂度分析

  • 时间复杂度:$O(n \log n)$,其中 $n$ 是数组 $\textit{squares}$ 的长度。排序需要的时间复杂度为 $O(n \log n)$。

  • 空间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{squares}$ 的长度。存储扫面线高度,需要的空间为 $O(n)$。

每日一题-分割正方形 I🟡

2026年1月13日 00:00

给你一个二维整数数组 squares ,其中 squares[i] = [xi, yi, li] 表示一个与 x 轴平行的正方形的左下角坐标和正方形的边长。

找到一个最小的 y 坐标,它对应一条水平线,该线需要满足它以上正方形的总面积 等于 该线以下正方形的总面积。

答案如果与实际答案的误差在 10-5 以内,将视为正确答案。

注意:正方形 可能会 重叠。重叠区域应该被 多次计数 

 

示例 1:

输入: squares = [[0,0,1],[2,2,1]]

输出: 1.00000

解释:

任何在 y = 1y = 2 之间的水平线都会有 1 平方单位的面积在其上方,1 平方单位的面积在其下方。最小的 y 坐标是 1。

示例 2:

输入: squares = [[0,0,2],[1,1,1]]

输出: 1.16667

解释:

面积如下:

  • 线下的面积:7/6 * 2 (红色) + 1/6 (蓝色) = 15/6 = 2.5
  • 线上的面积:5/6 * 2 (红色) + 5/6 (蓝色) = 15/6 = 2.5

由于线以上和线以下的面积相等,输出为 7/6 = 1.16667

 

提示:

  • 1 <= squares.length <= 5 * 104
  • squares[i] = [xi, yi, li]
  • squares[i].length == 3
  • 0 <= xi, yi <= 109
  • 1 <= li <= 109
  • 所有正方形的总面积不超过 1012

二分

作者 tsreaper
2025年2月16日 14:03

解法:二分

二分水平线的坐标,找到最小的坐标,使得水平线上面的面积和小等于水平线下面的面积和即可。

因为坐标最大值是 $2 \times 10^9$,而精度要求是 $10^{-5}$,我们的二分次数应该提供至少 $10^{-14}$ 的精度。直接二分 $100$ 次,$2^{-100}$ 远小于 $10^{-14}$。

复杂度 $\mathcal{O}(tn)$,其中 $t = 100$ 是二分次数。

参考代码(c++)

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        int mx = 0;
        for (auto &sq : squares) mx = max(mx, sq[1] + sq[2]);

        auto check = [&](double lim) {
            // a:水平线下面的面积和
            // b:水平线上面的面积和
            long double a = 0, b = 0;
            for (auto &sq : squares) {
                // 正方形完全位于水平线下面
                if (sq[1] + sq[2] <= lim) a += 1LL * sq[2] * sq[2];
                // 正方形完全位于水平线上面
                else if (sq[1] >= lim) b += 1LL * sq[2] * sq[2];
                // 正方形被水平线分成两半
                else {
                    double t = (lim - sq[1]) * sq[2];
                    a += t;
                    b += 1LL * sq[2] * sq[2] - t;
                }
            }
            return a >= b;
        };

        // 二分 100 次
        double head = 0, tail = mx;
        for (int i = 0; i < 100; i++) {
            double mid = (head + tail) / 2;
            if (check(mid)) tail = mid;
            else head = mid;
        }
        return (head + tail) / 2;
    }
};

三种方法:浮点二分 / 整数二分 / 差分(Python/Java/C++/Go)

作者 endlesscheng
2025年2月16日 08:43

方法一:浮点二分

所有正方形的面积之和为

$$
\textit{totalArea} = \sum_{i=0}^{n-1} l_i^2
$$

设在水平线 $Y=y$ 下方的面积之和为 $\textit{area}_y$,那么水平线上方的面积之和为 $\textit{totalArea}-\textit{area}_y$。

题目要求

$$
\textit{area}_y = \textit{totalArea}-\textit{area}_y
$$

$$
\textit{area}_y\cdot 2 = \textit{totalArea}
$$

我们可以二分最小的 $y$,满足

$$
\textit{area}_y\cdot 2 \ge \textit{totalArea}
$$

$\textit{area}_y$ 怎么算?

枚举正方形 $(x_i,y_i,l_i)$,如果水平线在正方形底边上面,即 $y_i < y$,那么这个正方形在水平线下方的面积为

$$
l_i\cdot\min(y-y_i, l_i)
$$

否则在水平线下方的面积为 $0$。

总的来说就是

$$
l_i\cdot\min(\max(y-y_i,0), l_i)
$$

在水平线下方的总面积为

$$
\textit{area}y = \sum{i=0}^{n-1} l_i\cdot\min(\max(y-y_i,0), l_i)
$$

细节

二分的左边界为 $0$,右边界为 $\max(y_i+l_i)$。这里无需讨论开闭区间,因为我们算的是小数。

循环条件怎么写?

推荐的写法是固定一个循环次数,因为浮点数有舍入误差,可能算出的 $\textit{mid}$ 和 $\textit{left}$ 相等,此时 $\textit{left}=\textit{mid}$ 不会更新 $\textit{left}$,导致死循环。

:本题由于值域小,也可以在 $\textit{left}$ 和 $\textit{right}$ 相距小于 $10^{-5}$ 时结束循环。但这种做法无法用于值域较大的场景,所以不推荐。

循环多少次?

设初始二分区间长度为 $L$,每二分一次,二分区间长度减半。要至少减半到 $10^{-5}$ 才能满足题目的误差要求。设循环次数为 $k$,我们有

$$
\dfrac{L}{2^k} \le 10^{-5}
$$

解得

$$
k\ge \log_2 (L\cdot 10^5)
$$

在本题的数据范围下,可以取 $k=48$(或者 $47$,取决于代码实现方式)。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        M = 100_000
        total_area = sum(l * l for _, _, l in squares)

        def check(y: float) -> bool:
            area = 0
            for _, yi, l in squares:
                if yi < y:
                    area += l * min(y - yi, l)
            return area >= total_area / 2

        left = 0
        right = max_y = max(y + l for _, y, l in squares)
        for _ in range((max_y * M).bit_length()):
            mid = (left + right) / 2
            if check(mid):
                right = mid
            else:
                left = mid
        return (left + right) / 2  # 区间中点误差小

###java

class Solution {
    public double separateSquares(int[][] squares) {
        long totArea = 0;
        int maxY = 0;
        for (int[] sq : squares) {
            int l = sq[2];
            totArea += (long) l * l;
            maxY = Math.max(maxY, sq[1] + l);
        }

        double left = 0;
        double right = maxY;
        for (int i = 0; i < 47; i++) {
            double mid = (left + right) / 2;
            if (check(squares, mid, totArea)) {
                right = mid;
            } else {
                left = mid;
            }
        }
        return (left + right) / 2; // 区间中点误差小
    }

    private boolean check(int[][] squares, double y, long totArea) {
        double area = 0;
        for (int[] sq : squares) {
            double yi = sq[1];
            if (yi < y) {
                double l = sq[2];
                area += l * Math.min(y - yi, l);
            }
        }
        return area >= totArea / 2.0;
    }
}

###cpp

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        long long tot_area = 0;
        int max_y = 0;
        for (auto& sq : squares) {
            int l = sq[2];
            tot_area += 1LL * l * l;
            max_y = max(max_y, sq[1] + l);
        }

        auto check = [&](double y) -> bool {
            double area = 0;
            for (auto& sq : squares) {
                double yi = sq[1];
                if (yi < y) {
                    double l = sq[2];
                    area += l * min(y - yi, l);
                }
            }
            return area >= tot_area / 2.0;
        };

        double left = 0, right = max_y;
        for (int i = 0; i < 47; i++) {
            double mid = (left + right) / 2;
            (check(mid) ? right : left) = mid;
        }
        return (left + right) / 2; // 区间中点误差小
    }
};

###go

func separateSquares(squares [][]int) float64 {
totArea := 0
maxY := 0
for _, sq := range squares {
l := sq[2]
totArea += l * l
maxY = max(maxY, sq[1]+l)
}

check := func(y float64) bool {
area := 0.
for _, sq := range squares {
yi := float64(sq[1])
if yi < y {
l := float64(sq[2])
area += l * min(y-yi, l)
}
}
return area >= float64(totArea)/2
}

left, right := 0., float64(maxY)
for range bits.Len(uint(maxY * 1e5)) {
mid := (left + right) / 2
if check(mid) {
right = mid
} else {
left = mid
}
}
return (left + right) / 2 // 区间中点误差小
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log (U/\epsilon))$,其中 $n$ 是 $\textit{squares}$ 的长度,$\epsilon=10^{-5}$,$U=\max(y_i+l_i)$。
  • 空间复杂度:$\mathcal{O}(1)$。

方法二:整数二分(写法一)

1)

方法一的 $y$ 是个小数。

记 $M = \epsilon^{-1} = 10^5$,改为二分整数 $y\cdot M$,最后把二分结果再除以 $M$,即为答案。

在使用整数计算的前提下,这可以保证返回结果与正确答案的绝对误差在 $1/M=10^{-5}$ 以内。

2)

下面代码采用开区间二分,这仅仅是二分的一种写法,使用闭区间或者半闭半开区间都是可以的。

  • 开区间左端点初始值:$0$。无法满足要求。
  • 开区间右端点初始值:$\max(y_i+l_i) \cdot M$。一定满足要求。

3)

能否全程使用整数计算?(只在返回的时候计算浮点数)

设当前二分的整数值为 $\textit{multiY}$,那么水平线下方的面积为

$$
\begin{aligned}
& l_i\cdot\min\left(\max\left(\dfrac{\textit{multiY}}{M}-y_i,0\right), l_i\right) \
={} & \dfrac{l_i\cdot\min(\max(\textit{multiY}-y_i\cdot M,0), l_i \cdot M)}{M} \
\end{aligned}
$$

所以有

$$
\textit{area}y = \dfrac{1}{M} \sum{i=0}^{n-1} l_i\cdot\min(\max(\textit{multiY}-y_i\cdot M,0),l_i \cdot M)
$$

判定条件

$$
\textit{area}_y\cdot 2 \ge \textit{totalArea}
$$

可以改为

$$
2 \sum_{i=0}^{n-1} l_i\cdot\min(\max(\textit{multiY}-y_i\cdot M,0), l_i\cdot M)\ge \textit{totalArea}\cdot M
$$

这样就可以全程使用整数计算了,只在最终返回时用到了浮点数。

###py

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        M = 100_000
        total_area = sum(l * l for _, _, l in squares)

        def check(multi_y: int) -> bool:
            area = 0
            for _, y, l in squares:
                if y * M < multi_y:
                    area += l * min(multi_y - y * M, l * M)
            return area * 2 >= total_area * M

        max_y = max(y + l for _, y, l in squares)
        return bisect_left(range(max_y * M), True, key=check) / M

###java

class Solution {
    private static final int M = 100_000;

    public double separateSquares(int[][] squares) {
        long totArea = 0;
        int maxY = 0;
        for (int[] sq : squares) {
            int l = sq[2];
            totArea += (long) l * l;
            maxY = Math.max(maxY, sq[1] + l);
        }

        long left = 0;
        long right = (long) maxY * M;
        while (left + 1 < right) {
            long mid = (left + right) >>> 1;
            if (check(squares, mid, totArea)) {
                right = mid;
            } else {
                left = mid;
            }
        }
        return (double) right / M;
    }

    private boolean check(int[][] squares, long multiY, double totArea) {
        long area = 0;
        for (int[] sq : squares) {
            long y = sq[1];
            if (y * M < multiY) {
                long l = sq[2];
                area += l * Math.min(multiY - y * M, l * M);
            }
        }
        return area * 2 >= totArea * M;
    }
}

###cpp

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        long long tot_area = 0;
        int max_y = 0;
        for (auto& sq : squares) {
            int l = sq[2];
            tot_area += 1LL * l * l;
            max_y = max(max_y, sq[1] + l);
        }

        const int M = 100'000;
        auto check = [&](long long multi_y) -> bool {
            long long area = 0;
            for (auto& sq : squares) {
                long long y = sq[1];
                if (y * M < multi_y) {
                    long long l = sq[2];
                    area += l * min(multi_y - y * M, l * M);
                }
            }
            return area * 2 >= tot_area * M;
        };

        long long left = 0, right = 1LL * max_y * M;
        while (left + 1 < right) {
            long long mid = left + (right - left) / 2;
            (check(mid) ? right : left) = mid;
        }
        return 1.0 * right / M;
    }
};

###go

func separateSquares(squares [][]int) float64 {
totArea := 0
maxY := 0
for _, sq := range squares {
l := sq[2]
totArea += l * l
maxY = max(maxY, sq[1]+l)
}

const m = 100_000
multiY := sort.Search(maxY*m, func(multiY int) bool {
area := 0
for _, sq := range squares {
y, l := sq[1], sq[2]
if y*m < multiY {
area += l * min(multiY-y*m, l*m)
}
}
return area*2 >= totArea*m
})
return float64(multiY) / m
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log (U/\epsilon))$,其中 $n$ 是 $\textit{squares}$ 的长度,$\epsilon=10^{-5}$,$U=\max(y_i+l_i)$。
  • 空间复杂度:$\mathcal{O}(1)$。

方法二:整数二分(写法二)

改成在 $0$ 到 $\max(y_i+l_i)$ 中二分最小的整数 $y$,满足

$$
\textit{area}_y\cdot 2 \ge \textit{totalArea}
$$

那么答案就在整数 $y-1$ 到整数 $y$ 之间。

由于输入都是整数,所以从 $y-1$ 到 $y$,在水平线下方的面积和是线性增加的,我们可以直接把答案解出来。

由于从 $y-1$ 到 $y$,矩形的底边长之和不变,所以用矩形面积的增量,除以矩形的高 $y-(y-1)=1$,就是矩形的底边长之和

$$
\textit{sumL} = \textit{area}y - \textit{area}{y-1}
$$

设答案为 $y'$,那么

$$
\textit{area}_{y'} = \textit{area}_y - (y-y')\cdot \textit{sumL}
$$

题目要求

$$
\textit{area}_{y'} \cdot 2 = \textit{totalArea}
$$

解得

$$
y' = y - \dfrac{\textit{area}_y - \textit{totalArea}/2}{\textit{sumL}} = y - \dfrac{\textit{area}_y\cdot 2 - \textit{totalArea}}{\textit{sumL}\cdot 2}
$$

###py

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        def calc_area(y: int) -> int:
            area = 0
            for _, yi, l in squares:
                if yi < y:
                    area += l * min(y - yi, l)
            return area

        tot_area = sum(l * l for _, _, l in squares)
        max_y = max(y + l for _, y, l in squares)
        y = bisect_left(range(max_y), tot_area, key=lambda y: calc_area(y) * 2)

        area_y = calc_area(y)
        sum_l = area_y - calc_area(y - 1)
        return y - (area_y * 2 - tot_area) / (sum_l * 2)  # 这样写误差更小

###java

class Solution {
    public double separateSquares(int[][] squares) {
        long totArea = 0;
        int maxY = 0;
        for (int[] sq : squares) {
            int l = sq[2];
            totArea += (long) l * l;
            maxY = Math.max(maxY, sq[1] + l);
        }

        int left = 0;
        int right = maxY;
        while (left + 1 < right) {
            int mid = (left + right) >>> 1;
            if (calcArea(squares, mid) * 2 >= totArea) {
                right = mid;
            } else {
                left = mid;
            }
        }
        int y = right;

        long areaY = calcArea(squares, y);
        long sumL = areaY - calcArea(squares, y - 1);
        return y - (areaY * 2 - totArea) / (sumL * 2.0); // 这样写误差更小
    }

    private long calcArea(int[][] squares, int y) {
        long area = 0;
        for (int[] sq : squares) {
            int yi = sq[1];
            if (yi < y) {
                int l = sq[2];
                area += (long) l * Math.min(y - yi, l);
            }
        }
        return area;
    }
}

###cpp

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        long long tot_area = 0;
        int max_y = 0;
        for (auto& sq : squares) {
            int l = sq[2];
            tot_area += 1LL * l * l;
            max_y = max(max_y, sq[1] + l);
        }

        auto calc_area = [&](int y) {
            long long area = 0;
            for (auto& sq : squares) {
                int yi = sq[1];
                if (yi < y) {
                    int l = sq[2];
                    area += 1LL * l * min(y - yi, l);
                }
            }
            return area;
        };

        int left = 0, right = max_y;
        while (left + 1 < right) {
            int mid = left + (right - left) / 2;
            (calc_area(mid) * 2 >= tot_area ? right : left) = mid;
        }
        int y = right;

        long long area_y = calc_area(y);
        long long sum_l = area_y - calc_area(y - 1);
        return y - (area_y * 2 - tot_area) / (sum_l * 2.0); // 这样写误差更小
    }
};

###go

func separateSquares(squares [][]int) float64 {
totArea := 0
maxY := 0
for _, sq := range squares {
l := sq[2]
totArea += l * l
maxY = max(maxY, sq[1]+l)
}

calcArea := func(y int) (area int) {
for _, sq := range squares {
yi := sq[1]
if yi < y {
l := sq[2]
area += l * min(y-yi, l)
}
}
return
}
y := sort.Search(maxY, func(y int) bool { return calcArea(y)*2 >= totArea })

areaY := calcArea(y)
sumL := areaY - calcArea(y-1)
return float64(y) - float64(areaY*2-totArea)/float64(sumL*2) // 这样写误差更小
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log U)$,其中 $n$ 是 $\textit{squares}$ 的长度,$U=\max(y_i+l_i)$。
  • 空间复杂度:$\mathcal{O}(1)$。

方法三:差分+扫描线

lc3453.png

想象有一根水平扫描线在从下往上扫描,对于示例 2,这根扫描线依次扫过 $y=0,1,2$:

  • 从 $y=0$ 到 $y=1$,面积的增加量可以视作一个底边长为 $2$,高为 $1$ 的矩形的面积,即 $2\cdot 1 = 2$。
  • 从 $y=1$ 到 $y=2$,面积的增加量可以视作一个底边长为 $2+1=3$(重叠的要累加),高为 $1$ 的矩形的面积,即 $3\cdot 1 = 3$。

扫描的过程中,维护面积之和 $\textit{area}$,底边长之和 $\textit{sumL}$。

设当前 $y$ 与下一个 $y'$ 之差为 $y'-y$,则新增面积为

$$
\textit{sumL}\cdot (y'-y)
$$

如果发现

$$
\textit{area} \cdot 2 \ge \textit{totalArea}
$$

那么可以直接算出答案,计算公式和上面「方法二:整数二分(写法二)」是一样的。

$\textit{sumL}$ 可以用差分维护,请看 差分原理讲解

###py

class Solution:
    def separateSquares(self, squares: List[List[int]]) -> float:
        tot_area = 0
        diff = defaultdict(int)
        for _, y, l in squares:
            tot_area += l * l
            diff[y] += l
            diff[y + l] -= l

        area = sum_l = 0
        for y, y2 in pairwise(sorted(diff)):
            sum_l += diff[y]  # 矩形底边长度之和
            area += sum_l * (y2 - y)  # 底边长 * 高 = 新增面积
            if area * 2 >= tot_area:
                return y2 - (area * 2 - tot_area) / (sum_l * 2)

###java

class Solution {
    public double separateSquares(int[][] squares) {
        long totArea = 0;
        TreeMap<Integer, Long> diff = new TreeMap<>();
        for (int[] sq : squares) {
            int y = sq[1];
            long l = sq[2];
            totArea += l * l;
            diff.merge(y, l, Long::sum);
            diff.merge(y + (int) l, -l, Long::sum);
        }

        long area = 0;
        long sumL = 0;
        int preY = 0; // 不好计算下一个 y,改成维护上一个 y
        for (var e : diff.entrySet()) {
            int y = e.getKey();
            area += sumL * (y - preY); // 底边长 * 高 = 新增面积
            if (area * 2 >= totArea) {
                return y - (area * 2 - totArea) / (sumL * 2.0);
            }
            preY = y;
            sumL += e.getValue(); // 矩形底边长度之和
        }
        return -1;
    }
}

###cpp

class Solution {
public:
    double separateSquares(vector<vector<int>>& squares) {
        long long tot_area = 0;
        map<int, long long> diff;
        for (auto& sq : squares) {
            int y = sq[1], l = sq[2];
            tot_area += 1LL * l * l;
            diff[y] += l;
            diff[y + l] -= l;
        }

        long long area = 0, sum_l = 0;
        for (auto it = diff.begin();;) {
            auto [y, sl] = *it;
            int y2 = (++it)->first;
            sum_l += sl; // 矩形底边长度之和
            area += sum_l * (y2 - y); // 底边长 * 高 = 新增面积
            if (area * 2 >= tot_area) {
                return y2 - (area * 2 - tot_area) / (sum_l * 2.0);
            }
        }
    }
};

###go

func separateSquares(squares [][]int) float64 {
totArea := 0
diff := map[int]int{}
for _, sq := range squares {
y, l := sq[1], sq[2]
totArea += l * l
diff[y] += l
diff[y+l] -= l
}

ys := slices.Sorted(maps.Keys(diff))
area, sumL := 0, 0
for i := 0; ; i++ {
sumL += diff[ys[i]] // 矩形底边长度之和
area += sumL * (ys[i+1] - ys[i]) // 底边长 * 高 = 新增面积
if area*2 >= totArea {
return float64(ys[i+1]) - float64(area*2-totArea)/float64(sumL*2)
}
}
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log n)$,其中 $n$ 是 $\textit{squares}$ 的长度。瓶颈在排序/维护有序集合上。
  • 空间复杂度:$\mathcal{O}(n)$。

专题训练

见下面数据结构题单的「§2.1 一维差分」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

每日一题-访问所有点的最小时间🟢

2026年1月12日 00:00

平面上有 n 个点,点的位置用整数坐标表示 points[i] = [xi, yi] 。请你计算访问所有这些点需要的 最小时间(以秒为单位)。

你需要按照下面的规则在平面上移动:

  • 每一秒内,你可以:
    • 沿水平方向移动一个单位长度,或者
    • 沿竖直方向移动一个单位长度,或者
    • 跨过对角线移动 sqrt(2) 个单位长度(可以看作在一秒内向水平和竖直方向各移动一个单位长度)。
  • 必须按照数组中出现的顺序来访问这些点。
  • 在访问某个点时,可以经过该点后面出现的点,但经过的那些点不算作有效访问。

 

示例 1:

输入:points = [[1,1],[3,4],[-1,0]]
输出:7
解释:一条最佳的访问路径是: [1,1] -> [2,2] -> [3,3] -> [3,4] -> [2,3] -> [1,2] -> [0,1] -> [-1,0]   
从 [1,1] 到 [3,4] 需要 3 秒 
从 [3,4] 到 [-1,0] 需要 4 秒
一共需要 7 秒

示例 2:

输入:points = [[3,2],[-2,2]]
输出:5

 

提示:

  • points.length == n
  • 1 <= n <= 100
  • points[i].length == 2
  • -1000 <= points[i][0], points[i][1] <= 1000

切比雪夫距离(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2025年12月31日 13:57

假设我们要从点 $(x_1,y_1)$ 移动到点 $(x_2,y_2)$。

水平距离为 $d_x = |x_1-x_2|$。

垂直距离为 $d_y = |y_1-y_2|$。

如果 $d_x$ 和 $d_y$ 都大于 $0$,那么:

  • 使用对角线移动,一秒就能把 $d_x$ 和 $d_y$ 都减少 $1$。
  • 另外两种移动,一秒只能把 $d_x$ 和 $d_y$ 的其中一个减少一。

所以当 $d_x$ 和 $d_y$ 都大于 $0$ 时,使用对角线移动是最优的。

  • 如果 $d_x > d_y$,先沿着对角线移动 $d_y$ 秒,再水平移动 $d_x - d_y$ 秒,一共移动 $d_x$ 秒。
  • 如果 $d_x \le d_y$,先沿着对角线移动 $d_x$ 秒,再垂直移动 $d_y - d_x$ 秒,一共移动 $d_y$ 秒。

所以从点 $(x_1,y_1)$ 移动到点 $(x_2,y_2)$ 至少要花

$$
\max(d_x,d_y) = \max(|x_1-x_2|,|y_1-y_2|)
$$

秒。上式也是两点的切比雪夫距离。

由于题目要求「必须按照数组中出现的顺序来访问这些点」,我们遍历 $\textit{points}$ 中的相邻点对,计算上式,累加即为答案。

###py

class Solution:
    def minTimeToVisitAllPoints(self, points: List[List[int]]) -> int:
        ans = 0
        for (x1, y1), (x2, y2) in pairwise(points):
            ans += max(abs(x1 - x2), abs(y1 - y2))
        return ans

###py

class Solution:
    def minTimeToVisitAllPoints(self, points: List[List[int]]) -> int:
        return sum(max(abs(x1 - x2), abs(y1 - y2)) for (x1, y1), (x2, y2) in pairwise(points))

###java

class Solution {
    public int minTimeToVisitAllPoints(int[][] points) {
        int ans = 0;
        for (int i = 1; i < points.length; i++) {
            int[] p = points[i - 1];
            int[] q = points[i];
            ans += Math.max(Math.abs(p[0] - q[0]), Math.abs(p[1] - q[1]));
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int minTimeToVisitAllPoints(vector<vector<int>>& points) {
        int ans = 0;
        for (int i = 1; i < points.size(); i++) {
            auto& p = points[i - 1];
            auto& q = points[i];
            ans += max(abs(p[0] - q[0]), abs(p[1] - q[1]));
        }
        return ans;
    }
};

###c

#define MAX(a, b) ((b) > (a) ? (b) : (a))

int minTimeToVisitAllPoints(int** points, int pointsSize, int* pointsColSize) {
    int ans = 0;
    for (int i = 1; i < pointsSize; i++) {
        int* p = points[i - 1];
        int* q = points[i];
        ans += MAX(abs(p[0] - q[0]), abs(p[1] - q[1]));
    }
    return ans;
}

###go

func minTimeToVisitAllPoints(points [][]int) (ans int) {
for i := 1; i < len(points); i++ {
p := points[i-1]
q := points[i]
ans += max(abs(p[0]-q[0]), abs(p[1]-q[1]))
}
return
}

func abs(x int) int {
if x < 0 {
return -x
}
return x
}

###js

var minTimeToVisitAllPoints = function(points) {
    let ans = 0;
    for (let i = 1; i < points.length; i++) {
        const [x1, y1] = points[i - 1];
        const [x2, y2] = points[i];
        ans += Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
    }
    return ans;
};

###rust

impl Solution {
    pub fn min_time_to_visit_all_points(points: Vec<Vec<i32>>) -> i32 {
        points.windows(2)
            .map(|w| (w[0][0] - w[1][0]).abs().max((w[0][1] - w[1][1]).abs()))
            .sum()
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{points}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

访问所有点的最小时间

2020年2月19日 11:34

方法一:切比雪夫距离

对于平面上的两个点 x = (x0, x1)y = (y0, y1),设它们横坐标距离之差为 dx = |x0 - y0|,纵坐标距离之差为 dy = |x1 - y1|,对于以下三种情况,我们可以分别计算出从 x 移动到 y 的最少次数:

  • dx < dy:沿对角线移动 dx 次,再竖直移动 dy - dx 次,总计 dx + (dy - dx) = dy 次;

  • dx == dy:沿对角线移动 dx 次;

  • dx > dy:沿对角线移动 dy 次,再水平移动 dx - dy 次,总计 dy + (dx - dy) = dx 次。

可以发现,对于任意一种情况,从 x 移动到 y 的最少次数为 dxdy 中的较大值 max(dx, dy),这也被称作 xy 之间的 切比雪夫距离

由于题目要求,需要按照数组中出现的顺序来访问这些点。因此我们遍历整个数组,对于数组中的相邻两个点,计算出它们的切比雪夫距离,所有的距离之和即为答案。

###C++

class Solution {
public:
    int minTimeToVisitAllPoints(vector<vector<int>>& points) {
        int x0 = points[0][0], x1 = points[0][1];
        int ans = 0;
        for (int i = 1; i < points.size(); ++i) {
            int y0 = points[i][0], y1 = points[i][1];
            ans += max(abs(x0 - y0), abs(x1 - y1));
            x0 = y0;
            x1 = y1;
        }
        return ans;
    }
};

###Python

class Solution:
    def minTimeToVisitAllPoints(self, points: List[List[int]]) -> int:
        x0, x1 = points[0]
        ans = 0
        for i in range(1, len(points)):
            y0, y1 = points[i]
            ans += max(abs(x0 - y0), abs(x1 - y1))
            x0, x1 = points[i]
        return ans

###Java

class Solution {
    public int minTimeToVisitAllPoints(int[][] points) {
        int x0 = points[0][0], y0 = points[0][1];
        int ans = 0;
        for (int i = 1; i < points.length; ++i) {
            int x1 = points[i][0], y1 = points[i][1];
            ans += Math.max(Math.abs(x0 - x1), Math.abs(y0 - y1));
            x0 = x1;
            y0 = y1;
        }
        return ans;
    }
}

###C#

public class Solution {
    public int MinTimeToVisitAllPoints(int[][] points) {
        int x0 = points[0][0], y0 = points[0][1];
        int ans = 0;
        for (int i = 1; i < points.Length; ++i) {
            int x1 = points[i][0], y1 = points[i][1];
            ans += Math.Max(Math.Abs(x0 - x1), Math.Abs(y0 - y1));
            x0 = x1;
            y0 = y1;
        }
        return ans;
    }
}

###Go

func minTimeToVisitAllPoints(points [][]int) int {
    x0, y0 := points[0][0], points[0][1]
    ans := 0
    for i := 1; i < len(points); i++ {
        x1, y1 := points[i][0], points[i][1]
        dx := abs(x0 - x1)
        dy := abs(y0 - y1)
        if dx > dy {
            ans += dx
        } else {
            ans += dy
        }
        x0, y0 = x1, y1
    }
    return ans
}

func abs(x int) int {
    if x < 0 {
        return -x
    }
    return x
}

###C

#include <stdlib.h>

int minTimeToVisitAllPoints(int** points, int pointsSize, int* pointsColSize) {
    int x0 = points[0][0], y0 = points[0][1];
    int ans = 0;
    for (int i = 1; i < pointsSize; ++i) {
        int x1 = points[i][0], y1 = points[i][1];
        int dx = abs(x0 - x1);
        int dy = abs(y0 - y1);
        ans += (dx > dy) ? dx : dy;
        x0 = x1;
        y0 = y1;
    }
    return ans;
}

###JavaScript

var minTimeToVisitAllPoints = function(points) {
    let x0 = points[0][0], y0 = points[0][1];
    let ans = 0;
    for (let i = 1; i < points.length; ++i) {
        let x1 = points[i][0], y1 = points[i][1];
        ans += Math.max(Math.abs(x0 - x1), Math.abs(y0 - y1));
        x0 = x1;
        y0 = y1;
    }
    return ans;
};

###TypeScript

function minTimeToVisitAllPoints(points: number[][]): number {
    let x0 = points[0][0], y0 = points[0][1];
    let ans = 0;
    for (let i = 1; i < points.length; ++i) {
        let x1 = points[i][0], y1 = points[i][1];
        ans += Math.max(Math.abs(x0 - x1), Math.abs(y0 - y1));
        x0 = x1;
        y0 = y1;
    }
    return ans;
}

###Rust

impl Solution {
    pub fn min_time_to_visit_all_points(points: Vec<Vec<i32>>) -> i32 {
        let mut x0 = points[0][0];
        let mut y0 = points[0][1];
        let mut ans = 0;
        
        for i in 1..points.len() {
            let x1 = points[i][0];
            let y1 = points[i][1];
            ans += (x0 - x1).abs().max((y0 - y1).abs());
            x0 = x1;
            y0 = y1;
        }
        
        ans
    }
}

复杂度分析

  • 时间复杂度:$O(N)$,其中 $N$ 是数组的长度。

  • 空间复杂度:$O(1)$。

C++:暴力法

作者 yfnupup
2019年11月24日 12:31

题解:两点之间的距离就是直角三角形直角边的较大值

image.png

代码如下:

###cpp

class Solution {
public:
    int minTimeToVisitAllPoints(vector<vector<int>>& points) {
        int step=0;
        for(int i=0;i<points.size()-1;++i)
        {
            int x=abs(points[i][0]-points[i+1][0]);
            int y=abs(points[i][1]-points[i+1][1]);
            step+=max(x,y);
        }
        return step;
    }  
};
❌
❌