【每日算法】一题四解 : 「模拟」&「树状数组 (含去重优化) 」&「线段树」|Python 主题月

简介: 【每日算法】一题四解 : 「模拟」&「树状数组 (含去重优化) 」&「线段树」|Python 主题月

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 1893. 检查是否区域内所有整数都被覆盖 ,难度为 简单


Tag : 「模拟」、「树状数组」、「线段树」


给你一个二维整数数组 ranges 和两个整数 left 和 right 。每个 ranges[i] = [starti, endi] 表示一个从 starti 到 endi 的 闭区间 。


如果闭区间 [left, right] 内每个整数都被 ranges 中 至少一个 区间覆盖,那么请你返回 true ,否则返回 false 。


已知区间 ranges[i] = [starti, endi] ,如果整数 x 满足 starti <= x <= endi ,那么我们称整数x 被覆盖了。


示例 1:


输入:ranges = [[1,2],[3,4],[5,6]], left = 2, right = 5
输出:true
解释:2 到 5 的每个整数都被覆盖了:
- 2 被第一个区间覆盖。
- 3 和 4 被第二个区间覆盖。
- 5 被第三个区间覆盖。
复制代码


示例 2:


输入:ranges = [[1,10],[10,20]], left = 21, right = 21
输出:false
解释:21 没有被任何一个区间覆盖。
复制代码


提示:


  • 1 <= ranges.length <= 50
  • 1 <= starti <= endi <= 50
  • 1 <= left <= right <= 50


模拟



一个简单的想法是根据题意进行模拟,检查 [left, right][left,right] 中的每个整数,如果检查过程中发现某个整数没被 rangesranges 中的闭区间所覆盖,那么直接返回 False,所有数值通过检查则返回 True


代码:


class Solution {
    public boolean isCovered(int[][] rs, int l, int r) {
        for (int i = l; i <= r; i++) {
            boolean ok = false;
            for (int[] cur : rs) {
                int a = cur[0], b = cur[1];
                if (a <= i && i <= b) {
                    ok = true;
                    break;
                }
            }
            if (!ok) return false;
        }
        return true;
    }
}
复制代码


Python 3 代码:


class Solution:
    def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
        for i in range(left, right+1):
            ok = False
            for a, b in ranges:
                if a <= i <= b:
                    ok = True
                    break
            if not ok:
                return False
        return True
复制代码


  • 时间复杂度:令 [left, right][left,right] 之间整数数量为 nnrangesranges 长度为 mm。整体复杂度为 O(n * m)O(nm)
  • 空间复杂度:O(1)O(1)


树状数组



针对此题,可以有一个很有意思的拓展,将本题难度提升到【中等】甚至是【困难】。


将查询 [left, right][left,right] 修改为「四元查询数组」querysquerys,每个 querys[i]querys[i] 包含四个指标 (a,b,l,r)(a,b,l,r):代表询问 [l, r][l,r] 中的每个数是否在 rangerange[a, b][a,b] 的闭区间所覆盖过。


如果进行这样的拓展的话,那么我们需要使用「持久化树状数组」或者「主席树」来配合「容斥原理」来做。


基本思想都是使用 range[0,b]range[0,b] 的计数情况减去 range[0, a-1]range[0,a1] 的计数情况来得出 [a, b][a,b] 的计数情况。


回到本题,由于数据范围很小,只有 5050,我们可以使用「树状数组」进行求解:


  • void add(int x, int u):对于数值 xx 出现次数进行 +u+u 操作;
  • int query(int x):查询某个满足 <= x<=x 的数值的个数。


那么显然,如果我们需要查询一个数值 xx 是否出现过,可以通过查询 cnt = query(x) - query(x - 1)cnt=query(x)query(x1) 来得知。


Java 代码:


class Solution {
    int n = 55;
    int[] tr = new int[n];
    int lowbit(int x) {
        return x & -x;
    }
    void add(int x, int u) {
        for (int i = x; i <= n; i += lowbit(i)) tr[i] += u;
    }
    int query(int x) {
        int ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];
        return ans;
    }
    public boolean isCovered(int[][] rs, int l, int r) {
        for (int[] cur : rs) {
            int a = cur[0], b = cur[1];
            for (int i = a; i <= b; i++) {
                add(i, 1);
            }
        }
        for (int i = l; i <= r; i++) {
            int cnt = query(i) - query(i - 1);
            if (cnt == 0) return false;
        }
        return true;
    }
}
复制代码


Python 3 代码:


class Solution:
    def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
        n = 55
        tr = [0] * n
        def lowbit(x):
            return x & -x
        def add(x, u):
            i = x
            while i <= n:
                tr[i] += u
                i += lowbit(i)
        def query(x):
            ans = 0
            i = x
            while i > 0:
                ans += tr[i]
                i -= lowbit(i)
            return ans
        for a,b in ranges:
            for i in range(a, b+1):
                add(i, 1)
        for i in range(left, right+1):
            cnt = query(i) - query(i-1)
            if not cnt:
                return False
        return True
复制代码


  • 时间复杂度:令 [left, right][left,right] 之间整数数量为 nn\sum_{i = 0}^{range.legth - 1}ranges[i].lengthi=0range.legth1ranges[i].lengthsumsum,常数 CC 固定为 5555。建树复杂度为 O(sum\log C)O(sumlogC),查询查询复杂度为 O(n\log{C})O(nlogC)。整体复杂度为 O(sum\log{C} + n\log{C})O(sumlogC+nlogC)
  • 空间复杂度:O(C)O(C)


树状数组(去重优化)



在朴素的「树状数组」解法中,我们无法直接查询 [l, r][l,r] 区间中被覆盖过的个数的根本原因是「某个值可能会被重复添加到树状数组中」。


因此,一种更加优秀的做法:在往树状数组中添树的时候进行去重,然后通过 cnt = query(r) - query(l - 1)cnt=query(r)query(l1) 直接得出 [l, r][l,r] 范围内有多少个数被添加过。


这样的 Set 去重操作可以使得我们查询的复杂度从 O(n\log{C})O(nlogC) 下降到 O(\log{C})O(logC)


由于数值范围很小,自然也能够使用数组来代替 Set 进行标记(见 P2)


Java 代码:


class Solution {
    int n = 55;
    int[] tr = new int[n];
    int lowbit(int x) {
        return x & -x;
    }
    void add(int x, int u) {
        for (int i = x; i <= n; i += lowbit(i)) tr[i] += u;
    }
    int query(int x) {
        int ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];
        return ans;
    }
    public boolean isCovered(int[][] rs, int l, int r) {
        Set<Integer> set = new HashSet<>();
        for (int[] cur : rs) {
            int a = cur[0], b = cur[1];
            for (int i = a; i <= b; i++) {
                if (!set.contains(i)) {
                    add(i, 1);
                    set.add(i);
                }
            }
        }
        int tot = r - l + 1, cnt = query(r) - query(l - 1);
        return tot == cnt;
    }
}
复制代码

Java 代码:

class Solution {
    int n = 55;
    int[] tr = new int[n];
    boolean[] vis = new boolean[n];
    int lowbit(int x) {
        return x & -x;
    }
    void add(int x, int u) {
        for (int i = x; i <= n; i += lowbit(i)) tr[i] += u;
    }
    int query(int x) {
        int ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];
        return ans;
    }
    public boolean isCovered(int[][] rs, int l, int r) {
        for (int[] cur : rs) {
            int a = cur[0], b = cur[1];
            for (int i = a; i <= b; i++) {
                if (!vis[i]) {
                    add(i, 1);
                    vis[i] = true;
                }
            }
        }
        int tot = r - l + 1, cnt = query(r) - query(l - 1);
        return tot == cnt;
    }
}
复制代码


  • 时间复杂度:令 [left, right][left,right] 之间整数数量为 nn\sum_{i = 0}^{range.legth - 1}ranges[i].lengthi=0range.legth1ranges[i].lengthsumsum,常数 CC 固定为 5555。建树复杂度为 O(sum\log C)O(sumlogC),查询查询复杂度为 O(\log{C})O(logC)。整体复杂度为 O(sum\log{C} + \log{C})O(sumlogC+logC)
  • 空间复杂度:O(C + \sum_{i = 0}^{range.legth - 1}ranges[i].length)O(C+i=0range.legth1ranges[i].length)


线段树(不含“懒标记”)



更加进阶的做法是使用「线段树」来做,与「树状数组(优化)」解法一样,线段树配合持久化也可以用于求解「在线」问题。


与主要解决「单点修改 & 区间查询」的树状数组不同,线段树能够解决绝大多数「区间修改(区间修改/单点修改)& 区间查询」问题。


对于本题,由于数据范围只有 5555,因此我们可以使用与「树状数组(优化)」解法相同的思路,实现一个不包含“懒标记”的线段树来做(仅支持单点修改 & 区间查询)。


Java 代码:


class Solution {
    // 代表 [l, r] 区间有 cnt 个数被覆盖
    class Node {
        int l, r, cnt;
        Node (int _l, int _r, int _cnt) {
            l = _l; r = _r; cnt = _cnt;
        }
    }
    int N = 55;
    Node[] tr = new Node[N * 4];
    void pushup(int u) {
        tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt;
    }
    void build(int u, int l, int r) {
        if (l == r) {
            tr[u] = new Node(l, r, 0);
        } else {
            tr[u] = new Node(l, r, 0);
            int mid = l + r >> 1;
            build(u << 1, l, mid);
            build(u << 1 | 1, mid + 1, r);
            pushup(u);
        }
    }
    // 从 tr 数组的下标 u 开始,在数值 x 的位置进行标记
    void update(int u, int x) {
        if (tr[u].l == x && tr[u].r == x) {
            tr[u].cnt = 1;
        } else {
            int mid = tr[u].l + tr[u].r >> 1;
            if (x <= mid) update(u << 1, x);
            else update(u << 1 | 1, x);
            pushup(u);
        }
    }
    // 从 tr 数组的下标 u 开始,查询 [l,r] 范围内有多少个数值被标记
    int query(int u, int l, int r) {
        if (l <= tr[u].l && tr[u].r <= r) return tr[u].cnt;
        int mid = tr[u].l + tr[u].r >> 1;
        int ans = 0;
        if (l <= mid) ans += query(u << 1, l, r);
        if (r > mid) ans += query(u << 1 | 1, l, r);
        return ans;
    }
    public boolean isCovered(int[][] rs, int l, int r) {
        build(1, 1, N);
        for (int[] cur : rs) {
            int a = cur[0], b = cur[1];
            for (int i = a; i <= b; i++) {
                update(1, i);
            }
        }
        int tot = r - l + 1 , cnt = query(1, l, r);
        return tot == cnt;
    }
}
复制代码


Python 3 代码:


class Solution:
    def isCovered(self, ranges: List[List[int]], l: int, r: int) -> bool:
        def pushup(u):
            tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt
        def build(u, l, r):
            tr[u] = Node(l, r, 0)
            if l != r:
                tr[u] = Node(l, r, 0)
                mid = l + r >> 1
                build(u << 1, l, mid)
                build(u << 1 | 1, mid + 1, r)
                pushup(u)
        # 从 tr 数组的下标 u 开始,在数值 x 的位置进行标记
        def update(u, x):
            if tr[u].l == x and tr[u].r == x:
                tr[u].cnt = 1
            else:
                mid = tr[u].l + tr[u].r >> 1
                if x <= mid:
                    update(u << 1, x)
                else:
                    update(u << 1 | 1, x)
                pushup(u)
        # 从 tr 数组的下标 u 开始,查询 [l,r] 范围内有多少个数值被标记
        def query(u, l, r):
            if l <= tr[u].l and tr[u].r <= r:
                return tr[u].cnt
            mid = tr[u].l + tr[u].r >> 1
            ans = 0
            if l <= mid:
                ans += query(u << 1, l, r)
            if r > mid:
                ans += query(u << 1 | 1, l, r)
            return ans
        N = 55
        tr = [None] * N * 4
        build(1, 1, N)
        for a,b in ranges:
            for i in range(a, b+1):
                update(1, i)
        tot = r - l + 1
        cnt = query(1, l, r)
        return tot == cnt
class Node:
    def __init__(self, l, r, cnt):
        self.l = l
        self.r = r
        self.cnt = cnt
复制代码


  • 时间复杂度:令 [left, right][left,right] 之间整数数量为 nn\sum_{i = 0}^{range.legth - 1}ranges[i].lengthi=0range.legth1ranges[i].lengthsumsum,常数 CC 固定为 5555。建树复杂度为 O(sum\log C)O(sumlogC),查询查询复杂度为 O(\log{C})O(logC)。整体复杂度为 O(sum\log{C} + \log{C})O(sumlogC+logC)
  • 空间复杂度:O(C * 4)O(C4)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.1893 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
19天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
眼疾识别系统,本系统使用Python作为主要开发语言,基于TensorFlow搭建卷积神经网络算法,并收集了4种常见的眼疾图像数据集(白内障、糖尿病性视网膜病变、青光眼和正常眼睛) 再使用通过搭建的算法模型对数据集进行训练得到一个识别精度较高的模型,然后保存为为本地h5格式文件。最后使用Django框架搭建了一个Web网页平台可视化操作界面,实现用户上传一张眼疾图片识别其名称。
81 4
基于Python深度学习的眼疾识别系统实现~人工智能+卷积网络算法
|
9天前
|
算法 数据可视化 安全
基于DWA优化算法的机器人路径规划matlab仿真
本项目基于DWA优化算法实现机器人路径规划的MATLAB仿真,适用于动态环境下的自主导航。使用MATLAB2022A版本运行,展示路径规划和预测结果。核心代码通过散点图和轨迹图可视化路径点及预测路径。DWA算法通过定义速度空间、采样候选动作并评估其优劣(目标方向性、障碍物距离、速度一致性),实时调整机器人运动参数,确保安全避障并接近目标。
105 68
|
29天前
|
存储 缓存 监控
局域网屏幕监控系统中的Python数据结构与算法实现
局域网屏幕监控系统用于实时捕获和监控局域网内多台设备的屏幕内容。本文介绍了一种基于Python双端队列(Deque)实现的滑动窗口数据缓存机制,以处理连续的屏幕帧数据流。通过固定长度的窗口,高效增删数据,确保低延迟显示和存储。该算法适用于数据压缩、异常检测等场景,保证系统在高负载下稳定运行。 本文转载自:https://www.vipshare.com
124 66
|
1天前
|
存储 算法 安全
控制局域网上网软件之 Python 字典树算法解析
控制局域网上网软件在现代网络管理中至关重要,用于控制设备的上网行为和访问权限。本文聚焦于字典树(Trie Tree)算法的应用,详细阐述其原理、优势及实现。通过字典树,软件能高效进行关键词匹配和过滤,提升系统性能。文中还提供了Python代码示例,展示了字典树在网址过滤和关键词屏蔽中的具体应用,为局域网的安全和管理提供有力支持。
31 17
|
19天前
|
机器学习/深度学习 数据采集 算法
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目基于MATLAB2022a实现时间序列预测,采用CNN-GRU-SAM网络结构。卷积层提取局部特征,GRU层处理长期依赖,自注意力机制捕捉全局特征。完整代码含中文注释和操作视频,运行效果无水印展示。算法通过数据归一化、种群初始化、适应度计算、个体更新等步骤优化网络参数,最终输出预测结果。适用于金融市场、气象预报等领域。
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
|
22天前
|
机器学习/深度学习 人工智能 算法
机器学习算法的优化与改进:提升模型性能的策略与方法
机器学习算法的优化与改进:提升模型性能的策略与方法
139 13
机器学习算法的优化与改进:提升模型性能的策略与方法
|
17天前
|
移动开发 算法 计算机视觉
基于分块贝叶斯非局部均值优化(OBNLM)的图像去噪算法matlab仿真
本项目基于分块贝叶斯非局部均值优化(OBNLM)算法实现图像去噪,使用MATLAB2022A进行仿真。通过调整块大小和窗口大小等参数,研究其对去噪效果的影响。OBNLM结合了经典NLM算法与贝叶斯统计理论,利用块匹配和概率模型优化相似块的加权融合,提高去噪效率和保真度。实验展示了不同参数设置下的去噪结果,验证了算法的有效性。
|
16天前
|
算法 决策智能
基于SA模拟退火优化算法的TSP问题求解matlab仿真,并对比ACO蚁群优化算法
本项目基于MATLAB2022A,使用模拟退火(SA)和蚁群优化(ACO)算法求解旅行商问题(TSP),对比两者的仿真时间、收敛曲线及最短路径长度。SA源于金属退火过程,允许暂时接受较差解以跳出局部最优;ACO模仿蚂蚁信息素机制,通过正反馈发现最优路径。结果显示SA全局探索能力强,ACO在路径优化类问题中表现优异。
|
16天前
|
存储 数据挖掘 数据处理
Python Pandas入门:行与列快速上手与优化技巧
Pandas是Python中强大的数据分析库,广泛应用于数据科学和数据分析领域。本文为初学者介绍Pandas的基本操作,包括安装、创建DataFrame、行与列的操作及优化技巧。通过实例讲解如何选择、添加、删除行与列,并提供链式操作、向量化处理、索引优化等高效使用Pandas的建议,帮助用户在实际工作中更便捷地处理数据。
31 2
|
25天前
|
机器学习/深度学习 算法
基于遗传优化的双BP神经网络金融序列预测算法matlab仿真
本项目基于遗传优化的双BP神经网络实现金融序列预测,使用MATLAB2022A进行仿真。算法通过两个初始学习率不同的BP神经网络(e1, e2)协同工作,结合遗传算法优化,提高预测精度。实验展示了三个算法的误差对比结果,验证了该方法的有效性。

热门文章

最新文章