diff --git "a/Index/\346\240\210.md" "b/Index/\346\240\210.md" index 73c07deb..7e193707 100644 --- "a/Index/\346\240\210.md" +++ "b/Index/\346\240\210.md" @@ -9,6 +9,7 @@ | [385. 迷你语法分析器](https://leetcode-cn.com/problems/mini-parser/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/mini-parser/solution/by-ac_oier-zuy6/) | 中等 | 🤩🤩🤩🤩🤩 | | [591. 标签验证器](https://leetcode-cn.com/problems/tag-validator/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/tag-validator/solution/by-ac_oier-9l8z/) | 困难 | 🤩🤩🤩🤩 | | [726. 原子的数量](https://leetcode-cn.com/problems/number-of-atoms/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-atoms/solution/gong-shui-san-xie-shi-yong-xiao-ji-qiao-l5ak4/) | 困难 | 🤩🤩🤩🤩 | +| [735. 行星碰撞](https://leetcode.cn/problems/asteroid-collision/) | [LeetCode 题解链接](https://leetcode.cn/problems/asteroid-collision/solution/by-ac_oier-p4qh/) | 中等 | 🤩🤩🤩🤩🤩 | | [1190. 反转每对括号间的子串](https://leetcode-cn.com/problems/reverse-substrings-between-each-pair-of-parentheses/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/reverse-substrings-between-each-pair-of-parentheses/solution/gong-shui-san-xie-shi-yong-shuang-duan-d-r35q/) | 中等 | 🤩🤩🤩🤩🤩 | | [面试题 03.01. 三合一](https://leetcode-cn.com/problems/three-in-one-lcci/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/three-in-one-lcci/solution/yi-ti-shuang-jie-er-wei-shu-zu-yi-wei-sh-lih7/) | 简单 | 🤩🤩🤩 | | [面试题 02.05. 链表求和](https://leetcode-cn.com/problems/sum-lists-lcci/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/sum-lists-lcci/solution/by-ac_oier-v1zb/) | 中等 | 🤩🤩🤩 | diff --git "a/Index/\346\250\241\346\213\237.md" "b/Index/\346\250\241\346\213\237.md" index 03321d85..77471acb 100644 --- "a/Index/\346\250\241\346\213\237.md" +++ "b/Index/\346\250\241\346\213\237.md" @@ -88,6 +88,7 @@ | [720. 词典中最长的单词](https://leetcode-cn.com/problems/longest-word-in-dictionary/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/longest-word-in-dictionary/solution/by-ac_oier-bmot/) | 简单 | 🤩🤩🤩🤩 | | [726. 原子的数量](https://leetcode-cn.com/problems/number-of-atoms/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-atoms/solution/gong-shui-san-xie-shi-yong-xiao-ji-qiao-l5ak4/) | 困难 | 🤩🤩🤩🤩 | | [729. 我的日程安排表 I](https://leetcode-cn.com/problems/my-calendar-i/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/my-calendar-i/solution/by-ac_oier-1znx/) | 中等 | 🤩🤩🤩 | +| [735. 行星碰撞](https://leetcode.cn/problems/asteroid-collision/) | [LeetCode 题解链接](https://leetcode.cn/problems/asteroid-collision/solution/by-ac_oier-p4qh/) | 中等 | 🤩🤩🤩🤩🤩 | | [747. 至少是其他数字两倍的最大数](https://leetcode-cn.com/problems/largest-number-at-least-twice-of-others/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/largest-number-at-least-twice-of-others/solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-8179/) | 简单 | 🤩🤩🤩🤩🤩 | | [748. 最短补全词](https://leetcode-cn.com/problems/shortest-completing-word/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/shortest-completing-word/solution/gong-shui-san-xie-jian-dan-zi-fu-chuan-j-x4ao/) | 简单 | 🤩🤩🤩🤩 | | [762. 二进制表示中质数个计算置位](https://leetcode-cn.com/problems/prime-number-of-set-bits-in-binary-representation/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/prime-number-of-set-bits-in-binary-representation/solution/by-ac_oier-w50x/) | 简单 | 🤩🤩🤩🤩 | diff --git "a/LeetCode/1251-1260/1252. \345\245\207\346\225\260\345\200\274\345\215\225\345\205\203\346\240\274\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1251-1260/1252. \345\245\207\346\225\260\345\200\274\345\215\225\345\205\203\346\240\274\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" index 248f94da..c3192980 100644 --- "a/LeetCode/1251-1260/1252. \345\245\207\346\225\260\345\200\274\345\215\225\345\205\203\346\240\274\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1251-1260/1252. \345\245\207\346\225\260\345\200\274\345\215\225\345\205\203\346\240\274\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -64,7 +64,7 @@ Tag : 「模拟」、「位运算」、「计数」 当 $r[idx]$ 为 `True` 含义为第 $idx$ 行的累加值为奇数,否则为偶数。列数组 `c` 的统计规则同理。 -代码: +Java 代码: ```Java class Solution { public int oddCells(int m, int n, int[][] ins) { @@ -78,6 +78,29 @@ class Solution { } } ``` +TypeScript 代码: +```TypeScript +function oddCells(m: number, n: number, ins: number[][]): number { + const r = new Array(m).fill(false), c = new Array(n).fill(false) + let a = 0, b = 0 + for (const [i, j] of ins) { + a += (r[i] = !r[i]) ? 1 : -1 + b += (c[j] = !c[j]) ? 1 : -1 + } + return a * (n - b) + (m - a) * b +}; +``` +Python 代码: +```Python3 +class Solution: + def oddCells(self, m: int, n: int, ins: List[List[int]]) -> int: + r, c = [False] * m, [False] * n + for i, j in ins: + r[i] ^= 1 + c[j] ^= 1 + a, b = sum(r), sum(c) + return a * (n - b) + (m - a) * b +``` * 时间复杂度:构建计数数组的复杂度为 $O(m + n)$,统计奇数行和奇数列复杂度为 $O(l)$,其中 $l$ 为数组 `ins` 的长度,复杂度为 $O(m + n + l)$ * 空间复杂度:$O(m + n)$ @@ -87,9 +110,9 @@ class Solution { 更进一步,我们可以使用两个 `long` 变量 $c1$ 和 $c2$ 来分别充当行和列的计数数组,当 $c1$ 的第 $k$ 位为 $1$,代表第 $k$ 行累加值为奇数,当 $c1$ 的第 $k$ 位为 $0$,代表第 $k$ 行累加值为偶数;$c2$ 的计数规则同理。而翻转二进制中的某一位可使用「异或」操作。 -当处理完所有的 `ins` 之后,可通过「遍历 $c1$ 的低 $m$ 位 + 遍历 $c2$ 的低 $n$ 位」来得到行数中奇数个数 $a$,列数中奇数个数 $b$,复杂度为 $O(m + n)$;也使用 `bitCount` 统计 `long` 二进制数中 $1$ 的个数(本质是分治操作),复杂度为 $O(\log{64})$。 +当处理完所有的 `ins` 之后,可通过「遍历 $c1$ 的低 $m$ 位 + 遍历 $c2$ 的低 $n$ 位」来得到行数中奇数个数 $a$,列数中奇数个数 $b$,复杂度为 $O(m + n)$;也可以使用 `bitCount` 统计 `long` 二进制数中 $1$ 的个数(本质是分治操作),复杂度为 $O(\log{64})$。 -代码: +Java 代码: ```Java class Solution { public int oddCells(int m, int n, int[][] ins) { @@ -105,9 +128,7 @@ class Solution { } } ``` - -- - +Java 代码: ```Java class Solution { public int oddCells(int m, int n, int[][] ins) { @@ -121,11 +142,81 @@ class Solution { } } ``` +TypeScript 代码: +```TypeScript +function oddCells(m: number, n: number, ins: number[][]): number { + const c1 = [0, 0], c2 = [0, 0] + for (const [i, j] of ins) { + c1[Math.ceil(i / 32)] ^= (1 << (i % 32)) + c2[Math.ceil(j / 32)] ^= (1 << (j % 32)) + } + let a = 0, b = 0 + for (let i = 0; i < m; i++) a += (c1[Math.ceil(i / 32)] >> (i % 32) & 1) + for (let i = 0; i < n; i++) b += (c2[Math.ceil(i / 32)] >> (i % 32) & 1) + return a * (n - b) + (m - a) * b +}; +``` +Python 代码: +```Python3 +class Solution: + def oddCells(self, m: int, n: int, ins: List[List[int]]) -> int: + c1, c2 = 0, 0 + for i, j in ins: + c1 ^= (1 << i) + c2 ^= (1 << j) + a, b = 0, 0 + for i in range(m): + a += (c1 >> i) & 1 + for i in range(n): + b += (c2 >> i) & 1 + return a * (n - b) + (m - a) * b +``` * 时间复杂度:处理所有的 `ins` 复杂度为 $O(l)$,其中 $l$ 为数组 `ins` 的长度;使用遍历方式统计奇数行和奇数列个数复杂度为 $O(m + n)$;使用 `bitCount` 操作统计二进制中 $1$ 个数,复杂度为 $O(\log{C})$,其中 $C = 64$ 为 `long` 二进制数长度,整体复杂度为 $O(l + m + n)$ 或 $O(l + \log{C})$ * 空间复杂度:$O(1)$ --- +### 答疑 + +评论区有同学提到 `bitCount` 的复杂度问题,如下是 `Long.bitCount` 操作的源码: + +```Java +public static int bitCount(long i) { + // HD, Figure 5-14 + i = i - ((i >>> 1) & 0x5555555555555555L); + i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L); + i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL; + i = i + (i >>> 8); + i = i + (i >>> 16); + i = i + (i >>> 32); + return (int)i & 0x7f; +} +``` + +这自然不是通过遍历统计位数的 $O(n)$ 做法,普遍会认为是 $O(1)$。 + +但上述操作目的是进行成组统计(分治),而能够这样写是因为默认了 `long` 是 $64$ 长度,因此严格意义来说这段代码复杂度是 $O(\log{64})$ 的。 + +作为对比,可以把 `Integer.bitCount` 的源码看一下: + +```Java +public static int bitCount(int i) { + // HD, Figure 5-2 + i = i - ((i >>> 1) & 0x55555555); + i = (i & 0x33333333) + ((i >>> 2) & 0x33333333); + i = (i + (i >>> 4)) & 0x0f0f0f0f; + i = i + (i >>> 8); + i = i + (i >>> 16); + return i & 0x3f; +} +``` + +统计原理如出一辙,而能够这样统计,是因为默认了 `int` 长度为 $32$,其分组统计所需要的操作次数也与二进制数的长度相关,因此复杂度为 $O(\log{32})$。 + +将上述两个 `bitCount` 视为 $O(1)$ 都是不对的。 + +--- + ### 最后 这是我们「刷穿 LeetCode」系列文章的第 `No.1252` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 diff --git "a/LeetCode/671-680/676. \345\256\236\347\216\260\344\270\200\344\270\252\351\255\224\346\263\225\345\255\227\345\205\270\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/671-680/676. \345\256\236\347\216\260\344\270\200\344\270\252\351\255\224\346\263\225\345\255\227\345\205\270\357\274\210\344\270\255\347\255\211\357\274\211.md" index cf817b36..108c0f96 100644 --- "a/LeetCode/671-680/676. \345\256\236\347\216\260\344\270\200\344\270\252\351\255\224\346\263\225\345\255\227\345\205\270\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/671-680/676. \345\256\236\347\216\260\344\270\200\344\270\252\351\255\224\346\263\225\345\255\227\345\205\270\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -59,7 +59,7 @@ magicDictionary.search("leetcoded"); // 返回 False > **不了解「Trie / 字典树」的同学可以看前置 🧀:[字典树入门](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247488490&idx=1&sn=db2998cb0e5f08684ee1b6009b974089)。里面通过图例展示了字典树基本形态,以及提供了「数组实现」和「TrieNode 实现」两种方式,还有「数组大小估算方式」和「Trie 应用面」介绍** -代码: +Java 代码: ```Java class MagicDictionary { int N = 100 * 100, M = 26, idx = 0; @@ -92,11 +92,100 @@ class MagicDictionary { } } ``` +TypeScript 代码: +```TypeScript +class MagicDictionary { + N: number = 100 * 100; M: number = 26; idx: number = 0; + tr: number[][] = new Array>(this.N) + isEnd: boolean[] = new Array(this.N * this.M).fill(false) + add(s: string): void { + let p = 0 + for (let i = 0; i < s.length; i++) { + const u = s.charCodeAt(i) - 'a'.charCodeAt(0) + if (this.tr[p] == undefined) this.tr[p] = new Array(this.M).fill(0) + if (this.tr[p][u] == 0) this.tr[p][u] = ++this.idx + p = this.tr[p][u] + } + this.isEnd[p] = true + } + query(s: string, idx: number, p: number, limit: number): boolean { + if (limit < 0) return false + if (idx == s.length) return this.isEnd[p] && limit == 0 + const u = s.charCodeAt(idx) - 'a'.charCodeAt(0) + for (let i = 0; i < 26; i++) { + if (this.tr[p] == undefined || this.tr[p][i] == 0) continue + if (this.query(s, idx + 1, this.tr[p][i], i == u ? limit : limit - 1)) return true + } + return false + } + buildDict(ss: string[]): void { + for (const s of ss) this.add(s) + } + search(s: string): boolean { + return this.query(s, 0, 0, 1) + } +} +``` * 时间复杂度:`buildDict` 操作需要将所有字符存入 `Trie`,复杂度为 $\sum_{i = 0}^{n - 1} len(ss[i]])$;`search` 操作在不考虑 `limit` 以及字典树中最多只有 $100$ 具体方案所带来的剪枝效果的话,最坏情况下要搜索所有 $C^L$ 个方案,其中 $C = 26$ 为字符集大小,$L = 100$ 为搜索字符串的最大长度 * 空间复杂度:$O(N \times L \times C)$,其中 $N = 100$ 为存入 `Trie` 的最大方案数,$L = 100$ 为存入字符串的最大长度,$C = 26$ 为字符集大小 --- +### 模拟 + +当然,利用数据范围只有 $100$,直接使用模拟也是可以的。 + +Java 代码: +```Java +class MagicDictionary { + String[] ss; + public void buildDict(String[] _ss) { + ss = _ss; + } + public boolean search(String str) { + for (String s : ss) { + int cnt = 0; + for (int i = 0; s.length() == str.length() && i < s.length() && cnt <= 1; i++) { + if (s.charAt(i) != str.charAt(i)) cnt++; + } + if (cnt == 1) return true; + } + return false; + } +} +``` +TypeScript 代码: +```TypeScript +class MagicDictionary { + ss: string[] + buildDict(_ss: string[]): void { + this.ss = _ss + } + search(s: string): boolean { + for (const str of this.ss) { + let cnt = 0 + for (let i = 0; str.length == s.length && i < s.length && cnt <= 1; i++) { + if (s.charAt(i) != str.charAt(i)) cnt++ + } + if (cnt == 1) return true + } + return false + } +} +``` +* 时间复杂度:`buildDict` 操作复杂度为 $O(1)$;`search` 操作复杂度为 $O(n \times L)$,其中 $n$ 为数组 `ss` 的长度,$L$ 为查询字符串的长度 +* 空间复杂度:$O(n)$ + +--- + +### 加餐 + +1. 前置练习题 [可用 Trie 进阶的模拟题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247492214&idx=1&sn=40fa070fe3b014873297f7ff740ba60f) + +2. 另外一道 [结合 DFS 的 Trie 运用题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247492188&idx=1&sn=a1436d1ffe2b8200a36c3196ca1c7ed1) + +--- + ### 最后 这是我们「刷穿 LeetCode」系列文章的第 `No.676` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 diff --git "a/LeetCode/731-740/735. \350\241\214\346\230\237\347\242\260\346\222\236\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/731-740/735. \350\241\214\346\230\237\347\242\260\346\222\236\357\274\210\344\270\255\347\255\211\357\274\211.md" new file mode 100644 index 00000000..940ba1e4 --- /dev/null +++ "b/LeetCode/731-740/735. \350\241\214\346\230\237\347\242\260\346\222\236\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -0,0 +1,122 @@ +### 题目描述 + +这是 LeetCode 上的 **[735. 行星碰撞](https://leetcode.cn/problems/asteroid-collision/solution/by-ac_oier-p4qh/)** ,难度为 **中等**。 + +Tag : 「栈」、「模拟」 + + + +给定一个整数数组 `asteroids`,表示在同一行的行星。 + +对于数组中的每一个元素,其绝对值表示行星的大小,正负表示行星的移动方向(正表示向右移动,负表示向左移动)。每一颗行星以相同的速度移动。 + +找出碰撞后剩下的所有行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。 + +示例 1: +``` +输入:asteroids = [5,10,-5] + +输出:[5,10] + +解释:10 和 -5 碰撞后只剩下 10 。 5 和 10 永远不会发生碰撞。 +``` +示例 2: +``` +输入:asteroids = [8,-8] + +输出:[] + +解释:8 和 -8 碰撞后,两者都发生爆炸。 +``` +示例 3: +``` +输入:asteroids = [10,2,-5] + +输出:[10] + +解释:2 和 -5 发生碰撞后剩下 -5 。10 和 -5 发生碰撞后剩下 10 。 +``` + +提示: +* $2 <= asteroids.length <= 10^4$ +* $-1000 <= asteroids[i] <= 1000$ +* $asteroids[i] != 0$ + +--- + +### 模拟 + 栈 + +为了方便,我们令 `asteroids` 为 `ats`。 + +由于碰撞抵消总是从相邻行星之间发生,我们可以使用「栈」来模拟该过程。 + +从前往后处理所有的 $ats[i]$,使用栈存储当前未被抵消的行星,当栈顶元素方向往右,当前 $ats[i]$ 方向往左时,会发生抵消操作,抵消过程根据规则进行即可。 + +Java 代码: +```Java +class Solution { + public int[] asteroidCollision(int[] ats) { + Deque d = new ArrayDeque<>(); + for (int t : ats) { + boolean ok = true; + while (ok && !d.isEmpty() && d.peekLast() > 0 && t < 0) { + int a = d.peekLast(), b = -t; + if (a <= b) d.pollLast(); + if (a >= b) ok = false; + } + if (ok) d.addLast(t); + } + int sz = d.size(); + int[] ans = new int[sz]; + while (!d.isEmpty()) ans[--sz] = d.pollLast(); + return ans; + } +} +``` +TypeScript 代码: +```TypeScript +function asteroidCollision(ats: number[]): number[] { + const stk: number[] = new Array() + for (const t of ats) { + let ok: boolean = true + while (ok && stk.length > 0 && stk[stk.length - 1] > 0 && t < 0) { + const a = stk[stk.length - 1], b = -t + if (a <= b) stk.pop() + if (a >= b) ok = false + } + if (ok) stk.push(t) + } + return stk +}; +``` +Python 3 代码: +```Python3 +class Solution: + def asteroidCollision(self, ats: List[int]) -> List[int]: + stk = [] + for t in ats: + ok = True + while ok and stk and stk[-1] > 0 and t < 0: + a, b = stk[-1], -t + if a <= b: + stk.pop(-1) + if a >= b: + ok = False + if ok: + stk.append(t) + return stk +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(n)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.735` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。