Skip to content

Latest commit

 

History

History
526 lines (469 loc) · 16.3 KB

README_Blind75.md

File metadata and controls

526 lines (469 loc) · 16.3 KB

【数组】(20240708-20240709,) 两数之和 买卖股票的最佳时机(以及几个变种题) 存在重复元素(以及几个变种题) 除自身以外数组的乘积 最大子数组和 乘积最大子数组 寻找旋转排序数组中的最小值 搜索旋转排序数组 三数之和 盛最多水的容器 排序(quicksort+mergesort) 【二进制】(20240709-20240710,) 两整数之和 位1的个数 比特位计数 丢失的数字 颠倒二进制位 只出现一次的数字(以及几个变种题) 【动态规划】(20240710-20240711,) 爬楼梯 使用最小花费爬楼梯 零钱兑换I&II(完全背包问题,需要花半天看一下0-1背包和完全背包问题) 单词拆分(完全背包-考虑顺序) 最长递增子序列 最长公共子序列 组合总和(以及几个变种题) 打家劫舍I&II&III 解码方法 不同路径I&II 跳跃游戏(时间复杂度O(n)) 【图】(20240712-20240713,) 克隆图 课程表 岛屿数量 最长连续序列 火星词典(plus会员)

现有一种使用英语字母的火星语言,这门语言的字母顺序对你来说是未知的。
给你一个来自这种外星语言字典的字符串列表words,words中的字符串已经按这门新语言的字典序
进行了排序。如果这种说法是错误的,并且给出的words不能对应任何字母的顺序,则返回 "" 。
否则,返回一个按新语言规则的字典递增顺序排序的独特字符串。如果有多个解决方案,则返回其中任意一个。

示例 1:
输入:words = ["wrt","wrf","er","ett","rftt"]
输出:"wertf"

示例 2:
输入:words = ["z","x"]
输出:"zx"

示例 3:
输入:words = ["z","x","z"]
输出:""
解释:不存在合法字母顺序,因此返回 "" 。
 

提示:
1 <= words.length <= 100
1 <= words[i].length <= 100
words[i] 仅由小写英文字母组成
#方法一:拓扑排序 + 深度优先搜索
class Solution:
    def alienOrder(self, words: List[str]) -> str:
        g = {}
        for c in words[0]:
            g[c] = []
        for s, t in pairwise(words):
            for c in t:
                g.setdefault(c, [])
            for u, v in zip(s, t):
                if u != v:
                    g[u].append(v)
                    break
            else:
                if len(s) > len(t):
                    return ""

        VISITING, VISITED = 1, 2
        states = {}
        order = []
        def dfs(u: str) -> bool:
            states[u] = VISITING
            for v in g[u]:
                if v not in states:
                    if not dfs(v):
                        return False
                elif states[v] == VISITING:
                    return False
            order.append(u)
            states[u] = VISITED
            return True

        return ''.join(reversed(order)) if all(dfs(u) for u in g if u not in states) else ""
# 方法二:拓扑排序 + 广度优先搜索
class Solution:
    def alienOrder(self, words: List[str]) -> str:
        g = defaultdict(list)
        inDeg = {c: 0 for c in words[0]}
        for s, t in pairwise(words):
            for c in t:
                inDeg.setdefault(c, 0)
            for u, v in zip(s, t):
                if u != v:
                    g[u].append(v)
                    inDeg[v] += 1
                    break
            else:
                if len(s) > len(t):
                    return ""

        q = [u for u, d in inDeg.items() if d == 0]
        for u in q:
            for v in g[u]:
                inDeg[v] -= 1
                if inDeg[v] == 0:
                    q.append(v)
        return ''.join(q) if len(q) == len(inDeg) else ""

以图判树(plus会员) 给定编号从 0 到 n - 1 的 n 个结点。给定一个整数 n 和一个 edges 列表,其中 edges[i] = [ai, bi] 表示图中节点 ai 和 bi 之间存在一条无向边。 如果这些边能够形成一个合法有效的树结构,则返回 true ,否则返回 false 。 以图判树1 输入: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]] 输出: true 以图判树2 输入: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]] 输出: false

无向图中连通分量的数目(plus会员) 你有一个包含 n 个节点的图。给定一个整数 n 和一个数组 edges ,其中 edges[i] = [ai, bi] 表示图中 ai 和 bi 之间有一条边。 返回 图中已连接分量的数目 。 示例1: 无向图中连通分量的数目1 输入: n = 5, edges = [[0, 1], [1, 2], [3, 4]] 输出: 2 无向图中连通分量的数目2 输入: n = 5, edges = [[0,1], [1,2], [2,3], [3,4]] 输出: 1

提示: 1 <= n <= 2000 1 <= edges.length <= 5000 edges[i].length == 2 0 <= ai <= bi < n ai != bi edges中不会出现重复的边

'''
思路和心得:

1.并查集就是针对这个问题设计的(用size进行压缩)

直接默写就ok
(有些函数用不到,但每次都默写全,比较好一点,模块化,肌肉记忆)

(一)size策略
size[node]是每课以node为根结点的树的结点个数。

(二)size策略+Find中扁平化(路径压缩)
size[node]与实际意义可能对不上
但size[root]是对的

(三)rank策略
是size策略的兄弟

(四)Find中扁平化(路径压缩)
比较省心。也只是尽量去扁平化。最后不能保证,最后的树高就是2,也可能碰巧了很高,但概率很小

(*)rank策略+Find中扁平化(路径压缩)。rank[root]对应不上实际
只要find优化了一次,很有可能,这棵树上很多点的rank都变了。rank[root]也与实际对不上了
'''
class UnionFind:
    def __init__(self, n):
        self.n = n
        self.part = n
        self.parent = [x for x in range(n)]
        self.size = [1 for _ in range(n)]

    def Find(self, x: int) -> int:
        if self.parent[x] == x:
            return x
        return self.Find(self.parent[x])

    def Union(self, x: int, y: int) -> bool:
        root_x = self.Find(x)
        root_y = self.Find(y)
        if root_x == root_y:
            return False
        if self.size[root_x] > self.size[root_y]:
            root_x, root_y = root_y, root_x
        self.parent[root_x] = root_y
        self.size[root_y] += self.size[root_x]
        self.part -= 1
        return True
    
    def in_the_same_part(self, x: int, y: int) -> bool:
        return self.Find(x) == self.Find(y)
    
    def get_part_size(self, x: int) -> int:
        root_x = self.Find(x)
        return self.size[root_x]

class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        UF = UnionFind(n)
        for x, y in edges:
            UF.Union(x, y)
        return UF.part
//Java三种解法 并查集+DFS+BFS
//1.并查集
public int countComponents(int n, int[][] edges) {
    int[] parents = new int[n];
    Arrays.fill(parents, -1);

    for (int[] e : edges) {
        int root1 = find(parents, e[0]);
        int root2 = find(parents, e[1]);
        if (root1 != root2) {
            parents[root1] = root2;
            n--;
        }
    }
    return n;
}

private int find(int[] parents, int x) {
    int root = x;
    while (parents[root] != -1) root = parents[root];
    return root;
}

//2.深度优先搜索dfs
public int countComponents(int n, int[][] edges) {
    int count = 0;
    List<List<Integer>> adjList = new ArrayList<>();
    boolean[] visited = new boolean[n];

    for (int i = 0; i < n; i++) adjList.add(new ArrayList<>());
    for (int[] edge : edges) {
        adjList.get(edge[0]).add(edge[1]);
        adjList.get(edge[1]).add(edge[0]);
    }
        
    for (int i = 0; i < n; i++) {
        if (!visited[i]) {
            count++;
            dfs(visited, i, adjList);
        }
    }
    return count;
}

private void dfs(boolean[] visited, int index, List<List<Integer>> adjList) {
    visited[index] = true;
    for (int i : adjList.get(index)) {
        if (!visited[i]) {
            dfs(visited, i, adjList);
        }
    }
}

//3.广度优先搜索bfs
public int countComponents(int n, int[][] edges) {
    int count = 0;
    List<List<Integer>> adjList = new ArrayList<>();
    boolean[] visited = new boolean[n];
        
    for (int i = 0; i < n; i++) adjList.add(new ArrayList<>());
    for (int[] edge : edges) {
        adjList.get(edge[0]).add(edge[1]);
        adjList.get(edge[1]).add(edge[0]);
    }

    for (int i = 0;  i < n; i++) {
        if (!visited[i]) {
            count++;
            Queue<Integer> queue = new LinkedList<>();
            queue.offer(i);
            while (!queue.isEmpty()) {
                int index = queue.poll();
                visited[index] = true;
                for (int next : adjList.get(index)) {
                    if (!visited[next]) queue.offer(next);
                }
            }
        }
    }
    return count;
}

【interval】(20240713-20240714,) 插入区间 合并区间 无重叠区间 会议室(plus会员) 给定一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,请你判断一个人是否能够参加这里面的全部会议。 示例 1: 输入:intervals = [[0,30],[5,10],[15,20]] 输出:false 示例 2: 输入:intervals = [[7,10],[2,4]] 输出:true 提示: 0 <= intervals.length <= 104 intervals[i].length == 2 0 <= starti < endi <= 106

#方法一:暴力法
class Solution:
   def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
       def overlap(interval1: List[int], interval2: List[int]) -> bool:
           return (interval1[0] >= interval2[0] and interval1[0] < interval2[1]
               or interval2[0] >= interval1[0] and interval2[0] < interval1[1])

       for i in range(len(intervals)):
           for j in range(i + 1, len(intervals)):
               if overlap(intervals[i], intervals[j]):
                   return False
       return True
#方法二:排序
class Solution:
   def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
       intervals.sort()
       for i in range(len(intervals) - 1):
           if intervals[i][1] > intervals[i + 1][0]:
               return False
       return True

会议室II(plus会员) 给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量 。 示例 1: 输入:intervals = [[0,30],[5,10],[15,20]] 输出:2 示例 2: 输入:intervals = [[7,10],[2,4]] 输出:1 提示: 1 <= intervals.length <= 104 0 <= starti < endi <= 106

#方法一:优先队列
 class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        
        # 如果没有要安排的会议,则不需要分配房间。
        if not intervals:
            return 0

        # 堆初始化
        free_rooms = []
        # 按会议开始时间的升序对会议进行排序。
        intervals.sort(key= lambda x: x[0])
        # 添加第一次会议。我们得给第一次会议腾出一间新房间。
        heapq.heappush(free_rooms, intervals[0][1])

        # 对于所有剩余的会议室
        for i in intervals[1:]:
            # 如果最早应该腾出的房间是空闲的,则将该房间分配给本次会议。
            if free_rooms[0] <= i[0]:
                heapq.heappop(free_rooms)

            # 如果要分配一个新房间,那么我们也要添加到堆中,
            # 如果分配了一个旧房间,那么我们还必须添加到具有更新的结束时间的堆中。
            heapq.heappush(free_rooms, i[1])

        # 堆的大小告诉我们所有会议所需的最小房间。
        return len(free_rooms)

#方法二:按时间顺序排序
class Solution:
    def minMeetingRooms(self, intervals: List[List[int]]) -> int:
        
        # 如果没有会议,我们不需要任何房间。
        if not intervals:
            return 0

        used_rooms = 0

        # 将开始计时和结束计时分开,并分别对它们进行排序。
        start_timings = sorted([i[0] for i in intervals])
        end_timings = sorted(i[1] for i in intervals)
        L = len(intervals)

        # 算法中的两个指针:e_ptr 和 s_ptr。
        end_pointer = 0
        start_pointer = 0

        # 直到所有会议都处理完毕
        while start_pointer < L:
            # 如果有一个会议在 `start_pointer` 开始时已经结束
            if start_timings[start_pointer] >= end_timings[end_pointer]:
                # 释放一个房间并递增end_pointer。
                used_rooms -= 1
                end_pointer += 1

            # 无论房间是否空闲,我们都会这样做。
            # 如果一个房间是空闲的,那么 used_rooms+=1 将不会有任何效果。 used_rooms 
            # 在这种情况下会保持不变。如果没有空闲的房间,则会增加已用房间数。
            used_rooms += 1    
            start_pointer += 1   

        return used_rooms

【链表】(20240715-20240716,) 反转链表I&II 环形链表I&II 合并两个有序链表 合并K个升序链表 删除链表的倒数第N个结点 重排链表 【矩阵】(20240717-20240718,) 矩阵置零 螺旋矩阵I&II 旋转图像 单词搜索 【字符串】(20240718-20240719,) 无重复字符的最长子串 替换后的最长重复字符 最小覆盖子串 有效的字母异位词 字母异位词分组 有效的括号 验证回文串 最长回文子串 回文子串 字符串的编码与解码(plus会员) 请你设计一个算法,可以将一个 字符串列表 编码成为一个 字符串。这个编码后的字符串是可以通过网络进行高效传送的,并且可以在接收端被解码回原来的字符串列表。

#方法一:使用非 ASCII 码的分隔符(最简单的方法就是分隔符连接字符串)
class Codec:
    def encode(self, strs):
        """Encodes a list of strings to a single string.
        :type strs: List[str]
        :rtype: str
        """
        if len(strs) == 0: 
            return chr(258)
        
        # encode here is a workaround to fix BE CodecDriver error
        return chr(257).join(strs)
        

    def decode(self, s):
        """Decodes a single string to a list of strings.
        :type s: str
        :rtype: List[str]
        """
        if s == chr(258): 
            return []
        return s.split(chr(257))
# 方法二:分块编码
#这种方法基于 HTTP v1.1 使用的编码,它不依赖于输入字符集,因此比方法一更具有通用性和有效性。
#数据流被分成块,每个块前面都有其字节大小。
class Codec:
    def len_to_str(self, x):
        """
        Encodes length of string to bytes string
        """
        x = len(x)
        bytes = [chr(x >> (i * 8) & 0xff) for i in range(4)]
        bytes.reverse()
        bytes_str = ''.join(bytes)
        return bytes_str
    
    def encode(self, strs):
        """Encodes a list of strings to a single string.
        :type strs: List[str]
        :rtype: str
        """
        # encode here is a workaround to fix BE CodecDriver error
        return ''.join(self.len_to_str(x) + x for x in strs)
        
    def str_to_int(self, bytes_str):
        """
        Decodes bytes string to integer.
        """
        result = 0
        for ch in bytes_str:
            result = result * 256 + ord(ch)
        return result
    
    def decode(self, s):
        """Decodes a single string to a list of strings.
        :type s: str
        :rtype: List[str]
        """
        i, n = 0, len(s)
        output = []
        while i < n:
            length = self.str_to_int(s[i: i + 4])
            i += 4
            output.append(s[i: i + length])
            i += length
        return output

【树】(20240719-20240723,) 二叉树的最大深度 相同的树 翻转二叉树 二叉树中的最大路径和 二叉树的层序遍历 二叉树的序列化与反序列化 另一棵树的子树 从前序与中序遍历序列构造二叉树 验证二叉搜索树 二叉搜索树中第K小的元素 二叉树的最近公共祖先 实现Trie(前缀树) 添加与搜索单词 - 数据结构设计 【堆】(20240723-20240724,) 前K个高频元素 前K个高频单词 数据流的中位数

Original Link:
Blind 75 LeetCode Questions