一些不常见但很好用的数据结构

并查集

并查集(Union-find Data Structure)是一种树型的数据结构。它的特点是由子结点找到父亲结点,用于处理一些不交集(Disjoint Sets)的合并及查询问题。
Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。

684. 冗余连接
https://leetcode.cn/problems/redundant-connection/solution/tong-su-jiang-jie-bing-cha-ji-bang-zhu-xiao-bai-ku/
这篇题解讲解并查集的原理讲的很清晰

拓展

130. 被围绕的区域
1254. 统计封闭岛屿的数目
这两题可以用并查集做,但用DFS或BFS会更直观且好写一些。

685. 冗余连接 II

树状数组

这个其实用的比较少,没时间的话可以不用太看。

针对不同的题目,我们有不同的方案可以选择(假设我们有一个数组):
数组不变,求区间和:「前缀和」、「树状数组」、「线段树」
多次修改某个数(单点),求区间和:「树状数组」、「线段树」
多次修改某个区间,输出最终结果:「差分」
多次修改某个区间,求区间和:「线段树」、「树状数组」(看修改区间范围大小)
多次将某个区间变成同一个数,求区间和:「线段树」、「树状数组」(看修改区间范围大小)
这样看来,「线段树」能解决的问题是最多的,那我们是不是无论什么情况都写「线段树」呢?
答案并不是,而且恰好相反,只有在我们遇到第 4 类问题,不得不写「线段树」的时候,我们才考虑线段树。
因为「线段树」代码很长,而且常数很大,实际表现不算很好。我们只有在不得不用的时候才考虑「线段树」。
总结一下,我们应该按这样的优先级进行考虑:
简单求区间和,用「前缀和」
多次将某个区间变成同一个数,用「线段树」
其他情况,用「树状数组」

其中,前缀和很好写,一般都能想到。而线段树代码过长,实用性较低。因此,这里主要介绍一些树状数组的题目。

307. 区域和检索 - 数组可修改
https://leetcode.cn/problems/range-sum-query-mutable/solution/-by-hu-ge-8-t4rn/
这里详细介绍了树状数组及其使用

拓扑排序

了解这类题的思路后,当图的题做熟练了就能比较轻松地写出来。一般来说是以邻接表建图,并用一个数组来记录每个节点的入度。之后有广度优先或深度优先两种方法来遍历。我比较习惯用广度优先的方法,也就是先把所有入度为0的结点入队,然后根据邻接表来减少后继节点的入度。若后继节点入度为零了则将该节点入队。直到队伍为空后,看从队伍中pop()的结点数是否为全部,若是则返回True,否则返回False

207. 课程表

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        edges = collections.defaultdict(list)
        indeg = [0] * numCourses

        for info in prerequisites:
            edges[info[1]].append(info[0])				#邻接表
            indeg[info[0]] += 1							#入度数组
        
        q = collections.deque([u for u in range(numCourses) if indeg[u] == 0])
        #以入度为0的结点初始化队列
        visited = 0
		#记录已访问节点数
        while q:
            visited += 1
            u = q.popleft()
            for v in edges[u]:
                indeg[v] -= 1
                if indeg[v] == 0:
                    q.append(v)
        return visited == numCourses

802. 找到最终的安全状态

1203. 项目管理
1591. 奇怪的打印机 II
这两题比较难,但有助于拓展思路,第一题是复杂的拓扑结构,第二题是逆向思维加拓扑。

差分数组

729. 我的日程安排表 I
731. 我的日程安排表 II
732. 我的日程安排表 III
这三题一样的,是差分数组的一种运用。

插旗法:进入一个区间的时候将该点坐标对应的值+1,代表插上一面进入的🚩,离开时将该点坐标值-1,代表插上一面离开的🚩,在同一个点可以同时插进入的旗或离开的旗,因为这样并不形成区间重叠。
这种方法非常适合解最大的区间重叠数量 (或最大的并行数量) 的题目,能够将时间复杂度控制在 O(nlog{n})O(nlogn),而且代码可以说是八九不离十。
https://leetcode.cn/problems/my-calendar-ii/solution/yi-fa-jie-nti-by-laughhhh-pll7/

from sortedcontainers import SortedDict

class MyCalendarThree:
    def __init__(self):
        self.d = SortedDict()

    def book(self, start: int, end: int) -> int:
        self.d[start] = self.d.setdefault(start, 0) + 1
        self.d[end] = self.d.setdefault(end, 0) - 1

        ans = maxBook = 0
        for freq in self.d.values():
            maxBook += freq
            ans = max(ans, maxBook)
        return ans

1109.航班预定统计(推荐)
995. K 连续位的最小翻转次数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值