diff --git a/Main.java b/Main.java deleted file mode 100644 index 3e2836ea8..000000000 --- a/Main.java +++ /dev/null @@ -1,5 +0,0 @@ -public class Main { - public static void main(String[] args) { - System.out.println("Hello World"); - } -} \ No newline at end of file diff --git a/README.md b/README.md index 46fc6987a..bd8ceb211 100644 --- a/README.md +++ b/README.md @@ -8,26 +8,29 @@ 点击此处:[在线阅读](http://jalan.space/leetcode-notebook/) -## Rum +## 这是什么? -- 🐱 LeetCode 解题本,基于**算法与数据结构**,对做过的 LeetCode 练习题进行归纳与总结。 -- 🍸 项目代号 [Rum](https://zh.wikipedia.org/wiki/%E5%85%B0%E5%A7%86%E9%85%92)([朗姆酒](https://zh.wikipedia.org/wiki/%E5%85%B0%E5%A7%86%E9%85%92)),鸡尾酒的热带气息基酒,暗指「算法与数据结构是程序员的基底」。 -- 👩‍💻 题解主要由 Python 书写,包含少量 Java / Go / PHP / Swift 版本 -- ⚔️ 使用 [docsify](https://docsify.js.org/#/) 构建,搭建教程见 [《docsify 入坑指南与我放弃 Gitbook 的那些理由》](http://jalan.space/2019/06/21/2019/begin-docsify/) - -欢迎关注我的公众号:`CodeWarrior_`。加入编程世界一起冒险,一起成长! +LeetCode 解题本,基于**算法与数据结构**,对做过的 LeetCode 练习题进行归纳与总结。该项目代号为 [Rum](https://zh.wikipedia.org/wiki/%E5%85%B0%E5%A7%86%E9%85%92),朗姆酒是鸡尾酒的热带气息基酒,暗指「算法与数据结构是程序员的基底」。 -
+项目内题解主要由 Python 书写,包含少量 Java/Go/PHP/Swift 版本,后续会陆续补充完全。 -### 基本概念 +项目使用 [docsify](https://docsify.js.org/#/) 构建,搭建教程见 [《docsify 入坑指南与我放弃 Gitbook 的那些理由》](http://jalan.space/2019/06/21/2019/begin-docsify/) + +欢迎关注我的公众号「编程拯救世界」(CodeWarrior_),加入编程世界一起冒险,一起成长! + +![](./docs/_img/qrcode.png) + +如果你对分享题解或做题感兴趣,欢迎加入[刷题小组](https://github.com/leetcode-notebook)。 + +## 基本概念 (更新中……) - [基础算法](concept/base-algorithm/) -### 题解分类 +## 题解分类 -#### 数据结构 +### 数据结构 * [数组](data-structure/array/) * [字符串](data-structure/string/) @@ -42,7 +45,7 @@ * [栈](data-structure/stack/) * [哈希表](data-structure/hash/) -#### 算法思想 +### 算法思想 * [递归](algorithm/recursion/) * 排序 diff --git a/answer/0027/main.go b/answer/0027/main.go new file mode 100644 index 000000000..06092e2a0 --- /dev/null +++ b/answer/0027/main.go @@ -0,0 +1,23 @@ +func removeElement(nums []int, val int) int { + length := len(nums) + if length == 0 { + return 0 + } + + i := 0 + j := 0 + for j < length { + if nums[j] == val { + // 去找一个不是 val 的值 + j++ + } else { + // 互换 + nums[i], nums[j] = nums[j], nums[i] + // i 在前进的过程中走的是 j 走过的路,一定不会再碰到 val + i++ + j++ + } + } + + return length - (j - i) +} \ No newline at end of file diff --git a/answer/0027/main.py b/answer/0027/main.py new file mode 100644 index 000000000..cdcdcb6b9 --- /dev/null +++ b/answer/0027/main.py @@ -0,0 +1,21 @@ +# python3 + +class Solution: + def removeElement(self, nums, val): + """ + :type nums: List[int] + :type val: int + :rtype: int + """ + length = len(nums) + i = 0 + j = 0 + while j < length: + if nums[j] != val: + nums[i] = nums[j] + i = i + 1 + j = j + 1 + else: + j = j + 1 + res = length - (j - i) + return res \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 46fc6987a..86af05ed1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -63,6 +63,14 @@ * [双指针](algorithm/double-pointer/) * [其他](algorithm/other/) +## 题解目录 + +整理中…… + +| # | Title | Solution | Difficulty | +| ---- | ---- | ---- | ---- | +| 0027 | [移除元素](https://leetcode-cn.com/problems/remove-element/) | [Python](https://github.com/JalanJiang/leetcode-notebook/blob/master/answer/0027/main.py), [Go](https://github.com/JalanJiang/leetcode-notebook/blob/master/answer/0027/main.go) | Easy | + ## 关于我们 ### 🐱Jalan diff --git a/docs/_sidebar.md b/docs/_sidebar.md index dfa6796e3..c746af3b8 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -37,7 +37,9 @@ * [双指针](algorithm/double-pointer/) * [滑动窗口](algorithm/sliding-window/) * [其他](algorithm/other/) + * [3.3 设计](design/README.md) * 3.3 竞赛 * [周赛](weekly/) * [双周赛](biweekly/) - * [其他](other/) \ No newline at end of file + * [其他](other/) + * [3.4 剑指 offer 系列](offer/) \ No newline at end of file diff --git a/docs/algorithm/backtrack/README.md b/docs/algorithm/backtrack/README.md index 8b717dd19..d4e00b278 100644 --- a/docs/algorithm/backtrack/README.md +++ b/docs/algorithm/backtrack/README.md @@ -52,4 +52,194 @@ class Solution(object): self.backtrack(tmp, i, combination + [num], nums, res) else: return +``` + +## 46. 全排列 + +[原题链接](https://leetcode-cn.com/problems/permutations/) + +### 思路:回溯 + +[参考题解](https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/) + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + def track_back(nums, track): + if len(nums) == len(track): + ans.append(track[:]) + # 遍历集合 + for n in nums: + if n in track: + # 已经在决策树中 + continue + # 加入决策 + track.append(n) + track_back(nums, track) + # 回溯 + track.pop() + + ans = [] + track = [] + track_back(nums, track) + return ans +``` + +## 216. 组合总和 III + +[原题链接](https://leetcode-cn.com/problems/combination-sum-iii/) + +回溯模板: + +``` +backtracking() { + if (终止条件) { + 存放结果; + } + + for (选择:选择列表(可以想成树中节点孩子的数量)) { + 递归,处理节点; + backtracking(); + 回溯,撤销处理结果 + } +} +``` + +### 题解 + + + +#### **Python** + +```python +class Solution: + def combinationSum3(self, k: int, n: int) -> List[List[int]]: + ans = [] + ''' + element: 数组内答案 + start: 遍历的开始位置 + num: 剩余数字 + ''' + def dfs(element, start, num): + # 符合条件,加入最终答案,结束递归 + if len(element) == k or num < 0: + if len(element) == k and num == 0: + # print(element) + # 深拷贝 + ans.append(element[:]) + return + + for i in range(start, 10): + # 加入当前值 + element.append(i) + # 递归 + dfs(element, i + 1, num - i) + # 撤销选择,即回溯 + element.pop() + + dfs([], 1, n) + return ans +``` + +#### **Go** + +```go +func combinationSum3(k int, n int) [][]int { + ans := [][]int{} + dfs(&ans, []int{}, 1, n, k) + return ans +} + +func dfs(ans *[][]int, element []int, start int, num int, k int) { + if len(element) == k || num <= 0 { + if len(element) == k && num == 0 { + temp := make([]int, k) + copy(temp, element) + *ans = append(*ans, temp) + } + return + } + + for i:=start; i < 10; i++ { + element = append(element, i) + dfs(ans, element, i + 1, num - i, k) + element = element[:len(element) - 1] + } +} +``` + + + +## 357. 计算各个位数不同的数字个数 + +[原题链接](https://leetcode-cn.com/problems/count-numbers-with-unique-digits/) + +### 解法一:回溯 + +用 `tags` 数组标记 0~9 数字出现的次数,再调用递归后进行回溯。注意对 0 进行特殊处理。 + +```python +class Solution: + ans = 0 + tags = [] # 记录数字出现的次数 + def countNumbersWithUniqueDigits(self, n: int) -> int: + # 0-9 的数字 + self.tags = [0 for _ in range(10)] + # 枚举所有数字:回溯 + """ + r: 轮次 + num: 数字 + """ + def dfs(r, num = 0): + if r <= 0: + # 结束递归 + return + + for i in range(10): + # 循环 0-9 + if num % 10 != i and self.tags[i] == 0: + # 条件枝剪 + self.ans += 1 + # 数字出现标记 + self.tags[i] = 1 + dfs(r - 1, num * 10 + i) + # 回溯 + self.tags[i] = 0 + + dfs(n) + # 补充 0 + return self.ans + 1 +``` + +### 解法二:动态规划 + +排列组合。 + +- `f(0) = 1` +- `f(1) = 9` +- `f(2) = 9 * 9 + f(1)` + - 第一个数字选择 1~9 + - 第二个数在 0~9 中选择和第一个数不同的数 +- `f(3) = 9 * 9 * 8 + f(2)` + +可以推出动态规划方程: + +``` +dp[i] = sum + dp[i - 1] +``` + +```python +class Solution: + def countNumbersWithUniqueDigits(self, n: int) -> int: + dp = [0 for _ in range(n + 1)] + # n = 1 时 + dp[0] = 1 + + for i in range(1, n + 1): + s = 9 + for j in range(1, i): + s *= (10 - j) + dp[i] = s + dp[i - 1] + + return dp[n] ``` \ No newline at end of file diff --git a/docs/algorithm/bit/README.md b/docs/algorithm/bit/README.md index 55c4282de..80173d454 100644 --- a/docs/algorithm/bit/README.md +++ b/docs/algorithm/bit/README.md @@ -39,6 +39,10 @@ class Solution(object): - 相同为 0,不同为 1 - 0 与任何数异或都等于该数本身 + + +#### **Python** + ```python class Solution: def singleNumber(self, nums): @@ -52,6 +56,20 @@ class Solution: return a ``` +#### **Go** + +```go +func singleNumber(nums []int) int { + ans := 0 + for _, n := range nums { + ans ^= n + } + return ans +} +``` + + + ## 190. 颠倒二进制位 @@ -182,6 +200,71 @@ class Solution(object): return count ``` +## 289. 生命游戏 + +[原题链接](https://leetcode-cn.com/problems/game-of-life/) + +### 思路 + +用两位二进制代表示状态: + +- 高位:下一个状态 +- 低位:现在的状态 + +因此,初始状态为 `00` 与 `01`。 + +步骤: + +1. 遍历矩阵,更新高位 +2. 再次遍历矩阵,更新最终值 + +```python +class Solution: + def gameOfLife(self, board: List[List[int]]) -> None: + """ + Do not return anything, modify board in-place instead. + """ + m = len(board) + n = len(board[0]) + # 更新矩阵 + for i in range(m): + for j in range(n): + lives = self.getLives(board, i, j) + if board[i][j] & 1 == 1: + # 活细胞 + if lives == 2 or lives == 3: + board[i][j] = 3 # 01->11 + else: + # 死细胞 + if lives == 3: + board[i][j] = 2 # 00->10 + + # 最终矩阵 + for i in range(m): + for j in range(n): + board[i][j] = board[i][j] >> 1 + + def getLives(self, board, i, j): + """ + 计算活细胞个数 + """ + m = len(board) + n = len(board[0]) + # 8 个方向 + directions = [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [-1, -1], [1, -1], [-1, 1]] + + lives = 0 + + for direction in directions: + d_x = direction[0] + i + d_y = direction[1] + j + + if (d_x >= 0 and d_x < m) and (d_y >= 0 and d_y < n): + if board[d_x][d_y] & 1 == 1: + lives += 1 + + return lives +``` ## 371. 两整数之和 diff --git a/docs/algorithm/divide-and-conquer/README.md b/docs/algorithm/divide-and-conquer/README.md index 96fd46530..4ac652481 100644 --- a/docs/algorithm/divide-and-conquer/README.md +++ b/docs/algorithm/divide-and-conquer/README.md @@ -1,3 +1,53 @@ +## 23. 合并K个排序链表 + +[原题链接](https://leetcode-cn.com/problems/merge-k-sorted-lists/) + +### 解一:顺序合并 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def mergeKLists(self, lists: List[ListNode]) -> ListNode: + length = len(lists) + i = 0 + ans = ListNode(0) + head = ans + while len(lists) > 0: + min_index = 0 + min_val = float('inf') + min_node = None + for i in range(len(lists)): + # 遍历数组 + node = lists[i] + if node is None: + continue + if node.val < min_val: + min_index = i + min_val = node.val + min_node = node + # 最小值进入链表 + if head is not None: + head.next = min_node + head = head.next + # 处理最小值 + # next_node = min_node.next + if min_node is None or min_node.next is None: + del lists[min_index] + else: + lists[min_index] = min_node.next + return ans.next +``` + +k 个链表,n 个节点 + +- 时间复杂度:$O(k*n)$ +- 空间复杂度:$O(n)$ + ## 241. 为运算表达式设计优先级 [原题链接](https://leetcode-cn.com/problems/different-ways-to-add-parentheses/) @@ -76,6 +126,61 @@ class Solution { } ``` +#### **Go** + +```go +import ( + "fmt" + "strconv" +) + +func diffWaysToCompute(input string) []int { + // 如果是数字,直接返回 + if isDigit(input) { + tmp, _ := strconv.Atoi(input) + return []int{tmp} + } + + // 空切片 + var res []int + // 遍历字符串 + for index, c := range input { + tmpC := string(c) + fmt.Print(tmpC) + if tmpC == "+" || tmpC == "-" || tmpC == "*" { + // 如果是运算符,则计算左右两边的算式 + left := diffWaysToCompute(input[:index]) + right := diffWaysToCompute(input[index+1:]) + + for _, leftNum := range left { + for _, rightNum := range right { + var addNum int + if tmpC == "+" { + addNum = leftNum + rightNum + } else if tmpC == "-" { + addNum = leftNum - rightNum + } else { + addNum = leftNum * rightNum + } + res = append(res, addNum) + } + } + } + } + + return res +} + +// 判断是否为全数字 +func isDigit(input string) bool { + _, err := strconv.Atoi(input) + if err != nil { + return false + } + return true +} +``` + ## 395. 至少有K个重复字符的最长子串 diff --git a/docs/algorithm/double-pointer/README.md b/docs/algorithm/double-pointer/README.md index 63d2acde8..8df804fe2 100644 --- a/docs/algorithm/double-pointer/README.md +++ b/docs/algorithm/double-pointer/README.md @@ -219,4 +219,121 @@ class Solution { } ``` +## 763. 划分字母区间 + +[原题链接](https://leetcode-cn.com/problems/partition-labels/) + +### 思路 + +- 哈希 +- 双指针 + +```python +class Solution: + def partitionLabels(self, S: str) -> List[int]: + # 字母最后出现的位置 + letter_ends = dict() + for i in range(len(S)): + s = S[i] + letter_ends[s] = i + + ans = list() + start = 0 + + while start < len(S): + begin = start + # 字母最后出现的位置 + end = letter_ends[S[start]] + while start < end: + letter = S[start] + letter_end = letter_ends[letter] + # 如果字母最后出现位置大于 end,对 end 进行更新 + if letter_end > end: + end = letter_end + start += 1 + ans.append(end - begin + 1) + start = end + 1 + + return ans +``` + +### 复杂度 + +- 时间复杂度:`O(n)` +- 空间复杂度:`O(26)` + + + +## 面试题 10.01. 合并排序的数组 + +[原题链接](https://leetcode-cn.com/problems/sorted-merge-lcci/) + +### 逆向双指针 + +- 定义两个指针 `cur_a` 与 `cur_b`,分别指向 A 数组与 B 数组的尾部,再定义一个指针 `cur` 指向 A 数组当前可以赋值的元素位置 +- 比较 `cur_a` 与 `cur_b` 指向的两个元素,把较大的元素赋值给 `cur` 所在位置 + + + +#### **Python** + +```python +class Solution: + def merge(self, A: List[int], m: int, B: List[int], n: int) -> None: + """ + Do not return anything, modify A in-place instead. + """ + # 双指针:指向两个比较的数 + cur_a = m - 1 + cur_b = n - 1 + # 指向要赋值的位置 + cur = len(A) - 1 + + while cur_a >= 0 and cur_b >= 0: + a = A[cur_a] + b = B[cur_b] + # 取较大的放在后面 + if a >= b: + A[cur] = a + cur_a -= 1 + else: + A[cur] = b + cur_b -= 1 + cur -= 1 + + while cur_b >= 0: + A[cur] = B[cur_b] + cur_b -= 1 + cur -= 1 +``` + +#### **Go** + +```go +func merge(A []int, m int, B []int, n int) { + curA := m - 1 + curB := n - 1 + cur := len(A) - 1 + + for curA >= 0 && curB >= 0 { + a := A[curA] + b := B[curB] + if a >= b { + A[cur] = a + curA -= 1 + } else { + A[cur] = b + curB -= 1 + } + cur -= 1 + } + + for curB >= 0 { + A[cur] = B[curB] + curB -= 1 + cur -= 1 + } +} +``` + \ No newline at end of file diff --git a/docs/algorithm/dynamic/README.md b/docs/algorithm/dynamic/README.md index 70c3c2727..d660b8b17 100644 --- a/docs/algorithm/dynamic/README.md +++ b/docs/algorithm/dynamic/README.md @@ -187,12 +187,26 @@ class Solution(object): return max_num ``` +2020.05.03 复盘: + +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + length = len(nums) + if length == 0: + return 0 + dp = [0 for _ in range(length)] + dp[0] = nums[0] + for i in range(1, length): + dp[i] = max(dp[i - 1] + nums[i], nums[i]) + return max(dp) +``` ## 55. 跳跃游戏 [原题链接](https://leetcode-cn.com/problems/jump-game/comments/) -### 思路 +### 思路一 数组从后往前遍历: @@ -215,6 +229,7 @@ class Solution(object): end = length - 1 for i in reversed(range(length - 1)): if i + nums[i] >= end: + # 把最后一个位置不断往前推 end = i if end == 0: @@ -223,6 +238,31 @@ class Solution(object): return False ``` +### 思路二 + +用 `mark[i]` 标记是否可以到达位置 `i`。 + +```python +class Solution: + def canJump(self, nums: List[int]) -> bool: + length = len(nums) + mark = [False for _ in range(length)] + mark[0] = True + begin = 1 + for i in range(length): + n = nums[i] + if mark[i]: + # 可以到达 + for j in range(begin - i, n + 1): + jump = i + j + if jump == length - 1: + return True + if jump >= length: + break + mark[jump] = True + begin = i + n + 1 if i + n + 1 < length else length - 1 + return mark[length - 1] +``` ## 62. 不同路径 @@ -444,6 +484,90 @@ class Solution(object): return cur ``` +## 72. 编辑距离 + +[原题链接](https://leetcode-cn.com/problems/edit-distance/) + +### 动态规划 + +用 `dp[i][j]` 表示 `words1` 前 `i` 个字符到 `words2` 前 `j` 个字符的编辑距离。 + + + +#### **Python** + +```python +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + length1 = len(word1) + length2 = len(word2) + # 如果有字符串为空 + if length1 == 0 or length2 == 0: + return length1 + length2 + + dp = [[0 for _ in range(length2 + 1)] for _ in range(length1 + 1)] + + # 初始化边界值 + for i in range(length1 + 1): + dp[i][0] = i + for j in range(length2 + 1): + dp[0][j] = j + + # 计算 dp + # 从字符串末尾插入或更新字符 + # 状态转移方程: + # 末尾相同时:dp[i][j] = dp[i - 1][j - 1] + # 末尾不同时(替换或插入操作):dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1) + for i in range(1, length1 + 1): + for j in range(1, length2 + 1): + if word1[i - 1] == word2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + else: + dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1 + + return dp[length1][length2] +``` + +#### **Go** + +```go +func minDistance(word1 string, word2 string) int { + length1 := len(word1) + length2 := len(word2) + var dp = make([][]int, length1 + 1) + for i := 0; i < length1 + 1; i++ { + dp[i] = make([]int, length2 + 1) + } + // 初始化 + for i := 0; i < length1 + 1; i++ { + dp[i][0] = i + } + for j := 0; j < length2 + 1; j++ { + dp[0][j] = j + } + // 计算 dp + for i := 1; i < length1 + 1; i ++ { + for j := 1; j < length2 + 1; j++ { + if word1[i - 1] == word2[j - 1] { + dp[i][j] = dp[i - 1][j - 1] + } else { + dp[i][j] = getMin(dp[i - 1][j - 1], getMin(dp[i - 1][j], dp[i][j - 1])) + 1 + } + } + } + return dp[length1][length2] +} + +func getMin(a int, b int) int { + if a < b { + return a + } + return b +} +``` + + + ## 95. 不同的二叉搜索树 II @@ -822,15 +946,11 @@ class Solution(object): 论动态规划,这题和 [322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/) 思路挺像的。 - - ## 300. 最长上升子序列 [原题链接](https://leetcode-cn.com/problems/longest-increasing-subsequence/) -### 思路 - -动态规划。 +### 解一:动态规划 设到某位置 n 的最长上升子序列为 `f(n)`,那么有: @@ -838,27 +958,256 @@ class Solution(object): f(n) = max(f(n), f(x) + 1) (nums[n] > numx[x] and n > x) ``` + + +#### **Python** + ```python -class Solution(object): - def lengthOfLIS(self, nums): - """ - :type nums: List[int] - :rtype: int - """ +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: length = len(nums) if length == 0: return 0 - dp = [1 for _ in range(length)] - - for i in range(1, length): + for i in range(length): for j in range(i): - if nums[i] > nums[j]: + if nums[j] < nums[i]: dp[i] = max(dp[i], dp[j] + 1) - return max(dp) ``` +#### **Go** + +```go +func lengthOfLIS(nums []int) int { + length := len(nums) + // 初始化 + dp := make([]int, length) + for i := 0; i < length; i++ { + dp[i] = 1 + } + + for i := 0; i < length; i++ { + for j := 0; j < i; j++ { + if nums[j] < nums[i] { + // dp + if dp[j] + 1 > dp[i] { + dp[i] = dp[j] + 1 + } + } + } + } + + // 返回最大 dp + res := 0 + for i := 0; i < length; i++ { + if dp[i] > res { + res = dp[i] + } + } + + return res +} +``` + + + +- 时间复杂度:$O(n^2)$ +- 空间复杂度:$O(n)$ + +### 解二:贪心 + 二分 + +- 贪心:尾部元素尽可能小才更「有机会上升」 +- 二分:使用二分查找找到要更新的元素 + +使用一个辅助列表 `tails`,用 `tails[i]` 表示长度为 `i` 的上升队列尾部最小元素,`tails` 为递增序列。 + +那么: + +- 如果 `num > tails[-1]`,直接追加 `num` 到 `tails` 尾部,且上升序列长度加 1 +- 否则在 `tails` 使用二分查找,找到第一个比 `num` 小的元素 `tails[k]`,并更新 `tails[k + 1] = num` + +```python +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + # 当前长度 + res = 0 + length = len(nums) + # tails[i] 代表上升子序列长度为 i 时尾部数字 + tails = [] + for num in nums: + if len(tails) == 0 or num > tails[-1]: + # 如果 tails 为空,或 num 大于 tails 最后一位数,追加 num + tails.append(num) + else: + # 使用二分查找,找到第一个比 num 小的数 + left = 0 + right = len(tails) - 1 + loc = right + while left <= right: + mid = (left + right) // 2 + if tails[mid] >= num: + # 找左区间 + loc = mid + right = mid - 1 + else: + # 找右区间 + left = mid + 1 + tails[loc] = num + return len(tails) +``` + +## 303. 区域和检索 - 数组不可变 + +[原题链接](https://leetcode-cn.com/problems/range-sum-query-immutable/) + +### 思路 + +动态规划前缀和。 + + + +#### **Python** + +```python +class NumArray: + + def __init__(self, nums: List[int]): + length = len(nums) + self.sum_nums = [0 for _ in range(length)] + for i in range(length): + if i == 0: + self.sum_nums[i] = nums[i] + else: + self.sum_nums[i] = self.sum_nums[i - 1] + nums[i] + + + def sumRange(self, i: int, j: int) -> int: + if i == 0: + return self.sum_nums[j] + else: + return self.sum_nums[j] - self.sum_nums[i - 1] + + +# Your NumArray object will be instantiated and called as such: +# obj = NumArray(nums) +# param_1 = obj.sumRange(i,j) +``` + +#### **Go** + +```go +type NumArray struct { + sums []int +} + + +func Constructor(nums []int) NumArray { + length := len(nums) + sums := make([]int, length) + for i, num := range nums { + if i == 0 { + sums[i] = num + } else { + sums[i] = sums[i - 1] + num + } + } + return NumArray{sums} +} + + +func (this *NumArray) SumRange(i int, j int) int { + if i == 0 { + return this.sums[j] + } else { + return this.sums[j] - this.sums[i - 1] + } +} + + +/** + * Your NumArray object will be instantiated and called as such: + * obj := Constructor(nums); + * param_1 := obj.SumRange(i,j); + */ +``` + + + +## 304. 二维区域和检索 - 矩阵不可变 + +[原题链接](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/) + +### 解法一:二维前缀和 + + + +#### **Python** + +```python +class NumMatrix: + + def __init__(self, matrix: List[List[int]]): + m = len(matrix) + n = len(matrix[0]) if matrix else 0 + self.sums = [[0 for _ in range(n + 1)] for _ in range(m + 1)] + + for i in range(m): + for j in range(n): + self.sums[i + 1][j + 1] = self.sums[i][j + 1] + self.sums[i + 1][j] - self.sums[i][j] + matrix[i][j] + + + def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int: + return self.sums[row2 + 1][col2 + 1] - self.sums[row2 + 1][col1] - self.sums[row1][col2 + 1] + self.sums[row1][col1] + + +# Your NumMatrix object will be instantiated and called as such: +# obj = NumMatrix(matrix) +# param_1 = obj.sumRegion(row1,col1,row2,col2) +``` + +#### **Go** + +```go +type NumMatrix struct { + sums [][]int +} + + +func Constructor(matrix [][]int) NumMatrix { + m := len(matrix) + if m == 0 { + return NumMatrix{} + } + n := len(matrix[0]) + sums := make([][]int, m + 1) + // 初始化第 0 行 + sums[0] = make([]int, n + 1) + for i, row := range matrix { + sums[i + 1] = make([]int, n + 1) + for j, num := range row { + sums[i + 1][j + 1] = sums[i][j + 1] + sums[i + 1][j] - sums[i][j] + num + } + } + + return NumMatrix{sums} +} + + +func (this *NumMatrix) SumRegion(row1 int, col1 int, row2 int, col2 int) int { + return this.sums[row2 + 1][col2 + 1] - this.sums[row2 + 1][col1] - this.sums[row1][col2 + 1] + this.sums[row1][col1] +} + + +/** + * Your NumMatrix object will be instantiated and called as such: + * obj := Constructor(matrix); + * param_1 := obj.SumRegion(row1,col1,row2,col2); + */ +``` + + + ## 309. 最佳买卖股票时机含冷冻期 [原题链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/) @@ -967,12 +1316,11 @@ class Solution(object): return res[amount] ``` - ## 338. 比特位计数 [原题链接](https://leetcode-cn.com/problems/counting-bits/description/) -### 思路 +### 解法一 动态规划无疑了 @@ -1000,6 +1348,49 @@ class Solution(object): return List1[:num+1] ``` +### 解法二 + +奇偶数的二进制规律: + +- 奇数二进制数总比前一个偶数二进制数多 1 个 1(即最后 1 位) +- 偶数二进制数 `x` 中 1 的个数总等于 `x / 2` 中 1 的个数(除 2 等于右移 1 位,即抹去最后一位 0) + + + +c + +```python +class Solution: + def countBits(self, num: int) -> List[int]: + ans = [0 for _ in range(num + 1)] + for i in range(num + 1): + if i % 2 == 0: + # 偶数 + ans[i] = ans[i // 2] + else: + # 奇数 + ans[i] = ans[i - 1] + 1 + return ans +``` + +#### **Go** + +```go +func countBits(num int) []int { + ans := make([]int, num + 1) + for i := 0; i <= num; i++ { + if i % 2 == 0 { + ans[i] = ans[i / 2] + } else { + ans[i] = ans[i - 1] + 1 + } + } + return ans +} +``` + + + ## 714. 买卖股票的最佳时机含手续费 [原题链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) @@ -1027,6 +1418,136 @@ class Solution(object): return cash ``` +## 494. 目标和 + +[原题链接](https://leetcode-cn.com/problems/target-sum/) + +### 解一:递归遍历所有情况 + +**本方法超出时间限制**。 + + + +#### **Python** + +```python +class Solution: + + res = 0 + + def findTargetSumWays(self, nums: List[int], S: int) -> int: + length = len(nums) + + def helper(index, cur): + if index == length: + print('res' + str(cur)) + if cur == S: + self.res += 1 + pass + return + for op in ["+", "-"]: + helper(index + 1, eval(str(cur) + op + str(nums[index]))) + + # 或直接调用两次递归: + # helper(index + 1, cur + nums[index]) + # helper(index + 1, cur - nums[index]) + + helper(0, 0) + return self.res +``` + +#### **Go** + +```go +var count int = 0 + +func findTargetSumWays(nums []int, S int) int { + helper(nums, 0, 0, S) + return count +} + +func helper(nums []int, cur int, index int, S int) { + if index == len(nums) { + if cur == S { + count += 1 + } + return + } + helper(nums, cur + nums[index], index + 1, S) + helper(nums, cur - nums[index], index + 1, S) +} +``` + + + +- 时间复杂度:$O(2^n)$ +- 空间复杂度:$O(n)$(递归调用栈) + +### 解二:动态规划 + +`dp[i][j]` 带表前 `i` 个数可得到和为 `j` 的组合数量。那么有推导式: + +``` +dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]] +``` + +也可写作: + +``` +dp[i][j + nums[i]] += dp[i - 1][j] +dp[i][j - nums[i]] += dp[i - 1][j] +``` + + + +#### **Python** + +```python +class Solution: + def findTargetSumWays(self, nums: List[int], S: int) -> int: + # 初始化 + length = len(nums) + dp = [[0 for _ in range(2001)] for _ in range(length)] + dp[0][nums[0] + 1000] = 1 + dp[0][-nums[0] + 1000] += 1 + + for i in range(1, length): + for s in range(-1000, 1001): + if dp[i - 1][s + 1000] > 0: + # 防止越界 + dp[i][s + nums[i] + 1000] += dp[i - 1][s + 1000] + dp[i][s - nums[i] + 1000] += dp[i - 1][s + 1000] + + return 0 if S > 1000 else dp[length - 1][S + 1000] +``` + +#### **Go** + +```go +func findTargetSumWays(nums []int, S int) int { + length := len(nums) + var dp [21][2001]int + dp[0][nums[0] + 1000] = 1 + dp[0][-nums[0] + 1000] += 1 + for i := 1; i < length; i++ { + for j := -1000; j < 1001; j++ { + if dp[i - 1][j + 1000] > 0 { + dp[i][j + nums[i] + 1000] += dp[i - 1][j + 1000] + dp[i][j - nums[i] + 1000] += dp[i - 1][j + 1000] + } + } + } + if S > 1000 { + return 0 + } + return dp[length - 1][S + 1000] +} +``` + + + +### 【TODO】解三:01 背包 + ## 740. 删除与获得点数 [原题链接](https://leetcode-cn.com/problems/delete-and-earn/) @@ -1067,6 +1588,97 @@ class Solution: return dp[nums_list[-1]] ``` +## 887. 鸡蛋掉落 + +[原题链接](https://leetcode-cn.com/problems/super-egg-drop/) + +### 动态规划 + +`(K, N)` 中 `K` 表示鸡蛋数,`N` 表示楼层数量。那么从 `X` 层楼扔鸡蛋时: + +- 鸡蛋碎了:状态变为 `(K-1, X-1)` +- 鸡蛋没碎:状态变为 `(K, N - X)` + +有状态转移方程如下: + +$dp(K, N) = 1 + min(max(dp(K - 1, X - 1), dp(K, N - X)))$ + +初始化值: + +1. 当只有 1 个鸡蛋时,有几层楼就要扔几次 +2. 当只有 1 层楼时,只要扔一次 +3. 0 层或 0 个鸡蛋时均初始化为 0 +4. 因为要求「最小值」,所以初始化其他数值时尽量给到最大值,可以赋值楼层数量 + 1 + +```go +func superEggDrop(K int, N int) int { + var dp [][]int + // 初始化 + for i := 0; i <= K; i++ { + tmp := make([]int, N + 1) + for j := 0; j <= N; j++ { + tmp[j] = j + 1 + if i == 0 { + // 0 个蛋 + tmp[j] = 0 + } + if i == 1 { + // 1 个蛋 + tmp[j] = j + } + if j == 0 { + // 0 层楼 + tmp[j] = 0 + } + if j == 1 { + // 1 层楼 + tmp[j] = 1 + } + } + dp = append(dp, tmp) + } + + for i:=2; i <= K; i++ { + for j := 2; j <= N; j++ { + left := 1 + right := j + for left <= right { + mid := (left + right) / 2 + if dp[i][j - mid] < dp[i - 1][mid - 1] { + right = mid - 1 + } else { + left = mid + 1 + } + } + t1 := j + 1 + t2 := j + 1 + if left <= j { + t1 = getMax(dp[i][j - left], dp[i - 1][left - 1]) + } + if right >= 1 { + t2 = getMax(dp[i][j - right], dp[i - 1][right - 1]) + } + dp[i][j] = getMin(t1, t2) + 1 + } + } + return dp[K][N] +} + +func getMax(a int, b int) int { + if a > b { + return a + } + return b +} + +func getMin(a int, b int) int { + if a < b { + return a + } + return b +} +``` + ## 898. 子数组按位或操作 [原题链接](https://leetcode-cn.com/problems/bitwise-ors-of-subarrays/) @@ -1089,6 +1701,75 @@ class Solution: return len(res) ``` +## 983. 最低票价 + +[原题链接](https://leetcode-cn.com/problems/minimum-cost-for-tickets/) + +### 解一:动态规划 + +从后往前进行动态规划,用 `dp[i]` 代表从第 `i` 天到最后一天需要的最低票价。 + +- 当 `i` 无需出行时,依据贪心法则:`dp[i] = dp[i + 1]`,即在第 `i` 天无需购票 +- 当 `i` 需要出行,`dp[i] = min(costs[j] + dp[i + j])`,`j` 的取值是 1/7/30。如果第 `i` 天出行,我们买了 `j` 天的票,那么后续 `j` 天都不需要购票事宜了,所以只要加上 `dp[i + j]` 的票价即可。 + + + +#### **Python** + +```python +class Solution: + def mincostTickets(self, days: List[int], costs: List[int]) -> int: + ans = 0 + dp = [0 for _ in range(400)] + for i in range(365, 0, -1): + if i in days: + # 需要出行 + min_dp = float('inf') + min_dp = min(costs[0] + dp[i + 1], costs[1] + dp[i + 7], costs[2] + dp[i + 30]) + dp[i] = min_dp + else: + # 不需要出行 + dp[i] = dp[i + 1] + # print(dp[20]) + return dp[1] +``` + +#### **Go** + +```go +func mincostTickets(days []int, costs []int) int { + dp := make([]int, 400) + dayMap := make(map[int]int) + for _, day := range days { + dayMap[day] = 1 + } + for i := 365; i > 0; i-- { + // 从后向前动态规划 + if _, ok := dayMap[i]; ok { + // 该天出行 + cost0 := costs[0] + dp[i + 1] + cost1 := costs[1] + dp[i + 7] + cost2 := costs[2] + dp[i + 30] + dp[i] = getMin(getMin(cost0, cost1), cost2) + } else { + // 该天不出行 + dp[i] = dp[i + 1] + } + } + + return dp[1] +} + +func getMin(a int, b int) int { + if a < b { + return a + } + return b +} +``` + + + ## 1137. 第 N 个泰波那契数 [原题链接](https://leetcode-cn.com/problems/n-th-tribonacci-number/) @@ -1167,4 +1848,32 @@ class Solution: return self.tribonacci(n - 3) + self.tribonacci(n - 2) + self.tribonacci(n - 1) ``` - \ No newline at end of file + + +## 1025. 除数博弈 + +[原题链接](https://leetcode-cn.com/problems/divisor-game/) + +### 思路 + +当 `N = 1` 时Alice 会输,当 `N = 2` 时 Alice 会赢…… + +以 `dp(N)` 代表 `N` 时先手的输赢。 + +在 `N` 中找到约数 `j`,若 `dp(N - j) == False`(Bob 输) 则说明 Alice 获胜。 + +```python +class Solution: + def divisorGame(self, N: int) -> bool: + dp = [False for _ in range(N + 1)] + if N <= 1: + return False + dp[1] = False + dp[2] = True + for i in range(3, N + 1): + for j in range(1, i // 2): + # i 的约数是否存在 dp[j] 为 False,此时 Alice 获胜 + if i % j == 0 and dp[i - j] is False: + dp[i] = True + return dp[N] +``` \ No newline at end of file diff --git a/docs/algorithm/greedy/README.md b/docs/algorithm/greedy/README.md index ee205d4f8..17fd025ab 100644 --- a/docs/algorithm/greedy/README.md +++ b/docs/algorithm/greedy/README.md @@ -74,6 +74,37 @@ class Solution(object): return res ``` +2020.05.04 复盘: + +```python +class Solution: + def jump(self, nums: List[int]) -> int: + # 每次跳到能跳到最远的位置 + ans = 0 + length = len(nums) + i = 0 + while i < length - 1: + num = nums[i] + max_index = i + max_jump = 0 + for j in range(1, num + 1): + # 跳跃到的位置 + next_i = i + j + if next_i >= length: + break + if next_i == length - 1: + # 已经跳到了最后一位 + max_index = next_i + break + if i + next_i + nums[next_i] > max_jump: + max_jump = i + next_i + nums[next_i] + max_index = next_i + # print(i, max_jump) + i = max_index + ans += 1 + return ans +``` + ## 122. 买卖股票的最佳时机 II [原题链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) @@ -633,6 +664,49 @@ class Solution { - 时间复杂度:`O(n)` - 空间复杂度:`O(n)` +## 860. 柠檬水找零 + +[原题链接](https://leetcode-cn.com/problems/lemonade-change/) + +### 思路 + +按顺序模拟找零顺序即可。 + +```python +class Solution: + def lemonadeChange(self, bills: List[int]) -> bool: + count_5 = 0 + count_10 = 0 + + for bill in bills: + if bill == 5: + count_5 += 1 + elif bill == 10: + if count_5 == 0: + return False + count_5 -= 1 + count_10 += 1 + else: + if count_10 == 0: + if count_5 < 3: + return False + else: + count_5 -= 3 + else: + if count_5 == 0: + return False + else: + count_10 -= 1 + count_5 -= 1 + + return True +``` + +### 复杂度 + +- 时间复杂度:O(n) +- 空间复杂度:O(1) + ## 955. 删列造序 II [原题链接](https://leetcode-cn.com/problems/delete-columns-to-make-sorted-ii/) diff --git a/docs/algorithm/math/README.md b/docs/algorithm/math/README.md index ffb87dd27..870508287 100644 --- a/docs/algorithm/math/README.md +++ b/docs/algorithm/math/README.md @@ -482,4 +482,150 @@ class Solution: if x_count == 0 and num != 0: return "No solution" return "x=" + str(-num // x_count) -``` \ No newline at end of file +``` + +## 836. 矩形重叠 + +[原题链接](https://leetcode-cn.com/problems/rectangle-overlap/) + +### 思路 + +判断「不相交」的条件。 + + + +#### **Python** + +```python +class Solution: + def isRectangleOverlap(self, rec1: List[int], rec2: List[int]) -> bool: + ax1, ay1 = rec1[0], rec1[1] + ax2, ay2 = rec1[2], rec1[3] + bx1, by1 = rec2[0], rec2[1] + bx2, by2 = rec2[2], rec2[3] + # 判断是否相交 + if ax2 <= bx1 or ax1 >= bx2 or ay2 <= by1 or ay1 >= by2: + return False + return True +``` + +#### **Go** + +```go +func isRectangleOverlap(rec1 []int, rec2 []int) bool { + ax1, ay1 := rec1[0], rec1[1] + ax2, ay2 := rec1[2], rec1[3] + bx1, by1 := rec2[0], rec2[1] + bx2, by2 := rec2[2], rec2[3] + if ax2 <= bx1 || ax1 >= bx2 || ay2 <= by1 || ay1 >= by2 { + return false + } + return true +} +``` + + + +## 1103. 分糖果 II + +[原题链接](https://leetcode-cn.com/problems/distribute-candies-to-people/) + +### 等差数列 + +```python +class Solution: + def distributeCandies(self, candies: int, num_people: int) -> List[int]: + s = 0 + x = 1 + people = 0 + while s + x < candies: + people += 1 + s += x + x += 1 + # 只够发到 x - 1 个人,第 x 个人可能没有足额糖果 + + # 计算可以发几轮(总轮数) + r = people // num_people + # print(people, r) + # 多发一排人数 + other = people % num_people + # 最后可能有剩余糖果分给最后一个人 + + # 等差数列 d = n + # 给每个小朋友发 + res = [0 for _ in range(num_people)] + for i in range(num_people): + res[i] = (i + 1) * r + (r * (r - 1) * num_people) // 2 + + # 给最后一排分糖果 + other_begin = r * num_people + 1 + for i in range(other): + res[i] += other_begin + other_begin += 1 + + if s < candies: + index = i + 1 + if i + 1 == num_people: + index = 0 + res[index] += candies - s + + # print(res) + return res +``` + +## 1276. 不浪费原料的汉堡制作方案 + +[原题链接](https://leetcode-cn.com/problems/number-of-burgers-with-no-waste-of-ingredients/) + +### 思路 + +数学题,解方程。 + +设有 `x` 个巨无霸,`y` 个小皇堡。那么有: + +$$ +4 \times x + 2y = tomatoSlices +$$ + +$$ +x + y = cheeseSlices +$$ + +约束项: + +- `x` 与 `y` 需大于等于 0 +- `tomatoSlices` 需要是偶数 + + + +#### **Python** + +```python +class Solution: + def numOfBurgers(self, tomatoSlices: int, cheeseSlices: int) -> List[int]: + if tomatoSlices % 2 != 0 or tomatoSlices > 4 * cheeseSlices or tomatoSlices < 2 * cheeseSlices: + return [] + + y = 2 * cheeseSlices - tomatoSlices // 2 + x = cheeseSlices - y + + return [x, y] +``` + +#### **Go** + +```go +func numOfBurgers(tomatoSlices int, cheeseSlices int) []int { + if tomatoSlices % 2 != 0 || tomatoSlices > 4 * cheeseSlices || tomatoSlices < 2 * cheeseSlices { + return make([]int, 0) + } + + res := make([]int, 2) + res[1] = 2 * cheeseSlices - tomatoSlices / 2 + res[0] = cheeseSlices - res[1] + + return res +} +``` + + \ No newline at end of file diff --git a/docs/algorithm/recursion/README.md b/docs/algorithm/recursion/README.md index 846c59cdd..e4ade5b35 100644 --- a/docs/algorithm/recursion/README.md +++ b/docs/algorithm/recursion/README.md @@ -1,3 +1,188 @@ +## 226. 翻转二叉树 + +[原题链接](https://leetcode-cn.com/problems/invert-binary-tree/) + +### 思路 + +递归。 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def invertTree(self, root: TreeNode) -> TreeNode: + if root is None: + return root + + # 翻转左子树 + left = self.invertTree(root.left) + # 翻转右子树 + right = self.invertTree(root.right) + + root.left, root.right = right, left + + return root +``` + +## 341. 扁平化嵌套列表迭代器 + +[原题链接](https://leetcode-cn.com/problems/flatten-nested-list-iterator/) + +### 解一:递归法 + +用一个队列辅助,设计递归函数 `generate(item)`: + +- 如果传入的 `item` 为列表,对 `item` 进行遍历,遍历出的元素再进入 `generate()` 进行递归 +- 如果传入的 `item` 不为列表(即为 `NestedInteger` 对象),调用 `item.isInteger()` 判断元素是否为整型: + - 元素为整型:直接加入辅助队列 + - 元素为列表:继续传入 `generate()` 进行递归 + +```python +# """ +# This is the interface that allows for creating nested lists. +# You should not implement it, or speculate about its implementation +# """ +#class NestedInteger: +# def isInteger(self) -> bool: +# """ +# @return True if this NestedInteger holds a single integer, rather than a nested list. +# """ +# +# def getInteger(self) -> int: +# """ +# @return the single integer that this NestedInteger holds, if it holds a single integer +# Return None if this NestedInteger holds a nested list +# """ +# +# def getList(self) -> [NestedInteger]: +# """ +# @return the nested list that this NestedInteger holds, if it holds a nested list +# Return None if this NestedInteger holds a single integer +# """ + +class NestedIterator: + def __init__(self, nestedList: [NestedInteger]): + self.data_list = [] + # 构造列表 + self.generate(nestedList) + # print(self.data_list) + + def generate(self, item): + """ + 构造列表 + """ + # 传入数据,如果是列表,进行递归 + if isinstance(item, list): + # 如果是列表,循环 + for i in item: + self.generate(i) + else: + if item.isInteger(): + # 是数字,加入队列 + self.data_list.append(item.getInteger()) + else: + # 是列表,取出列表 + self.generate(item.getList()) + + def next(self) -> int: + data = self.data_list[0] + del self.data_list[0] + return data + + def hasNext(self) -> bool: + return len(self.data_list) != 0 + +# Your NestedIterator object will be instantiated and called as such: +# i, v = NestedIterator(nestedList), [] +# while i.hasNext(): v.append(i.next()) +``` + +但该方法在构造器中一次性将列表元素全部提取,并不符合「迭代器」的概念。 + +### 解法二:栈 + +**用栈模拟递归的过程**。使用一个辅助栈,在 `hasNext()` 时不断处理栈顶的元素。使用 `flag` 标记栈顶是否已处理,并使用 `top` 记录迭代器的下一个数字。 + +一开始将整个 `nestedList` 压入栈中,每次处理栈时,都将栈顶元素取出: + +- 如果栈顶元素是列表:取出列表首个元素,将列表入栈,并将首个元素入栈 +- 如果栈顶元素不是列表: + - 如果栈顶元素是整型:将 `flag` 设置为 `True`,并将该整型赋值给 `top` + - 如果栈顶元素不是整型:使用 `getList()` 取出列表,并将列表入栈 + +当栈不为空且 `falg == False` 时,不断循环处理栈顶,直到取出下一个迭代值,将 `flag` 置为 `True`。 + +```python +# """ +# This is the interface that allows for creating nested lists. +# You should not implement it, or speculate about its implementation +# """ +#class NestedInteger: +# def isInteger(self) -> bool: +# """ +# @return True if this NestedInteger holds a single integer, rather than a nested list. +# """ +# +# def getInteger(self) -> int: +# """ +# @return the single integer that this NestedInteger holds, if it holds a single integer +# Return None if this NestedInteger holds a nested list +# """ +# +# def getList(self) -> [NestedInteger]: +# """ +# @return the nested list that this NestedInteger holds, if it holds a nested list +# Return None if this NestedInteger holds a single integer +# """ + +class NestedIterator: + def __init__(self, nestedList: [NestedInteger]): + self.stack = [] + # 初始化栈 + self.stack.append(nestedList) + self.top = 0 # 下一个元素 + self.flag = False # 栈顶是否已处理 + + def next(self) -> int: + self.flag = False # 栈顶恢复未处理状态 + return self.top + + def hasNext(self) -> bool: + if len(self.stack) == 0: + return False + + # 栈顶待处理且栈不为空 + while len(self.stack) > 0 and not self.flag: + # 取出栈顶 + top = self.stack.pop() + if isinstance(top, list): + # 如果是列表,取出首元素,并将两者都压入栈 + if len(top) > 0: + first = top[0] + del top[0] + self.stack.append(top) + self.stack.append(first) + else: + # 如果不是列表 + if top.isInteger(): + # 如果是整数,直接取出 + self.top = top.getInteger() + self.flag = True + else: + # 取出 List 压入栈 + self.stack.append(top.getList()) + return self.flag + +# Your NestedIterator object will be instantiated and called as such: +# i, v = NestedIterator(nestedList), [] +# while i.hasNext(): v.append(i.next()) +``` + ## 698. 划分为k个相等的子集 [原题链接](https://leetcode-cn.com/problems/partition-to-k-equal-sum-subsets/) @@ -51,4 +236,55 @@ class Solution: return False return dfs(0, 0, k) -``` \ No newline at end of file +``` + +## 779. 第K个语法符号 + +[原题链接](https://leetcode-cn.com/problems/k-th-symbol-in-grammar/) + +### 思路 + +通过观察规律可知:**第 N 行第 K 个数是由第 N - 1 行第 (K + 1) / 2 个数得来的**。 + +并且: + +- 当上一行的数字为 0 时,生成的数字是 `1 - (K % 2)` +- 当上一行的数字为 1 时,生成的数字是 `K % 2` + +递归函数设计: + +- 函数作用:返回第 `N` 行第 `K` 个数 +- 当 `N == 1` 时结束递归 + +#### 具体实现 + + + +#### **Python** + +```python +class Solution(object): + def kthGrammar(self, N, K): + if N == 1: + return 0 + # 得到第 N 行第 K 位的数字 + return self.kthGrammar(N - 1, (K + 1) // 2) ^ (1 - K % 2) +``` + +#### **Go** + +```go +func kthGrammar(N int, K int) int { + if N == 1 { + return 0 + } + return kthGrammar(N - 1, (K + 1) / 2) ^ (1 - K % 2) +} +``` + + + +#### 复杂度 + +- 时间复杂度:$O(N)$ +- 空间复杂度:$O(N)$ \ No newline at end of file diff --git a/docs/algorithm/research/bfs/README.md b/docs/algorithm/research/bfs/README.md index c10a81c4c..82007b201 100644 --- a/docs/algorithm/research/bfs/README.md +++ b/docs/algorithm/research/bfs/README.md @@ -2,7 +2,7 @@ [原题链接](https://leetcode-cn.com/problems/number-of-islands/) -### 思路 +### BFS 广度优先搜索 BFS。总结一下思想就是:一旦发现岛屿就找到与它相邻的岛屿并将它们沉没,那么下次再发现的岛屿就是新大陆了。 @@ -15,6 +15,10 @@ 3. 将岛屿沉没("1" -> "0"),防止后续重复计算 4. 使用广度优先遍历,查找该岛屿的四个相邻位置上是否还有岛屿,如果发现了岛屿就把它加入队列并且沉没 + + +#### **Python** + ```python class Solution(object): def numIslands(self, grid): @@ -60,4 +64,206 @@ class Solution(object): grid[node_area_x][node_area_y] = '0' return res +``` + +#### **Go** + +```go +func numIslands(grid [][]byte) int { + var ans int + + m := len(grid) + if m == 0 { + return ans + } + n := len(grid[0]) + + for i := 0; i < m; i++ { + for j := 0; j < n; j++ { + if grid[i][j] == '1' { + ans += 1 + // 是岛屿,bfs + tmp := []int{i, j} + queue := make([][]int, 0) + queue = append(queue, tmp) + for len(queue) > 0 { + position := queue[0] + // 删除第一个元素 + queue = queue[1:] + x := position[0] + y := position[1] + // 四个方向 (1, 0) (-1, 0) (0, 1) (0, -1) + if x + 1 < m && grid[x + 1][y] == '1' { + tmp := []int{x + 1, y} + grid[x + 1][y] = '0' + queue = append(queue, tmp) + } + if x - 1 >= 0 && grid[x - 1][y] == '1' { + tmp := []int{x - 1, y} + grid[x - 1][y] = '0' + queue = append(queue, tmp) + } + if y + 1 < n && grid[x][y + 1] == '1' { + tmp := []int{x, y + 1} + grid[x][y + 1] = '0' + queue = append(queue, tmp) + } + if y - 1 >= 0 && grid[x][y - 1] == '1' { + tmp := []int{x, y - 1} + grid[x][y - 1] = '0' + queue = append(queue, tmp) + } + } + // print(queue) + } + } + } + + return ans +} +``` + + + +## 542. 01 矩阵 + +[原题链接](https://leetcode-cn.com/problems/01-matrix/) + +### BFS:以 1 为源(超时) + +```python +class Solution: + def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: + m = len(matrix) + if m == 0: + return [] + n = len(matrix[0]) + + ans = [[0 for _ in range(n)] for _ in range(m)] + + for i in range(m): + for j in range(n): + if matrix[i][j] == 0: + # 0 不处理 + continue + mark = [[0 for _ in range(n)] for _ in range(m)] + step = 0 + queue = [(i, j, step)] + while len(queue) > 0: + # bfs + x, y, s = queue[0][0], queue[0][1], queue[0][2] + del queue[0] + if mark[x][y]: + # 已经访问过,跳过 + continue + # 处理 + mark[x][y] = 1 # 访问标记 + if matrix[x][y] == 0: + # 找到 0,进行标记,不继续遍历 + ans[i][j] = s + break + + # 否则加入上下左右 + directions = [[0, 1], [0, -1], [-1, 0], [1, 0]] + for d in directions: + n_x, n_y = x + d[0], y + d[1] + if n_x >= 0 and n_x < m and n_y >= 0 and n_y < n and mark[n_x][n_y] == 0: + # 坐标符合要求 + # print(n_x, n_y, s + 1) + queue.append((n_x, n_y, s + 1)) + + return ans +``` + +### BFS:以 0 为源 + +把所有 0 放入队列,每个 0 逐个向外 BFS 一圈标记相邻的 1,再把 BFS 到的 1 入队列…… + +```python +class Solution: + def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: + m = len(matrix) + if m == 0: + return [] + n = len(matrix[0]) + + queue = [] + + for i in range(m): + for j in range(n): + # 把 0 放入队列 + if matrix[i][j] == 0: + queue.append((i, j)) + else: + # 把 1 标记为未访问过的结点 + matrix[i][j] = -1 + directions = [[0, 1], [0, -1], [1, 0], [-1, 0]] + while len(queue) > 0: + x, y = queue[0][0], queue[0][1] + del queue[0] + for d in directions: + n_x, n_y = x + d[0], y + d[1] + if n_x >= 0 and n_x < m and n_y >= 0 and n_y < n and matrix[n_x][n_y] == -1: + matrix[n_x][n_y] = matrix[x][y] + 1 + queue.append((n_x, n_y)) + + return matrix +``` + +- 时间复杂度:$O(m * n)$ + +## 1162. 地图分析 + +[原题链接](https://leetcode-cn.com/problems/as-far-from-land-as-possible/) + +### 解一:广度优先搜索 + +- 先遍历 `grid` 一次,把陆地都存入队列,作为 BFS 第一层 +- 将队列中的陆地元素取出,分别向上下左右走一步,如果遇到了海洋则继续加入队列,作为 BFS 下一层 + - 遇到的海洋标记为 `2` 防止重复遍历 + +这样以来,BFS 走的最大层数就是最后要求的最远距离(将 曼哈顿距离 转为步数处理)。 + +```python +class Solution: + def maxDistance(self, grid: List[List[int]]) -> int: + m = len(grid) + if m == 0: + return -1 + n = len(grid[0]) + queue = [] + for i in range(m): + for j in range(n): + if grid[i][j] == 1: + # 发现陆地,加入队列 + queue.append([i, j]) + + if len(queue) == 0 or len(queue) == m * n: + return -1 + + distance = -1 + while len(queue) > 0: + distance += 1 + length = len(queue) + for i in range(length): + # 取出所有位置 + first = queue[0] + x, y = first[0], first[1] + del queue[0] + # 上下左右四个方向 + if x - 1 >= 0 and grid[x - 1][y] == 0: + # 避免重复判断 + grid[x - 1][y] = 2 + queue.append([x - 1, y]) + if x + 1 < m and grid[x + 1][y] == 0: + grid[x + 1][y] = 2 + queue.append([x + 1, y]) + if y - 1 >= 0 and grid[x][y - 1] == 0: + grid[x][y - 1] = 2 + queue.append([x, y - 1]) + if y + 1 < n and grid[x][y + 1] == 0: + grid[x][y + 1] = 2 + queue.append([x, y + 1]) + + return distance ``` \ No newline at end of file diff --git a/docs/algorithm/research/binary-search/README.md b/docs/algorithm/research/binary-search/README.md index 343470155..fc6580dc4 100644 --- a/docs/algorithm/research/binary-search/README.md +++ b/docs/algorithm/research/binary-search/README.md @@ -116,12 +116,49 @@ class Solution(object): return (c1 + c2) / 2.0 ``` +二分法递归写法: + +思想:转变为求**第 K 小数**。 + +Tips:因为长度要分奇偶讨论,因此直接取 `(m + n + 1) / 2` 与 `(m + n + 2) / 2` 的平均值,奇偶就一样了。 + +```python +class Solution: + def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: + def findKthElement(arr1, arr2, k): + # 默认 2 比 1 长 + length1, length2 = len(arr1), len(arr2) + if length1 > length2: + return findKthElement(arr2, arr1, k) + + # 如果 arr1 没有元素,直接返回 arr2 的第 k 小 + if length1 == 0: + return arr2[k - 1] + + # 等于 1 时单独处理 + if k == 1: + return min(arr1[0], arr2[0]) + + # 两者都有元素,对比 k/2 位置,但不超过数组长度 + mid1 = min(k//2, length1) - 1 + mid2 = min(k//2, length2) - 1 + if arr1[mid1] > arr2[mid2]: + # 删除 arr 的部分 + return findKthElement(arr1, arr2[mid2+1:], k - mid2 - 1) + else: + return findKthElement(arr1[mid1+1:], arr2, k - mid1 - 1) + + l1, l2 = len(nums1), len(nums2) + left, right = (l1 + l2 + 1)//2, (l1 + l2 + 2)//2 + return (findKthElement(nums1, nums2, left) + findKthElement(nums1, nums2, right)) / 2 +``` + ## 34. 在排序数组中查找元素的第一个和最后一个位置 [原题链接](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/) -### 思路 +### 解一:二分 + 线性查找 先使用二分查找法找到 target 值,然后从找到 target 值的位置往左右两侧延伸,直到寻找到两侧的边界值。 @@ -160,6 +197,43 @@ class Solution(object): return res ``` +### 解二:两侧都使用二分查找 + +```python +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + res = [-1, -1] + if len(nums) == 0: + return res + + left = 0 + right = len(nums) - 1 + while left < right: + mid = (left + right) // 2 + if nums[mid] < target: + left = mid + 1 + elif nums[mid] > target: + right = mid - 1 + else: + right = mid + if nums[left] == target: + res[0] = left + + left = 0 + right = len(nums) # 只有一个数的时候需要取到右边界 + while left < right: + mid = (left + right) // 2 + if nums[mid] < target: + left = mid + 1 + elif nums[mid] > target: + right = mid + else: + left = mid + 1 + if nums[left - 1] == target: + res[1] = left - 1 + + return res +``` ## 35. 搜索插入位置 @@ -210,6 +284,64 @@ class Solution(object): return left ``` +## 50. Pow(x, n) + +[原题链接](https://leetcode-cn.com/problems/powx-n/) + +### 思路:快速幂 + +利用分治的思想,$x^2n$ 均可以写作 $x^n * x^n$。 + +递归: + + + +#### **Python** + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + def fast_pow(x, n): + if n == 0: + return 1 + + fast_res = fast_pow(x, n//2) + if n % 2 == 0: + return fast_res * fast_res + else: + return fast_res * fast_res * x + + res = fast_pow(x, abs(n)) + return res if n > 0 else 1/res +``` + +#### **Go** + +```go +func myPow(x float64, n int) float64 { + if n >= 0 { + return quickMul(x, n) + } + return 1 / quickMul(x, -n) +} + +func quickMul(x float64, n int) float64 { + if n == 0 { + return 1 + } + if n == 1 { + return x + } + y := quickMul(x, n / 2) + if n % 2 == 0 { + return y * y + } else { + return y * y * x + } +} +``` + + ## 69. x 的平方根 @@ -219,6 +351,10 @@ class Solution(object): 二分查找,注意边界值的处理。 + + +#### **Python** + ```python class Solution(object): def mySqrt(self, x): @@ -245,6 +381,30 @@ class Solution(object): ``` +#### **Go** + +```go +func mySqrt(x int) int { + left := 0 + right := x + ans := 0 + for left <= right { + mid := (left + right) / 2 + // fmt.Println(mid) + if mid * mid <= x { + // 可能出现结果 + ans = mid + left = mid + 1 + } else { + right = mid - 1 + } + } + return ans +} +``` + + + ps:看评论有很秀的牛顿迭代法,有空研究下。 ## 74. 搜索二维矩阵 @@ -328,6 +488,102 @@ func searchMatrix(matrix [][]int, target int) bool { } ``` +## 81. 搜索旋转排序数组 II + +[原题链接](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) + +### 思路 + +划分出有序数组进行二分查找。 + +当出现重复元素时,即 `nums[left] == nums[mid] && nums[right] == nums[mid]`,无法判断数组哪侧有序,应从两端往中间缩短查找范围。 + +```go +func search(nums []int, target int) bool { + length := len(nums) + left := 0 + right := length - 1 + + for left <= right { + mid := (left + right) / 2 + if nums[mid] == target { + return true + } + if nums[left] == nums[mid] && nums[right] == nums[mid] { + left++ + right-- + } else if nums[left] <= nums[mid] { + // 左侧更小 + // 左侧区间是否连续 + if target >= nums[left] && target < nums[mid] { + right = mid - 1 + } else { + left = mid + 1 + } + } else { + // 左侧更大 + // 右侧区间是否连续 + if target > nums[mid] && target <= nums[length - 1] { + left = mid + 1 + } else { + right = mid - 1 + } + } + } + + return false +} +``` + +## 153. 寻找旋转排序数组中的最小值 + +[原题链接](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) + +### 思路 + +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + left = 0 + right = len(nums) - 1 + while left < right: + mid = (left + right) // 2 + if nums[mid] < nums[right]: + # mid 可能是最小值 + right = mid + else: + # 最小值在 mid 右侧 + left = mid + 1 + return nums[left] +``` + +## 154. 寻找旋转排序数组中的最小值 II + +[原题链接](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) + +## 思路 + +同 153,重点在于如何处理相等情况。 + +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + length = len(nums) + left = 0 + right = length - 1 + while left < right: + mid = (left + right) // 2 + if nums[mid] < nums[right]: + # 在左侧 + right = mid + elif nums[mid] > nums[right]: + # 在右侧 + left = mid + 1 + else: + right -= 1 + return nums[left] +``` + ## 162. 寻找峰值 [原题链接](https://leetcode-cn.com/problems/find-peak-element/) @@ -434,6 +690,109 @@ class Solution(object): return left ``` +## 367. 有效的完全平方数 + +[原题链接](https://leetcode-cn.com/problems/valid-perfect-square/) + +### 解一:暴力破解 + +#### **Python** + +```python +class Solution: + def isPerfectSquare(self, num: int) -> bool: + i = 1 + while i * i <= num: + if i * i == num: + return True + i += 1 + + return False +``` + +#### **Go** + +```go +func isPerfectSquare(num int) bool { + i := 1 + for i * i <= num { + if i * i == num { + return true + } + i++ + } + return false +} +``` + +### 解二:二分查找 + +#### **Python** + +```python +class Solution: + def isPerfectSquare(self, num: int) -> bool: + left = 1 + right = num + + while left < right: + mid = (left + right) >> 1 + tmp = mid * mid + if tmp < num: + left = mid + 1 + elif tmp > num: + right = mid - 1 + else: + return True + + return left * left == num +``` + +缩小查找范围:`right = num / 2` + +```python +class Solution: + def isPerfectSquare(self, num: int) -> bool: + if num < 2: + return True + left = 1 + right = num // 2 + + while left <= right: + mid = (left + right) // 2 + tmp = mid * mid + if tmp == num: + return True + elif tmp < num: + left = mid + 1 + else: + right = mid - 1 + return False +``` + +#### **Go** + +```go +func isPerfectSquare(num int) bool { + left := 0 + right := num + + for left < right { + mid := (left + right) >> 1 + tmp := mid * mid + if tmp < num { + left = mid + 1 + } else if tmp > num { + right = mid - 1 + } else { + return true + } + } + + return left * left == num +} +``` + ## 374. 猜数字大小 [原题链接](https://leetcode-cn.com/problems/guess-number-higher-or-lower/) @@ -516,6 +875,50 @@ public class Solution extends GuessGame { +## 410. 分割数组的最大值 + +[原题链接](https://leetcode-cn.com/problems/split-array-largest-sum/) + +### 解一:二分查找 + +想不到的神思路:对比分割次数,不断缩小最终答案范围。 + +在不考虑 `m` 的情况下,我们可知道最终的答案一定落在 `[max(nums), sum(nums)]` 区间内。因此,我们可以使用二分查找,在该范围内不断缩小最终答案的范围。 + +设 `left = max(nums)`,`right = sum(nums)`,`mid = (left + right) // 2`,此时假设 `mid` 是已求出的答案,即所谓「最大值」。我们可以用 `mid` 反推数组需要分割的数量 `cnt`: + +- `cnt <= m`:分割的数量过少,说明 `mid` 太大,`right = mid` +- `cnt > m`:分割数量过多,说明 `mid` 太小,`left = mid + 1` + +```python +class Solution: + def splitArray(self, nums: List[int], m: int) -> int: + # 最终结果范围:[max(nums), sum(nums)] + left = max(nums) + right = sum(nums) + while left < right: + mid = (left + right) // 2 + # 根据此 mid 反求划分数量 + cnt, total = 1, 0 + for num in nums: + total += num + if total > mid: + # 划分 + cnt += 1 + total = num + + if cnt < m: + # 划分少了,mid 太大,往左边找 + right = mid + elif cnt == m: + right = mid + else: + # 划分多了,mid 太小,往右边找 + left = mid + 1 + + return left +``` + ## 475. 供暖器 [原题链接](https://leetcode-cn.com/problems/heaters/) @@ -637,4 +1040,258 @@ func getMax(a int, b int) int { } ``` - \ No newline at end of file + + +## 658. 找到 K 个最接近的元素 + +[原题链接](https://leetcode-cn.com/problems/find-k-closest-elements/) + +### 解一:二分查找 + +在 `[0, length - k]` 区间查找最终答案的最左边界,对比方法是比较 `arr[mid] - x`(记作 `diff_left`) 和 `arr[mid + k] - x` (记作 `diff_right`)的大小: + +1. `diff_left < diff_right`:说明此时 `arr[mid]` 距离 `x` 更近些,可以继续向左边扩展(但保留此 `mid` 位置),`right = mid` +2. `diff_left == diff_right`:此时两处一样近,可以继续向左边扩展(但保留此 `mid` 位置),`right = mid` +3. `diff_left > diff_right`:说明 `arr[mid + k]` 距离 `x` 更近些,向右边扩展,`left = mid + 1` + +```python +class Solution: + def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]: + length = len(arr) + left = 0 + right = length - k + # 找到距离 k 最近的左边界 + while left < right: + mid = (left + right) // 2 + left_diff = x - arr[mid] + right_diff = arr[mid + k] - x + print(left, right, left_diff, right_diff) + if left_diff < right_diff: + # 左边离 x 比较近,区间可以继续向左扩张 + right = mid + # pass + elif left_diff == right_diff: + # 两边一样近,可以往左(取小值) + right = mid + else: + # 右边离 x 比较近,区间向右扩张 + left = mid + 1 + + return arr[left:left + k] +``` + +## 719. 找出第 k 小的距离对 + +[原题链接](https://leetcode-cn.com/problems/find-k-th-smallest-pair-distance/) + +### 解一:二分查找 + 双指针 + +- k 值落在 `[0, max(nums) - min(nums)]` 中,此为二分查找范围 +- 以此范围内的值查找小于该值的距离对数量 + +```python +class Solution: + def smallestDistancePair(self, nums: List[int], k: int) -> int: + nums.sort() + + def get_mid_count(target): + # 双指针 + i, count = 0, 0 + for j in range(1, len(nums)): + while nums[j] - nums[i] > target: + i += 1 + count += j - i + return count + + # 第 k 小距离在 [left, right] 之间 + left = 0 + right = nums[-1] - nums[0] + while left < right: + mid = (left + right) // 2 + # 计算小于等于 mid 的数量 + count = get_mid_count(mid) + if count >= k: + # 如果数量大于 k,说明 mid 大了或就是结果 + right = mid + else: + # 如果数量小于 k,说明 mid 小了 + left = mid + 1 + return left +``` + +## 744. 寻找比目标字母大的最小字母 + +[原题链接](https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/) + +### 思路 + +给你一个**排序后的字符列表**,和一个**目标字母**,要查找比这个字母**大**的**最小字母**,很容易就想到**二分查找**。 + +因为「在比较时,字母是依序循环出现的」,所以我们可以先处理特殊情况:**当我们要查找的字母大于等于列表的最后一个字母时,直接返回列表第一字母**。 + +下面说说二分查找。 + +首先定义好模板: + +```python +left = 0 +right = length + +while left < right: + mid = (left + rigt) // 2 + letter = letters[mid] + # 其他逻辑…… +``` + +此处我们取到的中间值 `letter` 与 `target` 存在三种大小关系: + +- `letter == target`:两者相等。此时说明我们已经找到了目标字母,但我们要找的是比目标字母大的最小字母,所以我们需要把查找范围变成 `mid` 右侧,继续向更大的字母范围处查找,即 `left = mid + 1` +- `letter < target`:当前字母小于目标字母,说明目标值在 `mid` 右侧,同上 `left = mid + 1` +- `letter > target`:当前字母大于目标字母。此时当前字母可能就是我们要找的最终答案了,但也有可能答案在左侧范围,所以 `right = mid`,**保留当前字母位置**,且继续向左侧查找 + +### 具体实现 + +```python +class Solution: + def nextGreatestLetter(self, letters: List[str], target: str) -> str: + length = len(letters) + # 循环的特殊情况 + if letters[length - 1] <= target: + return letters[0] + left = 0 + right = length - 1 + while left < right: + mid = (left + right) // 2 + letter = letters[mid] + if letter == target: + # 等于目标值,往右找 + left = mid + 1 + elif letter < target: + # 比目标值小,往右找 + left = mid + 1 + else: + # 比目标值大,可能就是要找的数! + right = mid + return letters[left] +``` + +## 1095. 山脉数组中查找目标值 + +[原题链接](https://leetcode-cn.com/problems/find-in-mountain-array/) + +### 思路 + +核心思想二分法: + +1. 通过二分法寻找峰值 +2. 二分法在峰值左侧寻找目标 +3. 如果目标不在左侧,使用二分法在峰值右侧寻找目标 + +```python +# """ +# This is MountainArray's API interface. +# You should not implement it, or speculate about its implementation +# """ +#class MountainArray: +# def get(self, index: int) -> int: +# def length(self) -> int: + +class Solution: + def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int: + length = mountain_arr.length() + # print(length) + # 找到峰值 + left = 0 + right = length - 1 + while left < right: + mid = (left + right) // 2 + if mountain_arr.get(mid + 1) > mountain_arr.get(mid): + # 峰值在右侧 + left = mid + 1 + else: + right = mid + # 峰值 + peak = left + + # 左侧二分查找 + left = 0 + right = peak + while left <= right: + mid = (left + right) // 2 + cur = mountain_arr.get(mid) + if cur == target: + return mid + elif cur > target: + right = mid - 1 + else: + left = mid + 1 + + # 右边二分查找:递减数组 + left = peak + 1 + right = length - 1 + while left <= right: + mid = (left + right) // 2 + cur = mountain_arr.get(mid) + if cur == target: + return mid + elif cur > target: + left = mid + 1 + else: + right = mid - 1 + + return -1 +``` + +## 1283. 使结果不超过阈值的最小除数 + +[原题链接](https://leetcode-cn.com/problems/find-the-smallest-divisor-given-a-threshold/) + +### 读题 + +简单得说,就是找到一个正整数 `x`,把 `nums` 中的数依次除以该数,相加之和 `total` 需要小于阈值 `threshold`,且要求 `x` 最小。可知当 `total` 越接近 `threshold` 时,`x` 越小。 + +### 思路 + +因为数据量的原因(1 <= nums.length <= 5 * 10^4),我们不能通过暴力破解枚举所有可能的 `x`,可以想到应当使用二分查找,那么二分查找的区间如何定义呢? + +左侧值 `left` 选取最小正整数 1,而右侧值 `right` 可以选择数组 `nums` 中的最大数 `max(nums)`。当 `x` 取值为 `max(nums)` 时,结果和 `total` 等于数组长度,若此时 `x` 再继续增加,`total` 则始终保持不变。因此右侧区间取值 `max(nums)`,再取更大的值则没有意义。 + +### 实现 + +```go +func smallestDivisor(nums []int, threshold int) int { + // 寻找最大数 + maxNum := 1 + for _, num := range nums { + if num > maxNum { + maxNum = num + } + } + + left := 1 + right := maxNum + ans := 0 + for left <= right { + mid := (left + right) / 2 + // 计算总数 + total := 0 + for _, num := range nums { + total += num / mid + if num % mid > 0 { + total += 1 + } + } + + if total <= threshold { + // 找到满足条件的 mid 了,但还希望能找到更小的:往左边找 + right = mid - 1 + ans = mid + } else { + // total 太大了,应该要提高 mid:往右边找 + left = mid + 1 + } + } + + return ans +} +``` \ No newline at end of file diff --git a/docs/algorithm/research/dfs/README.md b/docs/algorithm/research/dfs/README.md index d5ec77434..7f8bfccff 100644 --- a/docs/algorithm/research/dfs/README.md +++ b/docs/algorithm/research/dfs/README.md @@ -117,4 +117,89 @@ class Solution(object): return for i in xrange(index, len(nums)): self.dfs(target - nums[i], i + 1, tmp_list + [nums[i]], res, nums) +``` + +## 365. 水壶问题 + +[原题链接](https://leetcode-cn.com/problems/water-and-jug-problem/) + +### 解一:深度优先搜索 + +列举每次倒水的几种情况: + +1. 把 x 倒空 +2. 把 x 倒满 +3. 把 y 倒空 +4. 把 y 倒满 +5. 把 x 倒入 y,直到 y 满或 x 空 +6. 把 y 倒入 x,直到 x 满或 y 空 + +因为 Python 中会超过最大递归次数,所以用栈模拟递归过程。 + +```python +class Solution: + def canMeasureWater(self, x: int, y: int, z: int) -> bool: + # (x 剩余,y 剩余) + stack = [(0, 0)] + # 标记重复 + mark = set() + while len(stack) > 0: + remain_x, remain_y = stack.pop() + if remain_x == z or remain_y == z or remain_x + remain_y == z: + return True + # 是否已标记过该情况 + if (remain_x, remain_y) in mark: + continue + mark.add((remain_x, remain_y)) + # 分情况讨论 + # 倒满 x + stack.append((x, remain_y)) + # 倒满 y + stack.append((remain_x, y)) + # 清空 x + stack.append((0, remain_y)) + # 清空 y + stack.append((remain_x, 0)) + # 把 x 倒进 y,直到 y 满或 x 空 + stack.append((remain_x - min(remain_x, y - remain_y), remain_y + min(remain_x, y - remain_y))) + # 把 y 倒进 x,直到 x 满或 y 空 + stack.append((remain_x + min(remain_y, x - remain_x), remain_y - min(remain_y, x - remain_x))) + return False +``` + +## 695. 岛屿的最大面积 + +[原题链接](https://leetcode-cn.com/problems/max-area-of-island/) + +### 深度优先搜索 + +设计一个递归函数:输入坐标 `(i, j)`,返回该位置能构成岛屿的最大面积。 + +- 如果该位置不是岛屿,返回 0 +- 如果该位置是岛屿,计算其上下左右位置的面积并相加(此过程不断递归) + +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + res = 0 + for i in range(len(grid)): + for j in range(len(grid[0])): + res = max(self.dfs(grid, i, j), res) + return res + + def dfs(self, grid, i, j): + """ + 返回面积 + """ + if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != 1: + return 0 + # 避免重复计算 + grid[i][j] = 0 + # 是岛屿,面积为 1 + ans = 1 + # 四个方向 + for di, dj in [[0, -1], [0, 1], [-1, 0], [1, 0]]: + # 计算四个方向总面积 + ans += self.dfs(grid, i + di, j + dj) + return ans ``` \ No newline at end of file diff --git a/docs/algorithm/sliding-window/README.md b/docs/algorithm/sliding-window/README.md index 2a4a3d733..2bf02ec47 100644 --- a/docs/algorithm/sliding-window/README.md +++ b/docs/algorithm/sliding-window/README.md @@ -31,6 +31,23 @@ class Solution(object): return False ``` +超时方法: + +```python +class Solution: + def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool: + length = len(nums) + i = 0 + while i < length: + j = i + 1 + while j < length and j <= i + k: + if nums[i] == nums[j]: + return True + j += 1 + i += 1 + return False +``` + ## 239. 滑动窗口最大值 [原题链接](https://leetcode-cn.com/problems/sliding-window-maximum/) @@ -212,4 +229,34 @@ class Solution: ``` - 时间复杂度:$O(l1 + (l2 - l1))$($l1$ 为字符串 s1 长度,$l2$ 为字符串 s2 长度) -- 空间复杂度:$O(1)$ \ No newline at end of file +- 空间复杂度:$O(1)$ + +## 1248. 统计「优美子数组」 + +[原题链接](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/) + +### 滑动窗口 + +记录奇数的位置。固定 k 个奇数,子数组的个数 = 第一个奇数左边偶数的个数 * 最后一个奇数右边偶数的个数。 + +```python +class Solution: + def numberOfSubarrays(self, nums: List[int], k: int) -> int: + ans = 0 + # 开始位置 + odd = [-1] + # 记录奇数下标 + for i in range(len(nums)): + if nums[i] % 2 == 1: + odd.append(i) + # 添加结束位置 + odd.append(len(nums)) + + # 遍历奇数数组 + for j in range(1, len(odd) - k): + # 从第 i 个到 i + k - 1 + # 第 i 个奇数前面的偶数个数 * 第 i + k - 1 后面的偶数个数 + ans += (odd[j] - odd[j - 1]) * (odd[j + k] - odd[j + k - 1]) + + return ans +``` \ No newline at end of file diff --git a/docs/data-structure/array/README.md b/docs/data-structure/array/README.md index 3c3bf4f51..829b141f9 100644 --- a/docs/data-structure/array/README.md +++ b/docs/data-structure/array/README.md @@ -260,22 +260,106 @@ class Solution: return i + 1 ``` +优化 + +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + i = 0 + for j in range(1, len(nums)): + # 两者不想等时 + if nums[i] != nums[j]: + i += 1 + nums[i] = nums[j] + return i + 1 +``` + ## 27. 移除元素 [原题链接](https://leetcode-cn.com/problems/remove-element/description/) -### 思路 +### 题意解读 -双指针 i, j。 +划一下题目的重点: -- 初始:`i = 0, j = 0` -- 若 `nums[j] != val` - - `nums[i] = nums[j]` - - 同步增长双指针 -- 若 `nums[j] == val` - - j 变为快指针:`j = j + 1` - -### Python +- 原地移除 +- 不要使用额外的数组空间 +- 不需要考虑数组中超出新长度后面的元素 + +题目要求我们原地删除所有等于 `val` 的元素,不能使用额外空间,且**不用考虑删除后超出新数组长度后面的元素**。 + +也就是说,如果原数组 `nums` 长度为 `x`,要删除的 `val` 元素个数为 `y`,那么我们只要**把这 `n` 个要删除的元素所在位置用其他有效元素覆盖掉**,然后返回最终的数组长度 `x - y`。 + +**题目并非让我们真的删除数组的元素,而是要改写相关元素的值。** + +### 思路阐述 + +那么要如何进行元素的改写呢? + +既然要让 `val` 元素都堆在数组尾部,那么我们就派出一个开拓者探路,**只要遇到非 `val` 元素,就把它覆盖到前面来**。 + +因此,我们可以定义两个指针: + +- 快指针 `j`:用于寻找非 `val` 元素 +- 慢指针 `i`:当 `j` 找到非 `val` 元素时,就被非 `val` 元素覆盖 + +### 图解思路 + +以题中的 `nums = [3,2,2,3], val = 3` 为例。 + +开始时 `i` 和 `j` 都指向下标 0 位置: + +![初始化时,i = 0, j = 0](https://user-gold-cdn.xitu.io/2019/11/21/16e8e78d6bf1eaee?w=425&h=179&f=png&s=8968) + +此时 `j` 指向的元素为 `val`,所以把 `j` 右移动 1 位: + +![把快指针 j 右移一位](https://user-gold-cdn.xitu.io/2019/11/21/16e8e79c1dd17340?w=425&h=179&f=png&s=8969) + +此时,开拓者 `j` 找到了一个非 `val` 元素,那么就赋值给 `i` 吧: + +![赋值得到新序列](https://user-gold-cdn.xitu.io/2019/11/21/16e8e95a2b497021?w=425&h=251&f=png&s=12570) + +赋值以后,我们得到了一个新的序列 `[2, 2, 2, 3]`,我们可以得知: + +- `j` 指向的元素一定不是 `val` +- `i` 指向的元素也一定不是 `val`,因为它是从 `j` 指向的元素赋值得来的,`j` 指向非 `val` 元素才会进行赋值 + +这样一来,`i` 和 `j` 都完成了本轮使命,继续前进! + +因此每次交换以后,我们都同步增长双指针,令 `i = i + 1`,`j = j + 1`: + +![同步增长双指针](https://user-gold-cdn.xitu.io/2019/11/21/16e8e97cf4ef1990?w=425&h=225&f=png&s=9812) + +此时 `j` 又指向了一个非 `val` 元素,继续赋值: + +![再一次赋值得到新序列](https://user-gold-cdn.xitu.io/2019/11/21/16e8e989fd262a29?w=425&h=246&f=png&s=12623) + +因为本次 `i` 与 `j` 指向元素相同,所以赋值后序列没有改变。赋值操作后,我们继续同步增长双指针: + +![同步增长双指针](https://user-gold-cdn.xitu.io/2019/11/21/16e8e9a17c6ecae5?w=425&h=227&f=png&s=9909) + +此时 `j` 指向了一个 `val` 元素,无法进行赋值操作,继续增长 `j`,令 `j = j + 1`: + +![j 超出数组范围](https://user-gold-cdn.xitu.io/2019/11/21/16e8e9b72d7141a7?w=489&h=226&f=png&s=10105) + +此时我们发现 `j` 超出数组范围了,循环结束。`[2, 2, 2, 3]` 即为我们最终所求结果,而红色部分即为新数组长度,长度为 `len(nums) - (j - i)`。 + +### 总结一下 + +设置双指针 `i` 和 `j`,其中,`j` 用于寻找非 `val` 元素,来覆盖 `i` 所指向的元素。 + +- 初始时:设 `i = 0, j = 0` +- 遍历数组: + - 若 `nums[j] != val`: + - 把 `j` 的值赋给 `i`:`nums[i] = nums[j]` + - 同步增长双指针:`i = i + 1, j = j + 1` + - 若 `nums[j] == val`: + - `j` 变为快指针:`j = j + 1`,寻找下一个非 `val` 元素 + + + + +#### **Python** ```python # python3 @@ -301,6 +385,54 @@ class Solution: return res ``` +优化: + +```python +class Solution: + def removeElement(self, nums: List[int], val: int) -> int: + i = 0 + for j in range(len(nums)): + if nums[j] == val: + continue + nums[i], nums[j] = nums[j], nums[i] + i += 1 + return i +``` + +#### **Go** + +```go +func removeElement(nums []int, val int) int { + length := len(nums) + if length == 0 { + return 0 + } + + i := 0 + j := 0 + for j < length { + if nums[j] == val { + // 去找一个不是 val 的值 + j++ + } else { + // 赋值 + nums[i] = nums[j] + i++ + j++ + } + } + + return length - (j - i) +} +``` + + + +### 复杂度 + +- 时间复杂度:`O(n)` +- 空间复杂度:`O(1)`,没有使用到额外空间。 + ## 33. 搜索旋转排序数组 @@ -315,6 +447,10 @@ class Solution: - 有序:使用二分查找 - 无序:继续划分 + + +#### **Python** + ```python class Solution(object): def search(self, nums, target): @@ -345,6 +481,88 @@ class Solution(object): return -1 ``` +#### **Go** + +```go +func search(nums []int, target int) int { + length := len(nums) + left := 0 + right := length - 1 + for left <= right { + mid := (left + right) / 2 + // fmt.Println(left, right, mid) + if nums[mid] == target { + return mid + } + if nums[mid] <= nums[right] { + // 右边是顺序数组 + if nums[mid] <= target && target <= nums[right] { + // 判断是否在右边 + left = mid + 1 + } else { + right = mid - 1 + } + } else { + // 左边是顺序数组 + if nums[left] <= target && target <= nums[mid] { + // 判断是否在左边 + right = mid - 1 + } else { + left = mid + 1 + } + } + } + + return -1 +} +``` + + + +---- + +2020.04.27 复盘: + +想复杂了: + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + length = len(nums) + left = 0 + right = length - 1 + while left <= right: + mid = (left + right) // 2 + # print(left, right, mid) + if nums[mid] == target: + return mid + if nums[left] <= nums[right]: + # 顺序数组 + if nums[mid] > target: + right = mid - 1 + else: + left = mid + 1 + else: + # 非顺序数组:左侧的值比右边的大,说明旋转点在中间 + if target < nums[left] and target > nums[right]: + # 这种情况找不到 + return -1 + if nums[mid] >= nums[left]: + # 旋转点在右边 + if target <= nums[right] or target > nums[mid]: + # 寻找的点比中间值大或比右侧点小 + left = mid + 1 + else: + right = mid - 1 + else: + # 旋转点在左边 + if target < nums[mid] or target >= nums[left]: + right = mid - 1 + else: + left = mid + 1 + return -1 +``` + ## 36. 有效的数独 @@ -461,6 +679,50 @@ class Solution(object): return area ``` +### 解二:单调栈 + +维护一个单调栈:即从栈底到栈顶元素逐渐减小。 + +在遍历墙的过程中: + +1. 墙的高度小于或等于栈顶元素:入栈,此时不会产生积水 +2. 墙的高度大于栈顶元素:栈顶元素出栈,此时栈顶元素处会产生积水,产生积水的面积为:(该位置左右墙体的最小高度 - 栈顶元素高度)* 左右墙体距离差 + +```python +class Solution: + def trap(self, height: List[int]) -> int: + length = len(height) + if length == 0: + return 0 + + stack = [] + # 第一个元素入栈 + stack.append(0) + ans = 0 + for i in range(1, length): + # 栈顶元素 + top = stack[-1] + h = height[i] + # 循环 pop + while len(stack) != 0 and height[stack[-1]] < h: + # 弹出栈顶元素 + tmp = stack.pop() + # 存在墙才计算积水 + if len(stack) == 0: + break + # 计算左右两侧高度 min + diff = min(height[stack[-1]], h) + distance = i - stack[-1] - 1 + ans += distance * (diff - height[tmp]) + + stack.append(i) + + return ans +``` + +- 时间复杂度:$O(n)$ +- 空间复杂度:$O(n)$ + ## 48. 旋转图像 @@ -554,6 +816,24 @@ class Solution(object): return ans.values() ``` +普通写法,序列化为字符串: + +```python +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + table = dict() + for string in strs: + sort_string = "".join(sorted(string)) + if sort_string not in table: + table[sort_string] = [] + table[sort_string].append(string) + ans = [x for x in table.values()] + return ans +``` + +- 时间复杂度:O(n*klogk)(`n` 是 `strs` 长度,`k` 是 `strs` 中字符串的最大长度) +- 空间复杂度:O(n*k),存储在 `ans` 中的所有信息内容 + ### 参考资料 - [Python中collections.defaultdict()使用](https://www.jianshu.com/p/26df28b3bfc8) @@ -669,6 +949,10 @@ class Solution(object): return res ``` +复杂度: + +- 时间复杂度:$O(nlogn)$ +- 空间复杂度:$O(n)$ ## 66. 加一 @@ -749,24 +1033,36 @@ class Solution(object): - 此时要删除 `nums[i + 2]` 就要向后找到一个 `nums[j]`(满足 `nums[j] != nums[i]`)替换 `nums[i + 2]` ```python -class Solution(object): - def removeDuplicates(self, nums): - """ - :type nums: List[int] - :rtype: int - """ - length = len(nums) - - if length <= 2: - return length - +# 这个好理解一些 +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: i = 0 - for j in range(2, length): + for j in range(2, len(nums)): if nums[i] != nums[j]: nums[i + 2] = nums[j] - i = i + 1 - - return i+2 + i += 1 + # i 指向最后一个被交换的元素 + return i + 2 +``` + +补充解法: + +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + i, count = 1, 1 + for j in range(1, len(nums)): + if nums[j] == nums[j - 1]: + count += 1 + else: + count = 1 + + if count <= 2: + # 相同的数 <= 2,跳过 i + nums[i] = nums[j] + i += 1 + + return i ``` @@ -849,6 +1145,44 @@ class Solution(object): return res ``` +优化: + +#### **Python** + +```python +class Solution: + def generate(self, numRows: int) -> List[List[int]]: + ans = [] + for i in range(numRows): + tmp = [1 for _ in range(i + 1)] + for j in range(1, i): + tmp[j] = ans[i - 1][j - 1] + ans[i - 1][j] + ans.append(tmp) + + return ans +``` + +#### **Go** + +```go +func generate(numRows int) [][]int { + var res [][]int = make([][]int, numRows) + + for i := 0; i < numRows; i++ { + res[i] = make([]int, i + 1) + for j := 0; j < i + 1; j++ { + if j == 0 || j == i { + res[i][j] = 1 + } else { + res[i][j] = res[i - 1][j - 1] + res[i - 1][j] + } + } + } + + return res +} +``` + ## 119. 杨辉三角 II @@ -886,6 +1220,32 @@ class Solution(object): return cur ``` +### 解二:递归 + +```python +class Solution: + def generate(self, numRows: int) -> List[List[int]]: + ans = [] + # 递归:输入上一行,返回下一行 + def helper(pre): + length = len(pre) + if length == numRows: + return + if length == 0: + nxt = [1] + elif length == 1: + nxt = [1, 1] + else: + nxt = [1] + for i in range(length - 1): + nxt.append(pre[i] + pre[i + 1]) + nxt.append(1) + ans.append(nxt) + helper(nxt) + + helper([]) + return ans +``` ## 121. 买卖股票的最佳时机 @@ -924,6 +1284,10 @@ class Solution: 2019.01.05 复盘打卡 + + +#### **Python** + ```python class Solution(object): def maxProfit(self, prices): @@ -947,16 +1311,37 @@ class Solution(object): return max_val ``` +#### **Go** +```go +func maxProfit(prices []int) int { + dayCount := len(prices) + if dayCount == 0 { + return 0 + } + res := 0 + min := prices[0] + for i := 1; i < dayCount; i++ { + get := prices[i] - min + if get > res { + res = get + } + if prices[i] < min { + min = prices[i] + } + } + return res +} +``` + + ## 167. 两数之和 II - 输入有序数组 [原题链接](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/comments/) -### 思路 - -双指针 +### 解一:双指针 - 指针 i 指向最左端,从左到右依次循环 - 指针 j 指向最右端,从右到左依次循环 @@ -990,13 +1375,40 @@ class Solution(object): return [i + 1, j + 1] ``` +- 时间复杂度 `O(n)` +- 空间复杂度 `O(1)` + +### 解二:二分查找 + +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + length = len(numbers) + i = 0 + while i < length: + j = i + 1 + target_j = target - numbers[i] + left = j + right = length - 1 + while left <= right: + mid = (left + right) // 2 + if numbers[mid] == target_j: + return [i + 1, mid + 1] + elif numbers[mid] < target_j: + left = mid + 1 + else: + right = mid - 1 + i += 1 +``` +- 时间复杂度 `O(nlogn)` +- 空间复杂度:`O(1)` -## 169. 求众数 +## 169. 多数元素 [原题链接](https://leetcode-cn.com/problems/majority-element/) -### 解法一 +### 解法一:哈希表 计算每个数出现的个数,使用了额外空间。 @@ -1021,7 +1433,7 @@ class Solution(object): return n ``` -### 解法二 +### 解法二:摩尔投票法 摩尔投票法,相互抵消。 @@ -1049,6 +1461,14 @@ class Solution(object): return major ``` +### 解法三:分治 + +@TODO + +### 解法四:排序 + +@TODO + ## 189. 旋转数组 @@ -1390,6 +1810,25 @@ class Solution: j = j + 1 ``` +优化:让 `j` 跳过 0。 + +```python +class Solution: + def moveZeroes(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + i = 0 + for j in range(len(nums)): + if nums[j] == 0: + continue + else: + nums[i], nums[j] = nums[j], nums[i] + i += 1 + + return nums +``` + ## 287. 寻找重复数 @@ -1449,6 +1888,9 @@ class Solution: 将数组看成链表,val 是结点值也是下个节点的地址。因此这个问题就可以转换成判断链表有环,且找出入口节点。 +1. 如果有环:快慢指针在某一点相遇 +2. 此时再把其中一个指针移到起点,另一个指针移到相遇点(开始绕环跑),那么两个指针必然会在入口相遇 + ```python class Solution(object): def findDuplicate(self, nums): @@ -2411,6 +2853,338 @@ class Solution: return num ``` +## 807. 保持城市天际线 + +[原题链接](https://leetcode-cn.com/problems/max-increase-to-keep-city-skyline/) + +### 思路 + +找出每一行、每一列的天际线,每个位置建筑的最大高度不能超过它所在行列的天际线。 + + + +#### **Python** + +```python +class Solution: + def maxIncreaseKeepingSkyline(self, grid: List[List[int]]) -> int: + res = 0 + m = len(grid) + n = len(grid[0]) + + top = [0 for _ in range(n)] + left = [0 for _ in range(m)] + + for i in range(m): + for j in range(n): + g = grid[i][j] + left[i] = max(left[i], g) + top[j] = max(top[j], g) + + for i in range(m): + for j in range(n): + g = grid[i][j] + res += min(left[i], top[j]) - g + + return res +``` + +#### **Go** + +```go +func maxIncreaseKeepingSkyline(grid [][]int) int { + res := 0 + m := len(grid) + n := len(grid[0]) + + left := make([]int, m) + top := make([]int, n) + + for i := 0; i < m; i++ { + for j := 0; j < n; j++ { + g := grid[i][j] + left[i] = getMax(g, left[i]) + top[j] = getMax(g, top[j]) + } + } + + for i := 0; i < m; i++ { + for j := 0; j < n; j++ { + g := grid[i][j] + res += getMin(left[i], top[j]) - g + } + } + + return res +} + +func getMax(a int, b int) int { + if a > b { + return a + } else { + return b + } +} + +func getMin(a int, b int) int { + if a < b { + return a + } else { + return b + } +} +``` + +## 830. 较大分组的位置 + +[原题链接](https://leetcode-cn.com/problems/positions-of-large-groups/) + +### 顺序遍历 + +判断前后两个连续字母是否相同,若字母连续且当前字母连续出现次数已 >=3,那么加入结果集中。 + +```python +class Solution: + def largeGroupPositions(self, s: str) -> List[List[int]]: + length = len(s) + ans = [] + count = 1 + for i in range(length): + if i == length - 1 or s[i] != s[i + 1]: + # 出现不连续字母 + if count >= 3: + ans.append([i - count + 1, i]) + count = 1 + else: + count += 1 + + return ans +``` + +## 832. 翻转图像 + +[原题链接](https://leetcode-cn.com/problems/flipping-an-image/) + +### 思路 + +遍历二维数组,利用双指针进行元素的原地替换。 + +```python +class Solution: + def flipAndInvertImage(self, A: List[List[int]]) -> List[List[int]]: + for line in A: + line_length = len(line) + for i in range(line_length): + j = line_length - 1 - i + if i < j: + line[i], line[j] = line[j], line[i] + line[i] = 1 - line[i] + line[j] = 1 - line[j] + elif i == j: + line[i] = 1 - line[i] + + return A +``` + +## 867. 转置矩阵 + +[原题链接](https://leetcode-cn.com/problems/transpose-matrix/) + +### 思路 + +模拟矩阵转置过程,即二维数组下标参数兑换:`ans[j][i] = matrix[i][j]`。 + + + +#### **Python** + +```python +class Solution: + def transpose(self, matrix: List[List[int]]) -> List[List[int]]: + m = len(matrix) + n = len(matrix[0]) + ans = [[0 for _ in range(m)] for _ in range(n)] + + for i in range(m): + for j in range(n): + ans[j][i] = matrix[i][j] + + return ans +``` + +#### **Go** + +```go +func transpose(matrix [][]int) [][]int { + m := len(matrix) + n := len(matrix[0]) + ans := make([][]int, n) + // 初始化 + for i := range ans { + ans[i] = make([]int, m) + for j := range ans[i] { + ans[i][j] = 0 + } + } + + // 赋值 + for i, row:= range matrix { + for j, v := range row { + ans[j][i] = v + } + } + + return ans +} +``` + + + +## 914. 卡牌分组 + +[原题链接](https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/) + +### 解一:暴力枚举 + +```python +class Solution: + def hasGroupsSizeX(self, deck: List[int]) -> bool: + if len(deck) == 0: + return False + min_num = 10000 + max_num = 0 + num_map = dict() + for d in deck: + if d not in num_map: + num_map[d] = 0 + num_map[d] += 1 + + for num_count in num_map.values(): + min_num = min(min_num, num_count) + max_num = max(max_num, num_count) + + if min_num < 2: + return False + + for i in range(2, max_num + 1): + mark = True + for num_count in num_map.values(): + if num_count < i or num_count % i != 0: + mark = False + break + if mark: + return True + return False +``` + +- 时间复杂度:$O(n^2)$ +- 空间复杂度:$O(n)$ + +### 解二:求最大公约数 + +要求的 x 必须是所有卡牌数量的约数。 + +@TODO + +## 945. 使数组唯一的最小增量 + +[原题链接](https://leetcode-cn.com/problems/minimum-increment-to-make-array-unique/) + +### 解一:计数 + +一开始暴力破解超时了,优化一下采用以下写法: + +1. 先把重复出现的数字存储起来 +2. 遍历 0~80000 的数字(最坏情况出现 40000 个 40000,最多可以叠加到 80000) + - 如果发现数字是重复出现过的: + - 将记录重复出现数字的 `repeat` 变量 + 1 + - 在结果 `res` 提前减去 `重复个数 * 重复数字` + - 如果发现是空的位置: + - 用空的位置处理一个重复出现的数字:`repeat -= 1` + - 在 `res` 加上空位的数字 + +```python +class Solution: + def minIncrementForUnique(self, A: List[int]) -> int: + count = [0 for _ in range(80000)] + for x in A: + count[x] += 1 + + # 总数 + res = 0 + # 重复数量 + repeat = 0 + + for i in range(80000): + if count[i] > 1: + # 出现重复 + repeat += count[i] - 1 + # 减去多余的数 + res -= i * (count[i] - 1) + # 没有出现过的数 + if count[i] == 0 and repeat > 0: + repeat -= 1 # 处理一个重复的数 + res += i + + return res +``` + +## 999. 车的可用捕获量 + +[原题链接](https://leetcode-cn.com/problems/available-captures-for-rook/) + +### 思路 + +找车的四个方向是否能直接碰到卒。 + +```python +class Solution: + def numRookCaptures(self, board: List[List[str]]) -> int: + # 找到车的位置(R) + m = len(board) + if m == 0: + return 0 + n = len(board[0]) + r_x, r_y = -1, -1 + for i in range(m): + for j in range(n): + if board[i][j] == 'R': + r_x, r_y = i, j + break + res = 0 + # 找左侧 + for j in range(r_y - 1, -1, -1): + if board[r_x][j] == 'B': + break + if board[r_x][j] == 'p': + res += 1 + break + # 找右侧 + for j in range(r_y + 1, n): + if board[r_x][j] == 'B': + break + if board[r_x][j] == 'p': + res += 1 + break + # 找上侧 + for i in range(r_x - 1, -1, -1): + if board[i][r_y] == 'B': + break + if board[i][r_y] == 'p': + res += 1 + break + # 找下侧 + for i in range(r_x + 1, m): + if board[i][r_y] == 'B': + break + if board[i][r_y] == 'p': + res += 1 + break + + return res +``` + + ## 1002. 查找常用字符 @@ -2449,4 +3223,78 @@ class Solution(object): return tmp_string ``` +## 1013. 将数组分成和相等的三个部分 + +[原题链接](https://leetcode-cn.com/problems/partition-array-into-three-parts-with-equal-sum/) + +### 思路 + +1. 数组求和,看是否可以整除三,不能整除则直接返回 `false` +2. 双指针指向数组前后,前后都分别开始找累加和等于目标值的部分,如果前后都能找到,则中间的自动就找到了 + +注意问题: + +1. 存在 `[1,-1,1,-1]` 这样的用例,需要判断双指针最终位置,相距是否大于 1,即中间是否还有位置 +2. 目标值可以为 0,所以左右区间的数据应当初始化为 `A[0]` 与 `A[length - 1]` + +```python +class Solution: + def canThreePartsEqualSum(self, A: List[int]) -> bool: + # 求总和 + s = sum(A) + if s % 3 != 0: + return False + part = s // 3 + + # 双指针 + i = 0 + j = len(A) - 1 + # 计算左右区间和 + left_part = A[i] + right_part = A[j] + res = False + while i < j: + if left_part != part: + i += 1 + left_part += A[i] + + if right_part != part: + j -= 1 + right_part += A[j] + + if left_part == part and right_part == part: + res = True + break + # print(i, j) + # 处理特殊情况:[1,-1,1,-1] + return res and j - i > 1 +``` + +## 1071. 字符串的最大公因子 + +[原题链接](https://leetcode-cn.com/problems/greatest-common-divisor-of-strings/) + +### 暴力枚举 + +最大公因子必定是 `str1` 或 `str2` 的前缀,因此枚举前缀的长度并截取前缀进行匹配即可。 + +```python +class Solution: + def gcdOfStrings(self, str1: str, str2: str) -> str: + length1 = len(str1) + length2 = len(str2) + for i in range(min(length1, length2), 0, -1): + # 枚举 + if length1 % i == 0 and length2 % i == 0: + # 是否可构成前缀 + if str1[:i] * (length1 // i) == str1 and str1[:i] * (length2 // i) == str2: + return str1[:i] + return '' +``` + +- 时间复杂度: +- 空间复杂度: + +### 数学法 +若存在 `X`,那么有 `str1 + str2 = str2 + str1`。 \ No newline at end of file diff --git a/docs/data-structure/graph/README.md b/docs/data-structure/graph/README.md new file mode 100644 index 000000000..1ea43c32a --- /dev/null +++ b/docs/data-structure/graph/README.md @@ -0,0 +1,160 @@ +## 133. 克隆图 + +[原题链接](https://leetcode-cn.com/problems/clone-graph/) + +### DFS + +用字典 `mark` 记录遍历过的节点,`mark[node] = clone` 用于对应 `node` 的拷贝节点 `clone`。 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val = 0, neighbors = []): + self.val = val + self.neighbors = neighbors +""" +class Solution: + def cloneGraph(self, node: 'Node') -> 'Node': + # 记录访问过的节点 + mark = dict() + + def dfs(node): + if node is None: + return node + if node in mark: + return mark[node] + + clone = Node(node.val, []) + mark[node] = clone # node 对应的克隆节点为 clone,记录在字典中 + for n in node.neighbors: + clone.neighbors.append(dfs(n)) + + # 返回克隆节点 + return clone + + return dfs(node) +``` + +### BFS + +用辅助队列实现 BFS。 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val = 0, neighbors = []): + self.val = val + self.neighbors = neighbors +""" +class Solution: + def cloneGraph(self, node: 'Node') -> 'Node': + if node is None: + return node + + # 记录 node 对应的拷贝 + mark = dict() + # 辅助队列 + queue = list() + queue.append(node) + clone = Node(node.val, []) + + mark[node] = clone + + while len(queue) > 0: + # 取出一个节点 + node = queue[0] + del queue[0] + # 遍历邻接节点 + for n in node.neighbors: + if n not in mark: + mark[n] = Node(n.val, []) + # 新节点入队列 + queue.append(n) + mark[node].neighbors.append(mark[n]) + + return clone +``` + +## 210. 课程表 II + +[原题链接](https://leetcode-cn.com/problems/course-schedule-ii/) + +### 思路 + +在图中找到循环。 + +```python +class Solution: + def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: + length = len(prerequisites) + if length == 0: + return [i for i in range(numCourses)] + # n = len(prerequisites[0]) + + # 存储依赖关系 + degree = [0 for _ in range(numCourses)] + relations = dict() + for pre in prerequisites: + nxt = pre[0] + cur = pre[1] + # 依赖关系 + if cur not in relations: + relations[cur] = [nxt] + else: + relations[cur].append(nxt) + # nxt 课程的度 + 1 + degree[nxt] += 1 + + + # 入度为 0 的入队 + queue = [] + for i in range(numCourses): + if degree[i] == 0: + queue.append(i) + + # 遍历队列 + ans = [] + while len(queue) > 0: + first = queue[0] + del queue[0] + # if first not in ans: + ans.append(first) + # 获取下一批度为 0 的课程入队 + for course in relations.get(first, []): + degree[course] -= 1 + if degree[course] == 0: + queue.append(course) + + return ans if len(ans) == numCourses else [] +``` + +## 997. 找到小镇的法官 + +[原题链接](https://leetcode-cn.com/problems/find-the-town-judge) + +### 思路 + +这是一道关于图的题。 + +用一个二维数组存储每个节点的入度和出度,入度 = N - 1 且 出度 = 0 的节点为法官。 + + +```python +class Solution: + def findJudge(self, N: int, trust: List[List[int]]) -> int: + node = [[0, 0] for _ in range(N + 1)] + + # 记录 + for t in trust: + node[t[0]][0] += 1 + node[t[1]][1] += 1 + + for i in range(1, N + 1): + n = node[i] + if n[1] == N - 1 and n[0] == 0: + return i + + return -1 +``` \ No newline at end of file diff --git a/docs/data-structure/hash/README.md b/docs/data-structure/hash/README.md index 52ee3309e..d5663217e 100644 --- a/docs/data-structure/hash/README.md +++ b/docs/data-structure/hash/README.md @@ -2,7 +2,22 @@ [原题链接](https://leetcode-cn.com/problems/two-sum/submissions/) -### 解法一 +### 解法一:暴力 + +```python +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + length = len(nums) + for i in range(length): + for j in range(i + 1, length): + if nums[i] + nums[j] == target: + return [i, j] +``` + +- 时间复杂度:$O(n^2)$ +- 空间复杂度:$O(1)$ + +### 解法二:一遍哈希 利用哈希表,时间复杂度 O(n),空间复杂度 O(n)。 @@ -34,15 +49,20 @@ class Solution(object): return res_list ``` -### 解法二 - -时间复杂度 O(nlogn),空间复杂度 O(1)。 - -- 排序 -- 二分查找 - - +2020.06.02 复盘: +```python +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + table = dict() + for i in range(len(nums)): + n = nums[i] + tmp = target - n + if tmp in table: + return [i, table[tmp]] + else: + table[n] = i +``` ## 128. 最长连续序列 @@ -113,6 +133,38 @@ class Solution(object): return max_length ``` +## 205. 同构字符串 + +[原题链接](https://leetcode-cn.com/problems/isomorphic-strings/) + +### 思路 + +双哈希表 + 双指针。 + +```python +class Solution: + def isIsomorphic(self, s: str, t: str) -> bool: + length1 = len(s) + length2 = len(t) + if length1 != length2: + return False + table1 = dict() + table2 = dict() + for i in range(length1): + if s[i] not in table1: + # 没有映射过 + table1[s[i]] = t[i] + # 判断是否进行了相同映射 + if t[i] in table2: + return False + table2[t[i]] = s[i] + else: + # 映射过了 + if t[i] != table1[s[i]]: + # 映射的字母不同 + return False + return True +``` ## 217. 存在重复元素 @@ -155,15 +207,44 @@ class Solution: return True ``` +## 349. 两个数组的交集 + +[原题链接](https://leetcode-cn.com/problems/intersection-of-two-arrays/) + +### 解一:内置函数 + +```python +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + return list(set(nums1) & set(nums2)) +``` + +#### 复杂度 + +- 时间复杂度:将数组转为 `set` 的复杂度为 `O(n)`,转化两个数组时间复杂度 `O(m) + O(n)` + - 平均情况下:`O(m + n)` + - 最坏情况下:`O(m * n)` +- 空间复杂度:最坏情况 `O(m + n)`(数组元素都不同的情况) + +### 解二:排序 + 双指针 + +先对二者排序,使用双指针滑动查找。 + +### 解三:排序 + 二分查找 + +一个数组排序(短数组?),另一数组内的元素使用二分查找。 ## 350. 两个数组的交集 II [原题链接](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/) -### 解法一 +### 解法一:哈希 使用 hash 表的方式。 +- 遍历较短数组,将数据存储在哈希表中,存储的值为数字出现的次数 +- 遍历较长数组,查询数据是否在哈希表出现过 + 空间复杂度 `O(n)`,时间复杂度 `O(n)`。 ```python @@ -191,10 +272,136 @@ class Solution(object): return res ``` -### 解法二 +#### 复杂度 + +- 时间复杂度:`O(m + n)` +- 空间复杂度:`O(min(m, n))`(不会超过短数组长度) + +### 解法二:排序 + 双指针 排序 + 双指针,不需要额外空间。 +```python +class Solution: + def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: + nums1.sort() + nums2.sort() + i = 0 + j = 0 + length1 = len(nums1) + length2 = len(nums2) + res = [] + while i < length1 and j < length2: + if nums1[i] == nums2[j]: + res.append(nums1[i]) + i += 1 + j += 1 + elif nums1[i] < nums2[j]: + i += 1 + else: + j += 1 + return res +``` + +#### 复杂度 + +- 事件复杂度:`mO(logm) + nO(logn)` +- 空间复杂度:`O(len(res))`(结果答案长度) + +## 355. 设计推特 + +[原题链接](https://leetcode-cn.com/problems/design-twitter/) + +### 偷懒排序法 + +```python +class Node: + def __init__(self, time, tweet_id, nxt): + """ + 链表节点 + """ + self.time = 0 + self.nxt = nxt + self.tweet_id = tweet_id + +class Twitter: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.time = 0 + # 用户哈希 + self.tweets = dict() + self.followers = dict() + + + def postTweet(self, userId: int, tweetId: int) -> None: + """ + Compose a new tweet. + """ + # node = Node(self.time, tweetId, None) + # if userId in self.tweets: + # head = self.tweet[userId] + # node.nxt = head + # self.tweets[userId] = node + # self.time += 1 + if userId not in self.tweets: + self.tweets[userId] = [] + self.tweets[userId].append([self.time, tweetId]) + self.time += 1 + + + def getNewsFeed(self, userId: int) -> List[int]: + """ + Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. + """ + ans = [] + # 取出关注的用户 + followers = self.followers.get(userId, set()) + # print(self.tweets) + tweets = [] + if userId in self.tweets: + tweets = self.tweets[userId] + # print(tweets) + # print(followers) + for follower in followers: + # tweets.extend(self.tweets[follower]) + if follower in self.tweets: + tweets = tweets + self.tweets[follower] + tweets.sort(key=lambda x: x[0], reverse=True) + # print(tweets) + # print(tweets) + # print([x[1] for x in tweets[:10]]) + return [x[1] for x in tweets[:10]] + + def follow(self, followerId: int, followeeId: int) -> None: + """ + Follower follows a followee. If the operation is invalid, it should be a no-op. + """ + if followerId == followeeId: + # 不允许自己关注自己 + return + if followerId not in self.followers: + self.followers[followerId] = set() + self.followers[followerId].add(followeeId) + + + def unfollow(self, followerId: int, followeeId: int) -> None: + """ + Follower unfollows a followee. If the operation is invalid, it should be a no-op. + """ + if followerId not in self.followers: + return + self.followers[followerId].discard(followeeId) + +# Your Twitter object will be instantiated and called as such: +# obj = Twitter() +# obj.postTweet(userId,tweetId) +# param_2 = obj.getNewsFeed(userId) +# obj.follow(followerId,followeeId) +# obj.unfollow(followerId,followeeId) +``` ## 454. 四数相加 II @@ -231,6 +438,83 @@ class Solution(object): return count ``` +## 560. 和为K的子数组 + +[原题链接](https://leetcode-cn.com/problems/subarray-sum-equals-k/) + +### 思路一:暴力破解 + +1. 固定左边界 +2. 枚举右边界 + +```go +func subarraySum(nums []int, k int) int { + length := len(nums) + ans := 0 + for left := 0; left < length; left++ { + s := 0 + for right := left; right < length; right++ { + s += nums[right] + if s == k { + ans += 1 + } + } + } + return ans +} +``` + +- 时间复杂度:$O(n^2)$ +- 空间复杂度:$O(1)$ + +### 思路二:前缀和 + 哈希 + +用 `pre[i]` 表示 `[0, ..., i]` 的和。 + +因此,`[j, ..., i]` 的和为 k 可以表示为:`pre[i] - pre[j - 1] == k`。 + +移项后:`pre[j - 1] == pre[i] - k`,因此,只要统计 `pre[i] - k` 即可。 + + + +#### **Python** + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + ans, pre = 0, 0 + nums_dict = dict() + nums_dict[0] = 1 + for i in range(len(nums)): + pre += nums[i] + ans += nums_dict.get(pre - k, 0) + nums_dict[pre] = nums_dict.get(pre, 0) + 1 + return ans +``` + +#### **Go** + +```go +func subarraySum(nums []int, k int) int { + length := len(nums) + pre := 0 + ans := 0 + m := map[int]int{} + m[0] = 1 + for i := 0; i < length; i++ { + pre += nums[i] + if _, ok := m[pre - k]; ok { + ans += m[pre - k] + } + m[pre] += 1 + } + return ans +} +``` + + + + ## 575. 分糖果 [原题链接](https://leetcode-cn.com/problems/distribute-candies/) @@ -440,4 +724,336 @@ class Solution(object): return max_length ``` +# 599. 两个列表的最小索引总和 + +[原题链接](https://leetcode-cn.com/problems/minimum-index-sum-of-two-lists/) + +## 解一:哈希表 + +```python +class Solution: + def findRestaurant(self, list1: List[str], list2: List[str]) -> List[str]: + table1 = dict() + table2 = dict() + length1 = len(list1) + length2 = len(list2) + for i in range(length1): + word = list1[i] + table1[word] = i + + min_index = float('inf') + for j in range(length2): + word = list2[j] + if word in table1: + table2[word] = j + table1.get(word) + if table2[word] < min_index: + min_index = table2[word] + + ans = [] + for k, v in table2.items(): + if v == min_index: + ans.append(k) + + return ans +``` + +## 609. 在系统中查找重复文件 + +[原题链接](https://leetcode-cn.com/problems/find-duplicate-file-in-system/) + +### 思路 + +使用哈希表实现。将文件内容作为哈希 key 值,文件路径数组作为 value。 + +使用 Go 语言的哈希 + 数组表示:`contentMap := make(map[string][]string)`。 + +### 实现 + +```go +import "strings" + +func findDuplicate(paths []string) [][]string { + contentMap := make(map[string][]string) + ans := make([][]string, 0) + for _, pathString := range paths { + // 路径分割 + paths := strings.Split(pathString, " ") + document := "" + for idx, path := range paths { + if idx == 0 { + document = path + } else { + // 按括号切分 + contents := strings.Split(path, "(") + filePath := contents[0] + content := contents[1][:len(contents[1]) - 1] + + fullPath := document + "/" + filePath + contentMap[content] = append(contentMap[content], fullPath) + } + } + } + + for _, contents := range contentMap { + if len(contents) >= 2 { + ans = append(ans, contents) + } + } + + return ans +} +``` + +## 652. 寻找重复的子树 + +[原题链接](https://leetcode-cn.com/problems/find-duplicate-subtrees/) + +### 解一:序列化,结果存放在哈希表中 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + + def findDuplicateSubtrees(self, root: TreeNode) -> List[TreeNode]: + count = dict() + ans = list() + def collect(node): + if node is None: + return '#' + left_serial = collect(node.left) + right_serial = collect(node.right) + serial = "{},{},{}".format(node.val, left_serial, right_serial) + count[serial] = count.get(serial, 0) + 1 + if count[serial] == 2: + ans.append(node) + return serial + + collect(root) + return ans +``` + +## 705. 设计哈希集合 + +[原题链接](https://leetcode-cn.com/problems/design-hashset/) + +### 解一:数组模拟 + +此题应当要用普通的数据结构实现哈希集合。 + +题目指出「所有的值都在 `[0, 1000000]` 的范围内」,因此我们可以设计 100 个桶,每个桶内容纳 10000 个值,正好可以容纳下所有的数据。 + +哈希函数的映射规则:`hash_table[key % bucket][key // bucket]` + +```python +class MyHashSet: + def __init__(self): + """ + Initialize your data structure here. + """ + # 给出 100 个桶 + self.bucket = 100 + # 桶内元素 + self.bucket_num = 10000 + # 初始化 + self.hash_table = [[] for _ in range(self.bucket)] + + def add(self, key: int) -> None: + if not self.hash_table[key % self.bucket]: + self.hash_table[key % self.bucket] = [0] * self.bucket_num + self.hash_table[key % self.bucket][key // self.bucket] = 1 + + def remove(self, key: int) -> None: + if self.hash_table[key % self.bucket]: + self.hash_table[key % self.bucket][key // self.bucket] = 0 + + def contains(self, key: int) -> bool: + """ + Returns true if this set contains the specified element + """ + if not self.hash_table[key % self.bucket]: + return False + return self.hash_table[key%self.bucket][key//self.bucket] == 1 + + +# Your MyHashSet object will be instantiated and called as such: +# obj = MyHashSet() +# obj.add(key) +# obj.remove(key) +# param_3 = obj.contains(key) +``` + +## 706. 设计哈希映射 + +[原题链接](https://leetcode-cn.com/problems/design-hashmap/) + +### 思路 + +1. 哈希映射设计:如何通过 `key` 找到合适的桶 +2. 哈希碰撞处理:许多 `key` 都映射在一个桶里,如何进行这些值的存储与查找 + +```python +class Bucket: + def __init__(self): + self.bucket = [] + + def put(self, key, value): + for i in range(len(self.bucket)): + k, v = self.bucket[i][0], self.bucket[i][1] + if k == key: + self.bucket[i] = (key, value) + return + self.bucket.append((key, value)) + + def get(self, key): + for i in range(len(self.bucket)): + k, v = self.bucket[i][0], self.bucket[i][1] + if k == key: + return v + return -1 + + def remove(self, key): + for i, kv in enumerate(self.bucket): + if key == kv[0]: + # 删除元素问题 + del self.bucket[i] + +class MyHashMap: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.bucket_count = 2069 + self.hash_map = [Bucket() for _ in range(self.bucket_count)] + + + def put(self, key: int, value: int) -> None: + """ + value will always be non-negative. + """ + bucket_num = key % self.bucket_count + self.hash_map[bucket_num].put(key, value) + + + def get(self, key: int) -> int: + """ + Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key + """ + bucket_num = key % self.bucket_count + return self.hash_map[bucket_num].get(key) + + + def remove(self, key: int) -> None: + """ + Removes the mapping of the specified value key if this map contains a mapping for the key + """ + bucket_num = key % self.bucket_count + self.hash_map[bucket_num].remove(key) + + + +# Your MyHashMap object will be instantiated and called as such: +# obj = MyHashMap() +# obj.put(key,value) +# param_2 = obj.get(key) +# obj.remove(key) +``` + +### 解二:链表模拟 + +## 1160. 拼写单词 + +[原题链接](https://leetcode-cn.com/problems/find-words-that-can-be-formed-by-characters/) + +### 思路 + +使用哈希表存储 `chars` 中每个字符出现的次数,在遍历 `words` 时判断每个单词的字母是否可以命中 `chars` 的哈希表。 + + + +#### **Python** + +```python +class Solution: + def countCharacters(self, words: List[str], chars: str) -> int: + res = 0 + word_dict = dict() + # 词汇统计 + for c in chars: + if c not in word_dict: + word_dict[c] = 0 + word_dict[c] += 1 + + # 开始拼写 + for word in words: + tmp_word_dict = word_dict.copy() + get = True + for c in word: + if c in tmp_word_dict: + if tmp_word_dict[c] == 0: + get = False + break + else: + tmp_word_dict[c] -= 1 + else: + get = False + break + if get: + res += len(word) + + return res +``` + +#### **Go** + +```go +func countCharacters(words []string, chars string) int { + var res int + var wordMap = make(map[string]int) + // 统计 chars + for _, c := range chars { + if _, ok := wordMap[string(c)]; !ok { + // 不存在 + wordMap[string(c)] = 0 + } + wordMap[string(c)] += 1 + } + + // 统计 word 单词数,与统计比较 + for _, word := range words { + everyWordMap := make(map[string]int) + for _, c := range word { + if _, ok := everyWordMap[string(c)]; !ok { + everyWordMap[string(c)] = 0 + } + everyWordMap[string(c)] += 1 + } + + // 判断是否命中 + get := true + for c, count := range everyWordMap { + if mapCount, ok := wordMap[string(c)]; ok { + // 存在 + if count > mapCount { + get = false + break + } + } else { + get = false + break + } + } + if get { + res += len(word) + } + } + return res +} +``` + \ No newline at end of file diff --git a/docs/data-structure/linked_list/README.md b/docs/data-structure/linked_list/README.md index e2f051d7a..b3f5e70b1 100644 --- a/docs/data-structure/linked_list/README.md +++ b/docs/data-structure/linked_list/README.md @@ -132,9 +132,6 @@ class Solution(object): return head ``` - - - ## 21. 合并两个有序链表 [原题链接](https://leetcode-cn.com/problems/merge-two-sorted-lists/description/) @@ -167,17 +164,14 @@ class Solution(object): return head ``` - ## 24. 两两交换链表中的节点 -### 思路 +### 解一:迭代 - 为方便统一化创建空节点 `cur` - 交换呗,没啥好说 - 注意返回头节点 -### Python - ```python class Solution(object): def swapPairs(self, head): @@ -199,6 +193,210 @@ class Solution(object): return first.next ``` +### 解二:递归 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def swapPairs(self, head: ListNode) -> ListNode: + def helper(node, pre): + if node is None or node.next is None: + return + next_node = node.next + # 指针交换 + node.next = next_node.next + next_node.next = node + pre.next = next_node + helper(node.next, node) + + ans = ListNode(0) + ans.next = head + helper(head, ans) + return ans.next +``` + +优雅递归: + + + +#### **Python** + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def swapPairs(self, head: ListNode) -> ListNode: + if head is None or head.next is None: + return head + + first_node = head + second_node = head.next + + first_node.next = self.swapPairs(second_node.next) + second_node.next = first_node + + return second_node +``` + +#### **Go** + +```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func swapPairs(head *ListNode) *ListNode { + if head == nil || head.Next == nil { + return head + } + + firstNode := head + secondNode := head.Next + + firstNode.Next = swapPairs(secondNode.Next) + secondNode.Next = firstNode + + return secondNode +} +``` + + + +## 25. K 个一组翻转链表 + +[原题链接](https://leetcode-cn.com/problems/reverse-nodes-in-k-group/) + +### 思路 + +模拟过程。 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseKGroup(self, head: ListNode, k: int) -> ListNode: + cur_idx = 0 + ans = ListNode(0) + + # 计算链表长度 + length = 0 + tmp = head + while tmp is not None: + length += 1 + tmp = tmp.next + + # 上一个节点 + pre = None + pre_range_tail, range_head = ans, ans + while head is not None and cur_idx + k <= length: + for i in range(k): + if i == 0: + # 记录头部指针 + range_head = head + if i == k - 1: + # 记录尾部节点 + range_tail = head + # 记录已遍历节点数量 + cur_idx += 1 + # 获取下一个节点 + next_node = head.next + # 反转链表,指向上一个节点 + head.next = pre + # 当前节点成为下一轮的「上一个节点」 + pre = head + # 继续遍历下一个节点 + head = next_node + # 前后两个链表相连 + pre_range_tail.next = range_tail + # print(ans) + pre_range_tail = range_head + # print(ans) + # 一轮结束,改变指针连接 + # range_head.next = head + # 一轮结束后,改变 pre 指向 + pre = None + + # 如果有剩余节点,继续连接 + range_head.next = head + + return ans.next +``` + +- 事件复杂度:O(n) +- 空间复杂度:O(1) + +## 61. 旋转链表 + +[原题链接](https://leetcode-cn.com/problems/rotate-list/solution/chuan-zhen-yin-xian-by-liweiwei1419/) + +### 思路 + +双指针 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def rotateRight(self, head: ListNode, k: int) -> ListNode: + # 倒数第 n + 1 个节点变成尾部 + # 修改导出第 n + 1 个节点的 next 指针,指向 Null + # 修改尾节点指针,指向头部 + if head is None: + return None + # 计算链表长度 + tmp = head + length = 0 + while tmp.next is not None: + length += 1 + tmp = tmp.next + tail = tmp + length += 1 + + # 计算移动位 + k %= length + # 不需要移动 + if k == 0: + return head + + i = head + j = head + + # j 先走 k 步 + for l in range(k): + j = j.next + + while j.next is not None: + j = j.next + i = i.next + + # 得到 i + i_next = i.next + tail.next = head + i.next = None + + return i_next +``` + ## 83. 删除排序链表中的重复元素 @@ -230,6 +428,121 @@ class Solution(object): return head ``` +## 86. 分隔链表 + +[原题链接](https://leetcode-cn.com/problems/partition-list/) + +### 思路 + +创建两个链表。比 `x` 小的节点放在左链表中,比 `x` 大的节点放在右链表中,最后将两个链表连接。 + +⚠️注:记得把链表尾部置空。 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def partition(self, head: ListNode, x: int) -> ListNode: + left = ListNode(0) + right = ListNode(0) + + res = left + right_head = right + + while head is not None: + if head.val < x: + left.next = head + left = left.next + else: + right.next = head + right = right.next + head = head.next + + # 拼接链表 + left.next = right_head.next + right.next = None # 防止形成环 + + return res.next +``` + +## 92. 反转链表 II + +[原题链接](https://leetcode-cn.com/problems/reverse-linked-list-ii/) + +### 思路 + +需要找到几个关键节点: + +1. 开始翻转的节点 `begin` +2. 开始翻转节点的前一个节点 `begin_pre` +3. 结束翻转的节点 `end` +4. 结束翻转节点的下一个节点 `end_next` + +从 `begin` 到 `end` 位置执行反转操作。此段链表翻转后需要: + +1. 修改 `begin_pre` 节点的指针,指向 `end` 节点:`begin_pre.next = end` +2. 修改 `begin` 节点的指针,指向 `end_next` 节点:`begin.next = end_next` + +注意:`m` 可能会等于 `n`。 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode: + if head is None: + return head + + if m == n: + return head + + index = 0 + pre = None + cur = head + begin_pre = None + begin = None + end = None + end_next = None + + while cur is not None: + index += 1 + next_cur = cur.next + + if index == m: + # 记录开始节点 + begin = cur + if index == m - 1: + begin_pre = cur + if index == n: + # 记录结束位置 + end = cur + if index == n + 1: + end_next = cur + + if index > m and index <= n: + # 翻转 + cur.next = pre + pre = cur + cur = next_cur + + # 修改指针位置 + begin.next = end_next + if begin_pre is None: + # 从头开始翻转了 + return end + else: + begin_pre.next = end + return head +``` + ## 138. 复制带随机指针的链表 @@ -322,12 +635,136 @@ class Solution(object): return True ``` +另一种写法: + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def hasCycle(self, head: ListNode) -> bool: + slow = head + fast = head + + while fast is not None and fast.next is not None: + fast = fast.next.next + slow = slow.next + if fast == slow: + return True + + return False +``` + +## 147. 对链表进行插入排序 + +[原题链接](https://leetcode-cn.com/problems/insertion-sort-list/) + +### Tail 优化版 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def insertionSortList(self, head: ListNode) -> ListNode: + if head is None or head.next is None: + return head + + # 固定头部 + new_head = ListNode(float("-inf")) + + cur = head + pre = new_head + + # 加入尾部 + tail = new_head + + while cur is not None: + if tail.val < cur.val: + tail.next = cur + tail = cur + cur = cur.next + else: + cur_next = cur.next + # 此时:tail.next = cur,会产生循环,故断开 + tail.next = cur_next + while pre.next is not None and pre.next.val < cur.val: + pre = pre.next + cur.next = pre.next + pre.next = cur + cur = cur_next + pre = new_head + + return new_head.next +``` + +## 148. 排序链表 + +[原题链接](https://leetcode-cn.com/problems/sort-list/) + +### 解一:归并 + 额外空间 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def sortList(self, head: ListNode) -> ListNode: + # 只有一个节点或没有节点时,直接返回 + if head is None or head.next is None: + return head + + # 切分 + slow = head + fast = head.next + while fast is not None and fast.next is not None: + fast = fast.next.next + slow = slow.next + + r = slow.next + slow.next = None # 切断 + left = self.sortList(head) + right = self.sortList(r) + + # 合并 + h = ListNode(0) # 构造新链表 + res = h + + while left is not None and right is not None: + if left.val < right.val: + h.next = left + left = left.next + else: + h.next = right + right = right.next + h = h.next + + # 加入未排序的部分 + h.next = left if left else right + + return res.next +``` + +### 解二:归并(无需额外空间) + +@todo ## 160. 相交链表 [原题链接](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/) -### 思路 +核心思想:消除长度差。 + +### 解法一 - 找出两个链表的长度差值 step - 快慢指针,长的链表先走 step 步,然后循环两个链表 @@ -373,12 +810,47 @@ class Solution(object): return a ``` +### 解法二 + +设两个指针 `pa` 和 `pb` 分别指向 A 链表和 B 链表的表头,然后开始遍历。 + +当 `pa` 到达末尾时,将 `pa` 重置到链表 B 的头部;当 `pb` 到达尾部时,将 `pb` 重置到链表 A 的头部。用这种方法来消除 `pa` 和 `pb` 走过长度的长度差,如果 A 和 B 相交,那么 `pa` 和 `pb` 必定相遇。 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: + pa = headA + pb = headB + + while pa != pb: + # 不相交就继续走 + if pa is None: + pa = headB + else: + pa = pa.next + + if pb is None: + pb = headA + else: + pb = pb.next + + return pa +``` + +- 时间复杂度:$O(m + n)$ +- 空间复杂度:$O(1)$ ## 206. 反转链表 -[原题链接]() +[原题链接](https://leetcode-cn.com/problems/reverse-linked-list/) -### 思路 +### 迭代法 - 给一个新的链表 - 让原链表的节点与原链表断开连接 @@ -395,14 +867,64 @@ class Solution(object): newList = None #新链表 pre = None curNode = head + while curNode: tmp = curNode.next curNode.next = newList #让当前节点与原链表断开连接 newList = curNode #赋值给新链表 curNode = tmp + return newList ``` +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if head is None: + return None + + pre = head + cur = head.next + + while cur is not None: + next_cur = cur.next + cur.next = pre + pre = cur + cur = next_cur + + head.next = None + return pre +``` + +### 递归法 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if head is None or head.next is None: + return head + + # 取下一个节点 + node = self.reverseList(head.next) + + next_node = head.next + next_node.next = head + head.next = None + + return node +``` ## 234. 回文链表 @@ -444,7 +966,90 @@ class Solution(object): - 设置快慢指针,当快指针走完时,慢指针刚好走到中点 - 原地将后半段反转,进行回文 +## 237. 删除链表中的节点 + +[原题链接](https://leetcode-cn.com/problems/delete-node-in-a-linked-list/) + +### 思路分析 + +如果我们要在链表中删除一个节点,一般的操作是: + +1. 修改要删除节点的上一个节点的指针 +2. 将该指针指向要删除节点的下一个节点 + +例如,在链表 `[4, 5, 1, 9]` 中,当我们要删除节点 `5` 时,我们会修改节点 `5` 上一个节点 `4` 的指针,让它指向节点 `5` 的下一个节点,即节点 `1`: + +![修改节点 4 的指针,让它指向节点 1](https://pic.leetcode-cn.com/188c3905565b3609d3ce670cf1b73320908de4f6e1bdea61ab3a1b7442359def-file_1574907780588) + +**但这道题只告诉我们要删除的节点,我们并不知道该节点的上一个节点是什么**,这时候又该如何是好呢? + +既然我们要删除一个节点时需要知道它的上一个节点,如果我们无法得知上一个节点,我们就**找一个可以知道上一个节点的节点,把它变成要删除的节点,然后删除它**。 + +这样听起来好像有些拗口?没事,直接看一个例子! + +还是 `[4, 5, 1, 9]` 链表,还是删除节点 `5`。 + +首先,我们把节点 `5` 下一个节点的值赋给它,把它变成一个「不需要删除」的节点: + +![把节点 5 下一个节点的值赋给它](https://pic.leetcode-cn.com/6e65c25f7a28a7c8900fb0e8b9205b91cda81d920fb0014d606f6468a7008506-file_1574907780596) + +这样一来,第二个节点 `1` 和第三个节点 `1`,无论我们删除其中的哪一个,都可以得到最终结果 `[4, 1, 9]`。既然第二个节点不好删除,那我们就果断删除第三个啦~ + +改变第二个节点 `1` 的指针,将它指向第 4 个节点 `9`,这样一来,第三个节点 `1` 就被删除了: + +![改变第 2 个节点的指针,让它指向第 4 个节点](https://pic.leetcode-cn.com/10d4294214a45a545cecb6f072dd6b01a9e090ca67bc8d22003aed2c248a6e49-file_1574907780593) + +### 具体实现 + + + +#### **Python** + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def deleteNode(self, node): + """ + :type node: ListNode + :rtype: void Do not return anything, modify node in-place instead. + """ + node.val = node.next.val + node.next = node.next.next +``` + +#### **Go** + +```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func deleteNode(node *ListNode) { + node.Val = node.Next.Val + node.Next = node.Next.Next +} +``` + + + +### 复杂度 + +- 时间复杂度 `O(1)` +- 空间复杂度 `O(1)` +### 总结一下 + +这道题没有给出链表的头节点,而是直接给出要删除的节点,让我们进行原地删除。我们对于该节点的前一个节点一无所知,所以无法直接执行删除操作。因此,**我们将要删除节点的 `next` 节点的值赋值给要删除的节点,转而去删除 `next` 节点,从而达成目的。** + +题目中指明了「给定的节点为非末尾节点」且「链表至少包含两个节点」,所以上述方案是切实可行的。 ## 328. 奇偶链表 @@ -484,8 +1089,6 @@ class Solution(object): return l1.next ``` - - ## 445. 两数相加 II ### 解法一 @@ -559,6 +1162,59 @@ class Solution(object): 考虑不反转链表实现,可以用栈,Python 中就用 list `append()` 和 `pop()` 来即可。 +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: + stack1 = list() + stack2 = list() + + while l1 is not None: + stack1.append(l1.val) + l1 = l1.next + + while l2 is not None: + stack2.append(l2.val) + l2 = l2.next + + add_num = 0 + res = None + while len(stack1) > 0 or len(stack2) > 0: + num1, num2 = 0, 0 + + if len(stack1) > 0: + num1 = stack1.pop() + + if len(stack2) > 0: + num2 = stack2.pop() + + num = num1 + num2 + add_num + if num > 9: + add_num = 1 + else: + add_num = 0 + num %= 10 + + cur = ListNode(num) + cur.next = res + res = cur + + if add_num == 1: + cur = ListNode(1) + cur.next = res + res = cur + + return res +``` + +### 解法三 + +递归 ## 725. 分隔链表 @@ -622,5 +1278,29 @@ class Solution(object): return res_list ``` +## 876. 链表的中间结点 + +[原题链接](https://leetcode-cn.com/problems/middle-of-the-linked-list/solution/) + +### 快慢指针 + +fast 比 slow 速度快 2 倍。这样一来,fast 到达链表尾部后,slow 正好到达中间: + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None +class Solution: + def middleNode(self, head: ListNode) -> ListNode: + slow = head + fast = head + while fast is not None and fast.next is not None: + fast = fast.next.next + slow = slow.next + + return slow +``` \ No newline at end of file diff --git a/docs/data-structure/queue/README.md b/docs/data-structure/queue/README.md new file mode 100644 index 000000000..cfa4eaa50 --- /dev/null +++ b/docs/data-structure/queue/README.md @@ -0,0 +1,326 @@ +## 622. 设计循环队列 + +[原题链接](https://leetcode-cn.com/problems/design-circular-queue/) + +### 解一 + +没有过多考虑,直接用 list 实现,没有践行「循环」的概念。 + +```python +class MyCircularQueue: + + def __init__(self, k: int): + """ + Initialize your data structure here. Set the size of the queue to be k. + """ + self.list_data = [] + self.list_length = k + + def enQueue(self, value: int) -> bool: + """ + Insert an element into the circular queue. Return true if the operation is successful. + """ + if len(self.list_data) >= self.list_length: + return False + self.list_data.append(value) + return True + + def deQueue(self) -> bool: + """ + Delete an element from the circular queue. Return true if the operation is successful. + """ + if len(self.list_data) > 0: + del(self.list_data[0]) + return True + return False + + def Front(self) -> int: + """ + Get the front item from the queue. + """ + if len(self.list_data) == 0: + return -1 + return self.list_data[0] + + def Rear(self) -> int: + """ + Get the last item from the queue. + """ + if len(self.list_data) == 0: + return -1 + return self.list_data[-1] + + def isEmpty(self) -> bool: + """ + Checks whether the circular queue is empty or not. + """ + return len(self.list_data) == 0 + + def isFull(self) -> bool: + """ + Checks whether the circular queue is full or not. + """ + return len(self.list_data) == self.list_length + + +# Your MyCircularQueue object will be instantiated and called as such: +# obj = MyCircularQueue(k) +# param_1 = obj.enQueue(value) +# param_2 = obj.deQueue() +# param_3 = obj.Front() +# param_4 = obj.Rear() +# param_5 = obj.isEmpty() +# param_6 = obj.isFull() +``` + +### 解二:循环队列 + +用数组来实现,「循环」的意思就是没有头和尾的区别。且设计数组时选择「浪费一个位置」,让数组满和空两种状态不冲突。 + +设两个指针,`front` 指向头部,`rear` 指向尾部。 + +- 当 `front == rear` 时队列为空 +- 当 `(rear + 1) % length == front` 时队列为满 + +```python +class MyCircularQueue: + + def __init__(self, k: int): + """ + Initialize your data structure here. Set the size of the queue to be k. + """ + # 浪费一个位置 + self.k = k + 1 + # 设置双指针 + self.front = 0 + self.rear = 0 + self.list_data = [0 for _ in range(self.k)] + + def enQueue(self, value: int) -> bool: + """ + Insert an element into the circular queue. Return true if the operation is successful. + """ + if self.isFull(): + return False + self.list_data[self.rear] = value + self.rear = (self.rear + 1) % self.k + return True + + def deQueue(self) -> bool: + """ + Delete an element from the circular queue. Return true if the operation is successful. + """ + if self.isEmpty(): + return False + self.front = (self.front + 1) % self.k + return True + + def Front(self) -> int: + """ + Get the front item from the queue. + """ + if self.isEmpty(): + return -1 + return self.list_data[self.front] + + def Rear(self) -> int: + """ + Get the last item from the queue. + """ + if self.isEmpty(): + return -1 + return self.list_data[(self.rear - 1 + self.k) % self.k] + + def isEmpty(self) -> bool: + """ + Checks whether the circular queue is empty or not. + """ + return self.front == self.rear + + def isFull(self) -> bool: + """ + Checks whether the circular queue is full or not. + """ + return (self.rear + 1) % self.k == self.front + + +# Your MyCircularQueue object will be instantiated and called as such: +# obj = MyCircularQueue(k) +# param_1 = obj.enQueue(value) +# param_2 = obj.deQueue() +# param_3 = obj.Front() +# param_4 = obj.Rear() +# param_5 = obj.isEmpty() +# param_6 = obj.isFull() +``` + +## 641. 设计循环双端队列 + +[原题链接](https://leetcode-cn.com/problems/design-circular-deque/) + +### 思路 + +同上题(622. 设计循环队列)。 + +```python +class MyCircularDeque: + + def __init__(self, k: int): + """ + Initialize your data structure here. Set the size of the deque to be k. + """ + self.k = k + 1 + self.front = 0 + self.rear = 0 + self.list_data = [0 for _ in range(self.k)] + + def insertFront(self, value: int) -> bool: + """ + Adds an item at the front of Deque. Return true if the operation is successful. + """ + if self.isFull(): + return False + self.front = (self.front - 1) % self.k + self.list_data[self.front] = value + return True + + def insertLast(self, value: int) -> bool: + """ + Adds an item at the rear of Deque. Return true if the operation is successful. + """ + if self.isFull(): + return False + self.list_data[self.rear] = value + self.rear = (self.rear + 1) % self.k + return True + + def deleteFront(self) -> bool: + """ + Deletes an item from the front of Deque. Return true if the operation is successful. + """ + if self.isEmpty(): + return False + self.front = (self.front + 1) % self.k + return True + + def deleteLast(self) -> bool: + """ + Deletes an item from the rear of Deque. Return true if the operation is successful. + """ + if self.isEmpty(): + return False + self.rear = (self.rear - 1) % self.k + return True + + def getFront(self) -> int: + """ + Get the front item from the deque. + """ + if self.isEmpty(): + return -1 + return self.list_data[self.front] + + def getRear(self) -> int: + """ + Get the last item from the deque. + """ + if self.isEmpty(): + return -1 + return self.list_data[(self.rear - 1 + self.k) % self.k] + + def isEmpty(self) -> bool: + """ + Checks whether the circular deque is empty or not. + """ + return self.front == self.rear + + def isFull(self) -> bool: + """ + Checks whether the circular deque is full or not. + """ + return (self.rear + 1) % self.k == self.front + + +# Your MyCircularDeque object will be instantiated and called as such: +# obj = MyCircularDeque(k) +# param_1 = obj.insertFront(value) +# param_2 = obj.insertLast(value) +# param_3 = obj.deleteFront() +# param_4 = obj.deleteLast() +# param_5 = obj.getFront() +# param_6 = obj.getRear() +# param_7 = obj.isEmpty() +# param_8 = obj.isFull() +``` + +## 752. 打开转盘锁 + +[原题链接](https://leetcode-cn.com/problems/open-the-lock/) + +### 思路 + +BFS + +```python +class Solution: + def openLock(self, deadends: List[str], target: str) -> int: + queue = list() + queue.append(('0000', 0)) # 0000 也有可能是死亡数字 + already = {'0000'} # 节点是否已经遍历过 + while len(queue): # 当队列不为空时,循环队列 + # 取第一个元素 + node = queue[0] + del queue[0] + # 节点判断 + if node[0] == target: + return node[1] + if node[0] in deadends: + continue + # 获取周边节点 + negihbors = self.get_neighbors(node) + for n in negihbors: + if n[0] not in already: + already.add(n[0]) + queue.append(n) + return -1 + + def get_neighbors(self, node): + number = node[0] + # print(number) + depth = node[1] + negihbors = [] + directions = {1, -1} + for i in range(len(number)): + # 循环位 + for d in directions: + new_position = str((int(number[i]) + d) % 10) + item = (number[:i] + new_position + number[i+1:], depth + 1) + negihbors.append(item) + return negihbors +``` + +## 933. 最近的请求次数 + +[原题链接](https://leetcode-cn.com/problems/number-of-recent-calls/) + +### 思路 + +```python +class RecentCounter: + + def __init__(self): + self.ping_list = [] + + def ping(self, t: int) -> int: + first = t - 3000 + self.ping_list.append(t) + while first > self.ping_list[0]: + del(self.ping_list[0]) + + return len(self.ping_list) + + +# Your RecentCounter object will be instantiated and called as such: +# obj = RecentCounter() +# param_1 = obj.ping(t) +``` \ No newline at end of file diff --git a/docs/data-structure/stack/README.md b/docs/data-structure/stack/README.md index afdfe9182..848ab68e8 100644 --- a/docs/data-structure/stack/README.md +++ b/docs/data-structure/stack/README.md @@ -47,6 +47,96 @@ class Solution(object): return True ``` +## 84. 柱状图中最大的矩形 + +[原题链接](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) + +### 解法一 + +- 单调栈 +- 逐个遍历 +- 结果:超时 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + stack = [] # 单调递增栈 + ans = 0 + for h in heights: + stack.append(h) + # if len(stack) == 0: + # stack.append(h) + # else: + # while stack[-1] > h: + # # 对比栈顶元素 + # top = stack[-1] + # # 左侧更大的数字被化为小数 + # if top > h: + # stack[-1] = h + # stack.append(h) + # print(stack) + # 计算实际面积 + stack_length = len(stack) + for i in range(stack_length): + # element = stack[i] + if stack[i] > h: + stack[i] = h + area = (stack_length - i) * stack[i] + ans = max(ans, area) + # print(ans) + + return ans +``` + +### 解法二 + +面积计算方式: + +- 找到 `heights[i]` 左侧第一个小于它的元素位置 `left` +- 找到 `heights[i]` 右侧第一个小于它的元素位置 `right` +- `area = (right - left - 1) * heights[i]` + +利用维护单调递增栈得出元素的左右边界: + +- 左侧边界(左侧第一个小于当前位置的元素)可以在元素入栈时确认 +- 右侧边界(右侧第一个小于当前位置的元素)可以在元素出栈时确认 + +```python +class Solution: + def largestRectangleArea(self, heights: List[int]) -> int: + # 找左侧第一个小于 h 的位置 left + # 右侧第一个小于 h 的位置 right + # area = (right - left) * h + + # 维护单调栈 + length = len(heights) + stack = [] + lefts = [-1 for _ in range(length)] + rights = [length for _ in range(length)] + for i in range(length): + h = heights[i] + if len(stack) == 0: + stack.append((i, h)) + else: + # 维护单调栈 + while len(stack) > 0 and stack[-1][1] >= h: + # 弹出时能确认右侧最小元素 + min_element = stack.pop() + rights[min_element[0]] = i + # 入栈,确认左侧最小元素 + if len(stack) > 0: + lefts[i] = stack[-1][0] + stack.append((i, h)) + + ans = 0 + for i in range(length): + area = (rights[i] - lefts[i] - 1) * heights[i] + # print((rights[i] - lefts[i] + 1)) + ans = max(area, ans) + + return ans +``` + ## 150. 逆波兰表达式求值 [原题链接](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) @@ -103,6 +193,10 @@ class Solution(object): - 每次入栈两个值:当前入栈元素、栈内最小值,保证栈内最小值永远在栈顶 + + +#### **Python** + ```python class MinStack(object): @@ -153,6 +247,150 @@ class MinStack(object): return self.stack[len(self.stack) - 1] ``` +#### **Go** + +```go +type MinStack struct { + Stack1 []int // 存放数据栈 + Stack2 []int // 递减栈 +} + + +/** initialize your data structure here. */ +func Constructor() MinStack { + var minStack MinStack + minStack.Stack1 = make([]int, 0) + minStack.Stack2 = make([]int, 0) + return minStack +} + + +func (this *MinStack) Push(x int) { + // 入栈 + this.Stack1 = append(this.Stack1, x) + // 维护递减栈 + stack2Length := len(this.Stack2) + if stack2Length == 0 { + this.Stack2 = append(this.Stack2, x) + } else { + // 与栈顶元素对比 + top := this.Stack2[stack2Length - 1] + if x < top { + this.Stack2 = append(this.Stack2, x) + } else { + this.Stack2 = append(this.Stack2, top) + } + } + // fmt.Println(this.Stack1) + // fmt.Println(this.Stack2) +} + + +func (this *MinStack) Pop() { + // 弹出元素 + stack1Length := len(this.Stack1) + this.Stack1 = this.Stack1[:stack1Length - 1] + stack2Length := len(this.Stack2) + this.Stack2 = this.Stack2[:stack2Length - 1] + // fmt.Println(this.Stack1) + // fmt.Println(this.Stack2) +} + + +func (this *MinStack) Top() int { + // 返回栈顶元素 + stack1Length := len(this.Stack1) + return this.Stack1[stack1Length - 1] +} + + +func (this *MinStack) GetMin() int { + // 返回 stack2 栈顶元素 + stack2Length := len(this.Stack2) + return this.Stack2[stack2Length - 1] +} + + +/** + * Your MinStack object will be instantiated and called as such: + * obj := Constructor(); + * obj.Push(x); + * obj.Pop(); + * param_3 := obj.Top(); + * param_4 := obj.GetMin(); + */ +``` + + + +## 173. 二叉搜索树迭代器 + +[原题链接](https://leetcode-cn.com/problems/binary-search-tree-iterator/) + +### 解法一 + +在构造函数中使用中序遍历将二叉搜索树转为升序序列,然后在 `next` 时依次出列。 + +但时间复杂度不符合题目要求。 + +### 解法二 + +不需要一次性生成整个序列,可以用栈模拟递归过程。 + +1. 在构造函数中:将树的所有左节点压入栈(这样最左的节点就在栈顶了) +2. 调用 `next` 时,弹出栈顶元素,此时判断该节点是否存在右节点,若存在则将右节点入栈,且将该节点的所有左节点依次入栈(中序遍历的顺序为 左->中->右,弹出的栈顶元素相当于中间节点,遍历到中间节点后就要遍历右节点了) + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class BSTIterator: + + def __init__(self, root: TreeNode): + self.stack = [] + # 左节点依次入栈 + while root is not None: + self.stack.append(root) + root = root.left + + def next(self) -> int: + """ + @return the next smallest number + """ + # 弹出栈顶 + cur = self.stack.pop() + # 判断是否存在右子树 + right = cur.right + while right is not None: + self.stack.append(right) + right = right.left + return cur.val + + + def hasNext(self) -> bool: + """ + @return whether we have a next smallest number + """ + return len(self.stack) > 0 + + + +# Your BSTIterator object will be instantiated and called as such: +# obj = BSTIterator(root) +# param_1 = obj.next() +# param_2 = obj.hasNext() +``` + +关于复杂度的分析截取一下[这篇题解](https://leetcode-cn.com/problems/binary-search-tree-iterator/solution/nextshi-jian-fu-za-du-wei-o1-by-user5707f/): + +> 但是很多小伙伴会对next()中的循环操作的复杂度感到疑惑,认为既然加入了循环在里面,那时间复杂度肯定是大于O(1)不满足题目要求的。 +> 仔细分析一下,该循环只有在节点有右子树的时候才需要进行,也就是不是每一次操作都需要循环的,循环的次数加上初始化的循环总共会有O(n)次操作,均摊到每一次 `next()` 的话平均时间复杂度则是 `O(n)/n=O(1)`,因此可以确定该实现方式满足 `O(1)` 的要求。 +>这种分析方式称为摊还分析,详细的学习可以看看《算法导论》- 第17章 摊还分析 + ## 232. 用栈实现队列 [原题链接](https://leetcode-cn.com/problems/implement-queue-using-stacks/comments/) @@ -333,6 +571,548 @@ class MyStack: return True ``` +### 双队列解法一 + +定义两个辅助队列 `queue1` 与 `queue2`,使用一个变量 `top_element` 记录栈顶元素。 + +- `push()`:将元素入队 `queue1` +- `pop()`: + - 将 `queue1` 内所有元素全部出队,除最后一个元素外,其余入队 `queue2`,而后删除最后一个元素并返回 + - 更新 `top_element` + - 调换 `queue1` 与 `queue2` +- `top()`:返回 `top_elenemt` +- `empty()`:判断 `queue1` 的长度 + + + +#### **Python** + +```python +class MyStack: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.queue1 = [] + self.queue2 = [] + self.top_element = 0 + + def push(self, x: int) -> None: + """ + Push element x onto stack. + """ + self.queue1.append(x) + self.top_element = x + + def pop(self) -> int: + """ + Removes the element on top of the stack and returns that element. + """ + # 把 queue1 里的元素取出,留下一个,其余塞入 queue2 中 + length1 = len(self.queue1) + for i in range(length1 - 1): + item = self.queue1[0] + del self.queue1[0] + self.queue2.append(item) + self.top = item + target = self.queue1[0] + del self.queue1[0] + # 交换 queue1 与 queue2 + self.queue1 = self.queue2 + self.queue2 = [] + return target + + def top(self) -> int: + """ + Get the top element. + """ + # length1 = len(self.queue1) + # for i in range(length1): + # item = self.queue1[0] + # del self.queue1[0] + # self.queue2.append(item) + # self.queue1 = self.queue2 + # self.queue2 = [] + # return item + return self.top_element + + def empty(self) -> bool: + """ + Returns whether the stack is empty. + """ + return len(self.queue1) == 0 + + +# Your MyStack object will be instantiated and called as such: +# obj = MyStack() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.top() +# param_4 = obj.empty() +``` + +#### **Go** + +```go +type MyStack struct { + Queue1 []int + Queue2 []int + TopElement int +} + + +/** Initialize your data structure here. */ +func Constructor() MyStack { + var myStack MyStack + return myStack +} + + +/** Push element x onto stack. */ +func (this *MyStack) Push(x int) { + this.Queue1 = append(this.Queue1, x) + this.TopElement = x +} + + +/** Removes the element on top of the stack and returns that element. */ +func (this *MyStack) Pop() int { + length1 := len(this.Queue1) + for i := 0; i < length1 - 1; i++ { + // 取出每个元素 + item := this.Queue1[0] + this.TopElement = item + // 删除元素 + this.Queue1 = this.Queue1[1:] + // 入队列 2 + this.Queue2 = append(this.Queue2, item) + } + target := this.Queue1[0] + // 交换 + this.Queue1 = this.Queue2 + this.Queue2 = make([]int, 0) + return target +} + + +/** Get the top element. */ +func (this *MyStack) Top() int { + return this.TopElement +} + + +/** Returns whether the stack is empty. */ +func (this *MyStack) Empty() bool { + return len(this.Queue1) == 0 +} + + +/** + * Your MyStack object will be instantiated and called as such: + * obj := Constructor(); + * obj.Push(x); + * param_2 := obj.Pop(); + * param_3 := obj.Top(); + * param_4 := obj.Empty(); + */ +``` + + + +- 时间复杂度:压入 $O(1)$,弹出 $O(n)$ + +### 双队列解法二 + +定义两个辅助队列 `queue1` 与 `queue2`,使用一个变量 `top_element` 记录栈顶元素。 + +- `push()`: + - 将元素入队 `queue2`,此时 `queue2` 中的首个元素为栈顶元素 + - 更新 `top_element` + - 此时若 `queue1` 不为空,则让 `queue1` 中的元素逐个出队并加入 `queue2` 中 +- `pop()`: `queue1` 首个元素出队,更新 `top_element` +- `top()`: 返回 `top_element` +- `empty()`: 判断 `queue1` 长度 + + + +#### **Python** + +```python +class MyStack: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.queue1 = [] + self.queue2 = [] + self.top_element = 0 + + + def push(self, x: int) -> None: + """ + Push element x onto stack. + """ + # 更新栈顶元素 + self.top_element = x + # 加入 queue2 中 + self.queue2.append(x) + if not self.empty(): + # 如果 queue1 不为空,取出元素并加入 queue2 + length1 = len(self.queue1) + for i in range(length1): + self.queue2.append(self.queue1[0]) + del self.queue1[0] + # 交换 + self.queue1 = self.queue2 + self.queue2 = [] + + + def pop(self) -> int: + """ + Removes the element on top of the stack and returns that element. + """ + target = self.queue1[0] + del self.queue1[0] + # 更新 top_element + if not self.empty(): + self.top_element = self.queue1[0] + return target + + + def top(self) -> int: + """ + Get the top element. + """ + return self.top_element + + + def empty(self) -> bool: + """ + Returns whether the stack is empty. + """ + return len(self.queue1) == 0 + + +# Your MyStack object will be instantiated and called as such: +# obj = MyStack() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.top() +# param_4 = obj.empty() +``` + +#### **Go** + +```go +type MyStack struct { + Queue1 []int + Queue2 []int + TopElement int +} + + +/** Initialize your data structure here. */ +func Constructor() MyStack { + var myStack MyStack + return myStack +} + + +/** Push element x onto stack. */ +func (this *MyStack) Push(x int) { + this.Queue2 = append(this.Queue2, x) + // 更新栈顶元素 + this.TopElement = x + if !this.Empty() { + length1 := len(this.Queue1) + for i := 0; i < length1; i++ { + this.Queue2 = append(this.Queue2, this.Queue1[0]) + // 删除元素 + this.Queue1 = this.Queue1[1:] + } + } + // 交换 + this.Queue1 = this.Queue2 + this.Queue2 = make([]int, 0) +} + + +/** Removes the element on top of the stack and returns that element. */ +func (this *MyStack) Pop() int { + target := this.Queue1[0] + this.Queue1 = this.Queue1[1:] + if !this.Empty() { + // 更新栈顶元素 + this.TopElement = this.Queue1[0] + } + return target +} + + +/** Get the top element. */ +func (this *MyStack) Top() int { + return this.TopElement +} + + +/** Returns whether the stack is empty. */ +func (this *MyStack) Empty() bool { + return len(this.Queue1) == 0 +} + + +/** + * Your MyStack object will be instantiated and called as such: + * obj := Constructor(); + * obj.Push(x); + * param_2 := obj.Pop(); + * param_3 := obj.Top(); + * param_4 := obj.Empty(); + */ +``` + + + +- 时间复杂度:压入 $O(n)$,弹出 $O(1)$ + +### 单队列解法三 + +定义辅助队列 `queue`。 + +- `push()`: + - 将元素入队 + - 除新入队元素,将其他元素从队首取出,再从队尾入队(完成反序)。此时队首元素即为新入队元素 +- `pop()`:`queue` 首个元素出队 +- `top()`:获取 `queue` 首个元素 +- `empty()`:判断 `queue` 长度 + + + +#### **Python** + +```python +class MyStack: + + def __init__(self): + """ + Initialize your data structure here. + """ + self.queue = [] + + + def push(self, x: int) -> None: + """ + Push element x onto stack. + """ + self.queue.append(x) + # 队列反序 + length = len(self.queue) + for i in range(length - 1): + first = self.queue[0] + del self.queue[0] + self.queue.append(first) + + + def pop(self) -> int: + """ + Removes the element on top of the stack and returns that element. + """ + target = self.queue[0] + del self.queue[0] + return target + + + def top(self) -> int: + """ + Get the top element. + """ + return self.queue[0] + + + def empty(self) -> bool: + """ + Returns whether the stack is empty. + """ + return len(self.queue) == 0 + + +# Your MyStack object will be instantiated and called as such: +# obj = MyStack() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.top() +# param_4 = obj.empty() +``` + +#### **Go** + +```go +type MyStack struct { + Queue []int +} + + +/** Initialize your data structure here. */ +func Constructor() MyStack { + var myStack MyStack + return myStack +} + + +/** Push element x onto stack. */ +func (this *MyStack) Push(x int) { + this.Queue = append(this.Queue, x) + length := len(this.Queue) + for i := 0; i < length - 1; i++ { + // 反序操作 + first := this.Queue[0] + this.Queue = this.Queue[1:] + this.Queue = append(this.Queue, first) + } +} + + +/** Removes the element on top of the stack and returns that element. */ +func (this *MyStack) Pop() int { + target := this.Queue[0] + this.Queue = this.Queue[1:] + return target +} + + +/** Get the top element. */ +func (this *MyStack) Top() int { + return this.Queue[0] +} + + +/** Returns whether the stack is empty. */ +func (this *MyStack) Empty() bool { + return len(this.Queue) == 0 +} + + +/** + * Your MyStack object will be instantiated and called as such: + * obj := Constructor(); + * obj.Push(x); + * param_2 := obj.Pop(); + * param_3 := obj.Top(); + * param_4 := obj.Empty(); + */ +``` + + + +- 时间复杂度:压入 $O(n)$,弹出 $O(1)$ + +## 331. 验证二叉树的前序序列化 + +[原题链接](https://leetcode-cn.com/problems/verify-preorder-serialization-of-a-binary-tree/) + +### 解一:用栈辅助 + +按字符顺序依次入栈。如果入栈元素为 `#`,就判断栈顶能否凑成 `n##` 格式(`n` 为数字),如果可以就弹出 `n##`,让 `#` 入栈。因为 `n##` 表示一个叶节点,用 `#` 替代它以便让它的父节点达成叶节点条件(以此证明它是合法节点)。 + +```python +class Solution: + def isValidSerialization(self, preorder: str) -> bool: + + stack = list() + nodes = preorder.split(",") + + for node in nodes: + self.add_item(stack, node) + # print(stack) + + return True if len(stack) == 1 and stack[-1] == "#" else False + + def add_item(self, stack, node): + if node == "#": + if len(stack) > 1: + # 判断能否凑成 x## + if stack[-1] == "#" and stack[-2] != "#": + stack.pop() + stack.pop() + # 加入新的 # + self.add_item(stack, "#") + else: + stack.append(node) + else: + stack.append(node) + else: + stack.append(node) +``` + +## 394. 字符串解码 + +[原题链接](https://leetcode-cn.com/problems/decode-string/) + +### 解一:栈 + +1. 遇到非 `]` 元素先压入栈中 +2. 遇到 `]` 时,逐个弹出栈中元素,组成需要重复的字符串 `string`,直到遇到 `[` +3. 继续弹出 `[` 前的数字 `num` +4. 将 `string` 重复 `num` 压入栈中 +5. 重复上述过程,直到栈空 + +```python +class Solution: + def decodeString(self, s: str) -> str: + stack = [] + for c in s: + if c != ']': + stack.append(c) + else: + # 从栈中弹出 [] 内的元素与字母,并做乘法操作 + string = '' + while len(stack) > 0 and stack[-1] != '[': + string = stack.pop() + string + # 弹出 [ + stack.pop() + # 弹出重复数字 + num_string = '' + while len(stack) > 0 and stack[-1].isdigit(): + num_string = stack.pop() + num_string + num = int(num_string) + stack.append(string * num) + return ''.join(stack) +``` + +优化: + +1. 将数字记录在 `multi` 中,字母记录在 `res` 中 +2. 遇到 `[` 时,将 `[multi, res]` 入栈,并重置 `multi` 和 `res` +3. 遇到 `]` 时,从栈顶取元素 `[num, string]`,并计算 `res = string + num * res` + +```python +class Solution: + def decodeString(self, s: str) -> str: + stack = [] + # 存放字符串和数字 + res, multi = '', 0 + # ans = '' + for c in s: + if c == '[': + # 已经统计的 res 和 multi 入栈 + stack.append([res, multi]) + res, multi = '', 0 + elif c == ']': + # 出栈并计算结果 + string, num = stack.pop() + res = string + res * num + elif '0' <= c and c <= '9': + # 记录数字 + multi = 10 * multi + int(c) + else: + # 遇到字母 + res += c + # print(stack) + return res +``` + ## 503. 下一个更大元素 II [原题链接](https://leetcode-cn.com/problems/next-greater-element-ii/submissions/) @@ -453,4 +1233,55 @@ class Solution(object): return res_list ``` +## 946. 验证栈序列 +[原题链接](https://leetcode-cn.com/problems/validate-stack-sequences/) + +模拟栈序列。 + +```python +class Solution: + def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: + stack = [] + push_i = 0 + pop_i = 0 + length = len(pushed) + + while 1: + # 判断栈顶元素是否为 pop 元素 + if len(stack) > 0 and stack[-1] == popped[pop_i]: + stack.pop() + pop_i += 1 + else: + # 压入新的元素 + if push_i >= length: + break + stack.append(pushed[push_i]) + push_i += 1 + + return len(stack) == 0 +``` + +## 1111. 有效括号的嵌套深度 + +[原题链接](https://leetcode-cn.com/problems/maximum-nesting-depth-of-two-valid-parentheses-strings/) + +### 解一:栈匹配 + +1. 通过栈匹配来计算每个括号所属的深度 +2. 把同一深度的括号平均分配给 A 和 B + +```python +class Solution: + def maxDepthAfterSplit(self, seq: str) -> List[int]: + depth = 0 + ans = [] + for s in seq: + if s == '(': + depth += 1 + ans.append(depth % 2) + else: + ans.append(depth % 2) + depth -= 1 + return ans +``` \ No newline at end of file diff --git a/docs/data-structure/string/README.md b/docs/data-structure/string/README.md index 0f80b77e7..5cfa658c7 100644 --- a/docs/data-structure/string/README.md +++ b/docs/data-structure/string/README.md @@ -76,7 +76,34 @@ if __name__ == '__main__': [原题链接](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/) -### 思路 +### 解一:暴力求解 + +```python +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + length = len(s) + ans = 0 + for i in range(length): + tmp = 1 + nums = dict() + nums[s[i]] = True + for j in range(i + 1, length): + c = s[j] + if c in nums: + # 已经存在 + break + else: + # 不存在,继续 + tmp += 1 + nums[c] = True + ans = max(ans, tmp) + return ans +``` + +- 时间复杂度:`O(n^2)` +- 空间复杂度:`O(m)` + +### 解二 - 用 hash 记录每个字符出现的位置 - 当前字符: @@ -108,6 +135,28 @@ class Solution(object): return max_length ``` +2020.05.02 复盘: + +```python +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + ans = 0 + # 滑动窗口,左侧:start,右侧:i + start = 0 + length = len(s) + # 记录字符所在下标 + nums = dict() + for i in range(length): + cur_c = s[i] + # print(start) + if cur_c in nums: + if nums[cur_c] >= start: + start = nums[cur_c] + 1 + # 替换字符所在位置 + nums[cur_c] = i + ans = max(ans, i - start + 1) + return ans +``` ## 6. Z 字形变换 @@ -875,6 +924,38 @@ class Solution(object): return True ``` +复盘: + +```python +class Solution: + def isPalindrome(self, s: str) -> bool: + length = len(s) + i = 0 + j = length - 1 + + while i < j: + if self.isValid(s[i]) and self.isValid(s[j]): + if s[i].lower() != s[j].lower(): + return False + i += 1 + j -= 1 + + if not self.isValid(s[i]): + i += 1 + + if not self.isValid(s[j]): + j -= 1 + return True + + def isValid(self, c): + if c.isdigit() or c.isalpha(): + return True + return False +``` + +- 时间复杂度:`O(n)` +- 空间复杂度:`O(1)` + ## 139. 单词拆分 @@ -1028,7 +1109,7 @@ class Solution(object): [原题链接](https://leetcode-cn.com/problems/valid-anagram/) -### 思路 +### 解法一:哈希 - 统计 s 和 t 中所有字母出现的次数 - 判断是否相同 @@ -1061,6 +1142,22 @@ class Solution(object): return True ``` +- 时间复杂度:O(n) +- 空间复杂度:O(n) + +### 解法二:排序 + +```python +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + s = sorted(s) + t = sorted(t) + return s == t +``` + +- 时间复杂度:O(nlogn) +- 空间复杂度:O(1) + ### 顺便复习 Python 字典 创建字典 @@ -1091,7 +1188,7 @@ for key, value in c_dict.items(): [原题链接](https://leetcode-cn.com/problems/reverse-string/) -### 思路 +### 解一:双指针 头尾双指针,不停交换并往中间靠拢。 @@ -1113,6 +1210,48 @@ class Solution(object): j = j - 1 ``` +### 解二:递归 + + + +#### **Python** + +```python +class Solution: + def reverseString(self, s: List[str]) -> None: + """ + Do not return anything, modify s in-place instead. + """ + def helper(left, right): + if left >= right: + return + s[left], s[right] = s[right], s[left] + helper(left + 1, right - 1) + + helper(0, len(s) - 1) +``` + +#### **Go** + +```go +func reverseString(s []byte) { + helper(0, len(s) - 1, s) +} + +func helper(left int, right int, s []byte) { + if left >= right { + return + } + + tmp := s[left] + s[left] = s[right] + s[right] = tmp + helper(left + 1, right - 1, s) +} +``` + + + ## 345. 反转字符串中的元音字母 @@ -1246,6 +1385,68 @@ class Solution(object): return length + num ``` +---- + +2020.03.19 二刷 + +在最后返回时可以判断下结果长度和最初字符串的长度,如果结果长度短,则将结果长度 +1(即加上任何一个字符构成奇数回文串)。 + + + +#### **Python** + +```python +class Solution: + def longestPalindrome(self, s: str) -> int: + length = len(s) + c_map = dict() + for c in s: + if c not in c_map: + c_map[c] = 0 + c_map[c] += 1 + res = 0 + for v in c_map.values(): + if v % 2 == 0: + # 偶数处理 + res += v + else: + # 奇数处理 + res += (v - 1) + return res + 1 if res < length else res +``` + +#### **Go** + +```go +func longestPalindrome(s string) int { + res := 0 + cMap := make(map[string]int) + // 数据统计 + for _, c := range s { + if _, ok := cMap[string(c)]; !ok { + cMap[string(c)] = 0 + } + cMap[string(c)] += 1 + } + + // 判断奇偶 + for _, value := range cMap { + if value % 2 == 0 { + res += value + } else { + res += (value - 1) + } + } + + if res < len(s) { + return res + 1 + } + return res +} +``` + + + ## 459. 重复的子字符串 diff --git a/docs/data-structure/tree/bfs/README.md b/docs/data-structure/tree/bfs/README.md index 2f554973f..146dd9600 100644 --- a/docs/data-structure/tree/bfs/README.md +++ b/docs/data-structure/tree/bfs/README.md @@ -11,43 +11,112 @@ - 若该节点的左节点不为空:左节点入队列 - 若该节点的右节点不为空:右节点入队列 + + +#### **Python** + ```python # Definition for a binary tree node. -# class TreeNode(object): +# class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None -class Solution(object): - def levelOrder(self, root): - """ - :type root: TreeNode - :rtype: List[List[int]] - """ - if root is None: - return [] - - q = list() - res = list() - q.append(root) - while q: - tmp = list() - # 此时队列中的节点为同层节点 - for i in range(len(q)): - node = q[0] - del q[0] - #if node is not None: +class Solution: + def levelOrder(self, root: TreeNode) -> List[List[int]]: + queue = [] + queue.append(root) + res = [] + while len(queue) > 0: + length = len(queue) + tmp = [] + for i in range(length): + node = queue[0] + del queue[0] + if node is None: + continue tmp.append(node.val) - if node.left is not None: - q.append(node.left) - if node.right is not None: - q.append(node.right) - - res.append(tmp) + queue.append(node.left) + queue.append(node.right) + if len(tmp) > 0: + res.append(tmp) return res ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func levelOrder(root *TreeNode) [][]int { + queue := make([]*TreeNode, 0) + queue = append(queue, root) + res := make([][]int, 0) + for len(queue) > 0 { + tmp := make([]int, 0) + length := len(queue) + for i := 0; i < length; i++ { + q := queue[0] + queue = queue[1:] + if q == nil { + continue + } + tmp = append(tmp, q.Val) + queue = append(queue, q.Left) + queue = append(queue, q.Right) + } + if len(tmp) > 0 { + res = append(res, tmp) + } + } + return res +} +``` + +## 107. 二叉树的层序遍历 II + +[原题链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) + +与 106 类似,只是结果输出顺序不同。 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def levelOrderBottom(self, root: TreeNode) -> List[List[int]]: + ans = [] + q = [root] + while len(q) != 0: + # 遍历一层 + tmp = [] + for i in range(len(q)): + top = q[0] + del q[0] + if top is None: + continue + tmp.append(top.val) + q.append(top.left) + q.append(top.right) + if len(tmp) != 0: + ans.append(tmp) + + return ans[::-1] +``` + + + ## 108. 将有序数组转换为二叉搜索树 [原题链接](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/submissions/) @@ -91,7 +160,7 @@ class Solution(object): [原题链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/) -### 思路 +### 解一:实用了额外空间 层次遍历的变种考点。 @@ -136,6 +205,201 @@ class Solution(object): return root ``` +### 解二:常数空间 + 递归 + +- 不断找到下一个可关联的右侧不为空节点 +- 注意:先构造右子树 + + + +#### **Python** + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None): + self.val = val + self.left = left + self.right = right + self.next = next +""" +class Solution: + def connect(self, root: 'Node') -> 'Node': + self.handler(root) + return root + + def handler(self, root): + if root is None or (root.left is None and root.right is None): + return + + # 处理左节点 + if root.left is not None: + if root.right is not None: + # 如果存在右节点:指向右节点 + root.left.next = root.right + else: + # 如果不存在右节点;一直往下找到第一个存在的右侧节点 + root.left.next = self.get_next(root) + + # 处理右节点 + # 使用 next 指针 + if root.right is not None: + root.right.next = self.get_next(root) + + # 先递归右子树 + self.handler(root.right) + self.handler(root.left) + + + def get_next(self, root): + next_node = root.next + while next_node is not None: + if next_node.left is not None: + return next_node.left + if next_node.right is not None: + return next_node.right + next_node = next_node.next + return None +``` + + + +## 199. 二叉树的右视图 + +[原题链接](https://leetcode-cn.com/problems/binary-tree-right-side-view/) + +### BFS 层级别离 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def rightSideView(self, root: TreeNode) -> List[int]: + ans = [] + queue = [root] + while len(queue) > 0: + q_length = len(queue) + for i in range(q_length): + first = queue[0] + del queue[0] + if first is None: + continue + if i == q_length - 1: + ans.append(first.val) + if first.left is not None: + queue.append(first.left) + if first.right is not None: + queue.append(first.right) + + return ans +``` + +- 时间复杂度:$O(n)$ +- 空间复杂度:$O(n)$ + +## 297. 二叉树的序列化与反序列化 + +[原题链接](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/) + +### BFS:层序遍历 + +- 序列化:将题中二叉树利用辅助队列层序遍历为 `1,2,3,null,null,4,5` +- 反序列化:字符串转为数组,将第一个节点入队列,依旧以队列的方式进行反序列化 + +```python +# Definition for a binary tree node. +# class TreeNode(object): +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Codec: + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + str_list = [] + queue = [] + queue.append(root) + while len(queue) > 0: + q_length = len(queue) + for i in range(q_length): + # 取出队列头部节点 + first = queue[0] + del queue[0] + if first is None: + str_list.append("N") + continue + str_list.append(str(first.val)) + # 左右节点入队列 + queue.append(first.left) + queue.append(first.right) + # print(str_list) + return ','.join(str_list) + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + str_list = data.split(',') + # 取出第一个节点 + first = str_list[0] + root = self.get_node(first) + queue = [] + queue.append(root) + del str_list[0] + while len(queue) > 0: + q_length = len(queue) + for i in range(q_length): + first = queue[0] + del queue[0] + if first is None: + continue + # 构造它的左右节点 + str_list_length = len(str_list) + if str_list_length >= 2: + left_node = self.get_node(str_list[0]) + del str_list[0] + right_node = self.get_node(str_list[0]) + del str_list[0] + elif str_list_length == 1: + left_node = self.get_node(str_list[0]) + right_node = None + del str_list[0] + else: + left_node = None + right_node = None + first.left = left_node + first.right = right_node + if left_node is not None: + queue.append(left_node) + if right_node is not None: + queue.append(right_node) + + return root + + def get_node(self, root_val): + if root_val == 'N': + return None + else: + return TreeNode(int(root_val)) + +# Your Codec object will be instantiated and called as such: +# codec = Codec() +# codec.deserialize(codec.serialize(root)) +``` + ## 513. 找树左下角的值 [原题链接](https://leetcode-cn.com/problems/find-bottom-left-tree-value/comments/) diff --git a/docs/data-structure/tree/bst/README.md b/docs/data-structure/tree/bst/README.md new file mode 100644 index 000000000..367c0942f --- /dev/null +++ b/docs/data-structure/tree/bst/README.md @@ -0,0 +1,102 @@ +## 220. 存在重复元素 III + +[原题链接](https://leetcode-cn.com/problems/contains-duplicate-iii/) + +### 思路 + +这道题的中文翻译真的很迷,先对题目再做一个翻译: + +给定一个整数数组,判断数组中是否有两个不同的索引 `i` 和 `j`,使得:存在 `abs(i - j) <= k` 时,有 `abs(nums[i] - nums[j]) <=t`。 + +### 解一:暴力破解(超时) + +取出元素 `nums[i]`,在 `i + 1 ~ i + k` 的范围内遍历是否存在 `abs(nums[i] - nums[j]) <= t` 的数,如果存在则返回 `True`。 + +```python +class Solution: + def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: + num_length = len(nums) + for i in range(num_length): + for j in range(i + 1, i + k + 1): + if j < num_length: + if abs(nums[i] - nums[j]) <= t: + return True + return False +``` + +### 解二:桶 + +使用 `nums[i] // (t + 1)` 计算桶编号,这样保证同一个桶中的元素差值不会超过 `t`,因此只要命中同一个桶即可返回 `True`。 + +因此题需要维护长度为 `k` 的滑动窗口,因此当元素大于 `K` 时需要清理桶中元素。 + +```python +class Solution: + def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: + if t < 0: + return False + bucket = dict() + for i in range(len(nums)): + bucket_num = nums[i] // (t + 1) + if bucket_num in bucket: + # 命中 + return True + # 检查前后两个桶,因为范围是 2t + if bucket_num + 1 in bucket and abs(bucket[bucket_num + 1] - nums[i]) <= t: + return True + if bucket_num - 1 in bucket and abs(bucket[bucket_num - 1] - nums[i]) <= t: + return True + bucket[bucket_num] = nums[i] + if len(bucket) > k: + # 弹出元素 + bucket.pop(nums[i - k] // (t + 1)) + return False +``` + +```go +func containsNearbyAlmostDuplicate(nums []int, k int, t int) bool { + // var bucket map[int]int + if t < 0 { + return false + } + var bucket = make(map[int]int) + for i := 0; i < len(nums); i++ { + // 计算桶编号 + bucketNum := getBucketKey(nums[i], t + 1) + // 是否命中桶 + if _, ok := bucket[bucketNum]; ok { + // 存在 + return true + } + // 是否命中相邻桶 + if val, ok := bucket[bucketNum - 1]; ok { + if val - nums[i] <= t && val - nums[i] >= -t { + return true + } + } + if val, ok := bucket[bucketNum + 1]; ok { + if val - nums[i] <= t && val - nums[i] >= -t { + return true + } + } + bucket[bucketNum] = nums[i] + // 多余元素弹出 + if len(bucket) > k { + delete(bucket, getBucketKey(nums[i - k], t + 1)) + } + } + return false +} + +func getBucketKey(num int, t int) int { + if num < 0 { + return (num + 1) / (t - 1) + } else { + return num / t + } +} +``` + +### 解三:平衡二叉树 + + diff --git a/docs/data-structure/tree/dfs/README.md b/docs/data-structure/tree/dfs/README.md index aea369b40..f0df6ddc5 100644 --- a/docs/data-structure/tree/dfs/README.md +++ b/docs/data-structure/tree/dfs/README.md @@ -14,6 +14,10 @@ void dfs(TreeNode root) { } ``` + + +#### **Python** + ```python class Solution(object): def inorderTraversal(self, root): @@ -33,12 +37,47 @@ class Solution(object): self.visitNode(root.right, l) ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +var res []int + +func inorderTraversal(root *TreeNode) []int { + res = make([]int, 0) + handler(root) + return res +} + +func handler(root *TreeNode) { + if root == nil { + return + } + handler(root.Left) + res = append(res, root.Val) + handler(root.Right) +} +``` + + + ### 解法二 非递归法。 - 寻找当前节点的左节点,依次入栈 +#### **Python** + + + ```python class Solution(object): def inorderTraversal(self, root): @@ -55,16 +94,52 @@ class Solution(object): cur = cur.left node = stack.pop() res.append(node.val) + # 下一个节点轮到右节点(左 -> 中 -> 右) cur = node.right return res ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + +func inorderTraversal(root *TreeNode) []int { + if root == nil { + return nil + } + stack := make([]*TreeNode, 0) + res := make([]int, 0) + for root != nil || len(stack) > 0 { + for root != nil { + stack = append(stack, root) + root = root.Left + } + // 取栈顶 + top := stack[len(stack) - 1] + res = append(res, top.Val) + // 删除栈顶 + stack = stack[:len(stack) - 1] + root = top.Right + } + return res +} +``` + + ## 98. 验证二叉搜索树 [原题链接](https://leetcode-cn.com/problems/validate-binary-search-tree/submissions/) -### 思路 +### 解一:中序遍历 中序遍历为升序 @@ -99,6 +174,94 @@ class Solution(object): return ``` +### 解二:递归 + + + +#### **Python** + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def isValidBST(self, root: TreeNode) -> bool: + return self.helper(root, None, None) + + def helper(self, root, low, high): + if root is None: + return True + if low is not None and root.val <= low: + return False + if high is not None and root.val >= high: + return False + return self.helper(root.left, low, root.val) and self.helper(root.right, root.val, high) +``` + +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func isValidBST(root *TreeNode) bool { + return helper(root, -1<<63, 1<<63-1) +} + +func helper(root *TreeNode, low int, high int) bool { + if root == nil { + return true + } + if root.Val <= low { + return false + } + if root.Val >= high { + return false + } + return helper(root.Left, low, root.Val) && helper(root.Right, root.Val, high) +} +``` + +### 解三:栈辅助中序遍历 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def isValidBST(self, root: TreeNode) -> bool: + stack = [] + pre = float('-inf') + while root is not None or len(stack) > 0: + # 左侧压入栈 + while root is not None: + stack.append(root) + root = root.left + # 弹出栈顶 + top = stack.pop() + # 判断终序遍历前 1 个数是否小于当前数 + if top.val <= pre: + return False + pre = top.val + root = top.right + + return True +``` + + + ## 105. 从前序与中序遍历序列构造二叉树 [原题链接](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) @@ -185,7 +348,10 @@ class Solution(object): ### 解法二 -非递归,使用 list 模拟栈。 +非递归,使用栈辅助。 + +- 将 root 右节点、左节点依次压入栈 +- 循环弹出栈顶节点,再按节点的右、左节点依次入栈 ```python class Solution(object): @@ -213,65 +379,145 @@ class Solution(object): [原题链接](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) -### 解法一 +### 解法一:递归 -- 递归 +后序遍历。 -后序遍历: + -``` -void dfs(TreeNode root) { - dfs(root.left); - dfs(root.right); - visit(root); -} -``` +#### **Python** ```python -class Solution(object): - def postorderTraversal(self, root): - """ - :type root: TreeNode - :rtype: List[int] - """ - l = [] - self.visitRoot(root, l) - return l - - def visitRoot(self, root, l): +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def postorderTraversal(self, root: TreeNode) -> List[int]: + res = [] + self.handler(root, res) + return res + + def handler(self, root, res): if root is None: return - self.visitRoot(root.left, l) - self.visitRoot(root.right, l) - l.append(root.val) + self.handler(root.left, res) + self.handler(root.right, res) + res.append(root.val) ``` -### 解法二 +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +var res []int + +func postorderTraversal(root *TreeNode) []int { + res = make([]int, 0) + handler(root) + return res +} + +func handler(root *TreeNode) { + if root == nil { + return + } + handler(root.Left) + handler(root.Right) + res = append(res, root.Val) +} +``` + + + +### 解法二:迭代 - 后序遍历顺序为 left->right->root,反序后为:root->right->left - 用栈实现 root->right->left 的顺序,再将列表反序 + + +#### **Python** + ```python -class Solution(object): - def postorderTraversal(self, root): - """ - :type root: TreeNode - :rtype: List[int] - """ +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def postorderTraversal(self, root: TreeNode) -> List[int]: + # 后序遍历:左 -> 右 -> 中 + # 反过来:中 -> 右 -> 左 stack = [] stack.append(root) res = [] - while stack: - node = stack.pop() - if node is None: + while len(stack) > 0: + top = stack.pop() + if top is None: continue - stack.append(node.left) - stack.append(node.right) - res.append(node.val) + # 用栈先将左节点入栈 + stack.append(top.left) + stack.append(top.right) + res.append(top.val) res.reverse() return res ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +var res []int + +func postorderTraversal(root *TreeNode) []int { + res := make([]int, 0) + stack := make([]*TreeNode, 0) + stack = append(stack, root) + for len(stack) > 0 { + top := stack[len(stack) - 1] + // 删除栈顶 + stack = stack[:len(stack) - 1] + if top == nil { + continue + } + stack = append(stack, top.Left) + stack = append(stack, top.Right) + res = append(res, top.Val) + } + res = reverse(res) + return res +} + +func reverse(res []int) []int { + for i, j := 0, len(res) - 1; i < j; i, j = i + 1, j - 1 { + res[i], res[j] = res[j], res[i] + } + return res +} +``` + + + ## 230. 二叉搜索树中第K小的元素 [原题链接](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/) diff --git a/docs/data-structure/tree/n-ary/README.md b/docs/data-structure/tree/n-ary/README.md new file mode 100644 index 000000000..3194e3012 --- /dev/null +++ b/docs/data-structure/tree/n-ary/README.md @@ -0,0 +1,215 @@ +## 429. N叉树的层序遍历 + +[原题链接](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/) + +### 解一:迭代 + +用队列辅助。 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val=None, children=None): + self.val = val + self.children = children +""" +class Solution: + def levelOrder(self, root: 'Node') -> List[List[int]]: + queue = [] + res = [] + queue.append(root) + while len(queue) > 0: + q_length = len(queue) + tmp = [] + for i in range(q_length): + first = queue[0] + del queue[0] + if first is None: + continue + tmp.append(first.val) + for child in first.children: + queue.append(child) + if len(tmp) > 0: + res.append(tmp) + return res +``` + +- 时间复杂度:$O(n)$,n 为节点数量 +- 空间复杂度:$O(n)$ + +### 解二:递归 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val=None, children=None): + self.val = val + self.children = children +""" +class Solution: + def levelOrder(self, root: 'Node') -> List[List[int]]: + res = [] + self.helper(res, root, 0) + return res + + def helper(self, res, root, level): + if root is None: + return + if len(res) == level: + res.append([]) + res[level].append(root.val) + for child in root.children: + self.helper(res, child, level + 1) +``` + +- 时间复杂度:$O(n)$ +- 空间复杂度:最坏 $O(n)$,最好 $O(logn)$ + +## 559. N叉树的最大深度 + +[原题链接](https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/) + +### 递归 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val=None, children=None): + self.val = val + self.children = children +""" +class Solution: + def maxDepth(self, root: 'Node') -> int: + if root is None: + return 0 + if len(root.children) == 0: + return 1 + max_depth = 0 + for child in root.children: + max_depth = max(max_depth, self.maxDepth(child)) + return max_depth + 1 +``` + +## 589. N叉树的前序遍历 + +[原题链接](https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/) + +### 解一:递归 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val=None, children=None): + self.val = val + self.children = children +""" +class Solution: + def preorder(self, root: 'Node') -> List[int]: + res = [] + self.helper(root, res) + return res + + def helper(self, root, res): + if root is None: + return + res.append(root.val) + children = root.children + for child in children: + self.helper(child, res) +``` + +### 解二:遍历 + +用栈辅助。 + +1. 首先,将 `root` 压入栈中 +2. 在栈不为空时,对栈进行遍历,每次弹出栈顶元素 +3. 若栈顶元素节点不为空,则将该节点值放入结果集中,且将该节点的子节点**从右至左**压入栈中(这样弹出时就是从左至右,符合前序遍历的顺序) + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val=None, children=None): + self.val = val + self.children = children +""" +class Solution: + def preorder(self, root: 'Node') -> List[int]: + stack = [] + stack.append(root) + res = [] + while len(stack) > 0: + top = stack.pop() + if top is None: + continue + res.append(top.val) + # 反序插入子节点 + children = top.children + for i in range(len(children) - 1, -1, -1): + child = children[i] + stack.append(child) + return res +``` + +## 590. N叉树的后序遍历 + +[原题链接](https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/) + +### 解一:递归 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val=None, children=None): + self.val = val + self.children = children +""" +class Solution: + def postorder(self, root: 'Node') -> List[int]: + res = [] + self.healper(root, res) + return res + + def healper(self, root, res): + if root is None: + return + children = root.children + for child in children: + self.healper(child, res) + res.append(root.val) +``` + +### 解二:迭代 + +用一个辅助栈。 + +后序遍历的顺序是:子节点从左至右 -> 根节点。因此我们可以先把「根节点 -> 子节点从右至左」写入结果集中,在返回时再将结果集反序。 + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val=None, children=None): + self.val = val + self.children = children +""" +class Solution: + def postorder(self, root: 'Node') -> List[int]: + res = [] + stack = [] + stack.append(root) + while len(stack): + top = stack.pop() + if top is None: + continue + res.append(top.val) + for child in top.children: + stack.append(child) + return res[::-1] +``` \ No newline at end of file diff --git a/docs/data-structure/tree/other/README.md b/docs/data-structure/tree/other/README.md index 3a297c14d..8378eaff8 100644 --- a/docs/data-structure/tree/other/README.md +++ b/docs/data-structure/tree/other/README.md @@ -2,7 +2,7 @@ [原题链接](https://leetcode-cn.com/problems/symmetric-tree/description/) -### 思路 +### 解一:递归 递归法,判断二叉树是否为镜像对称。 @@ -34,7 +34,9 @@ else: - A节点的左节点与B节点的右节点 - A节点的右节点与B节点的左节点 -### python 实现 + + +#### **Python** ```python # Definition for a binary tree node. @@ -63,29 +65,131 @@ class Solution: return self.checkElement(left_root.left, right_root.right) and self.checkElement(left_root.right, right_root.left) ``` -## 104. 二叉树的最大深度 +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func isSymmetric(root *TreeNode) bool { + if root == nil { + return true + } + return symmetric(root.Left, root.Right) +} + +func symmetric(left *TreeNode, right *TreeNode) bool { + if left == nil || right == nil { + return left == right + } + if left.Val != right.Val { + return false + } + return symmetric(left.Left, right.Right) && symmetric(left.Right, right.Left) +} +``` -[原题链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/description/) + -### 思路 +### 解二:迭代 -递归求解~ + -取左右子树最大深度值 + 1(1 为到 root 节点的深度) +#### **Python** ```python -class Solution(object): - def maxDepth(self, root): - """ - :type root: TreeNode - :rtype: int - """ +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def isSymmetric(self, root: TreeNode) -> bool: if root is None: - return 0 - else: - return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 + return True + queue = [] + queue.append(root.left) + queue.append(root.right) + while len(queue) > 0: + left = queue[0] + del queue[0] + right = queue[0] + del queue[0] + + if left is None: + if right is None: + pass + else: + return False + else: + if right is None: + return False + else: + queue.append(left.left) + queue.append(right.right) + queue.append(left.right) + queue.append(right.left) + if left.val != right.val: + return False + + return True ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func isSymmetric(root *TreeNode) bool { + queue := make([]*TreeNode, 0) + if root == nil { + return true + } + queue = append(queue, root.Left) + queue = append(queue, root.Right) + for len(queue) > 0 { + left := queue[0] + queue = queue[1:] + right := queue[0] + queue = queue[1:] + + if left == nil { + if right != nil { + return false + } + } else { + if right == nil { + return false + } else { + queue = append(queue, left.Left) + queue = append(queue, right.Right) + queue = append(queue, left.Right) + queue = append(queue, right.Left) + if left.Val != right.Val { + return false + } + } + } + } + return true +} +``` + + + ## 687. 最长同值路径 [原题链接](https://leetcode-cn.com/problems/longest-univalue-path/description/) diff --git a/docs/data-structure/tree/recursion/README.md b/docs/data-structure/tree/recursion/README.md index d392cbfe5..bf7e753d8 100644 --- a/docs/data-structure/tree/recursion/README.md +++ b/docs/data-structure/tree/recursion/README.md @@ -38,14 +38,81 @@ class Solution: ## 104. 二叉树的最大深度 -[原题链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/description/) +- [原题链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/description/) +- [详解链接](https://juejin.im/post/5de254ce51882523467752d0) -### 思路 +### 自顶向下 + + -递归求解~ +#### **Python** + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + depth = 0 + def maxDepth(self, root: TreeNode) -> int: + self.depth = 0 + self.handler(root, 1) + return self.depth + + def handler(self, root, depth): + if root is None: + return + self.depth = max(self.depth, depth) + self.handler(root.left, depth + 1) + self.handler(root.right, depth + 1) +``` + +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + +var depth int +func maxDepth(root *TreeNode) int { + depth = 0 + handler(root, 1) + return depth +} + +func handler(root *TreeNode, curDepth int) { + if root == nil { + return + } + if curDepth > depth { + depth = curDepth + } + handler(root.Left, curDepth + 1) + handler(root.Right, curDepth + 1) +} +``` + + + +### 自底向上 + +「自底向上」:我们首先对所有子节点递归地调用函数,然后根据返回值和根节点本身的值得到答案。 取左右子树最大深度值 + 1(1 为到 root 节点的深度) + + +#### **Python** + ```python class Solution(object): def maxDepth(self, root): @@ -59,6 +126,53 @@ class Solution(object): return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 ``` +20210113 复盘: + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def maxDepth(self, root: TreeNode) -> int: + if root is None: + return 0 + left_depth = self.maxDepth(root.left) + 1 + right_depth = self.maxDepth(root.right) + 1 + return max(left_depth, right_depth) +``` + +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func maxDepth(root *TreeNode) int { + if root == nil { + return 0 + } + return max(maxDepth(root.Left), maxDepth(root.Right)) + 1 +} + +func max(a int, b int) int { + if a > b { + return a + } + return b +} +``` + + + ## 105. 从前序与中序遍历序列构造二叉树 @@ -88,6 +202,10 @@ class Solution(object): 整个过程我们可以用递归来完成。 + + +#### **Python** + ```python # Definition for a binary tree node. # class TreeNode(object): @@ -117,6 +235,45 @@ class Solution(object): return root ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func buildTree(preorder []int, inorder []int) *TreeNode { + if len(preorder) == 0 { + return nil + } + root := new(TreeNode) + // 前序遍历第一个是根元素 + root.Val = preorder[0] + // 找到位置 + rootIndex := findRootIndex(inorder, root.Val) + // 构造左右子树 + root.Left = buildTree(preorder[1:1+rootIndex], inorder[:rootIndex]) + root.Right = buildTree(preorder[1+rootIndex:], inorder[rootIndex+1:]) + + return root +} + +func findRootIndex(inorder []int, rootVal int) int { + for i, val := range inorder { + if val == rootVal { + return i + } + } + return 0 +} +``` + + + ## 106. 从中序与后序遍历序列构造二叉树 [原题链接](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) @@ -226,35 +383,123 @@ func findIndex(array []int, num int) int { [原题链接](https://leetcode-cn.com/problems/balanced-binary-tree/description/) -### 思路 - 和 [104](/tree/104.md) 的套路一样,加上对比逻辑而已。 +### 解一:自顶向下 + +对每个节点都计算它左右子树的高度,判断是否为平衡二叉树。 + ```python -class Solution(object): - - result = True - - def isBalanced(self, root): +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def isBalanced(self, root: TreeNode) -> bool: + if root is None: + return True + left_depth = self.helper(root.left, 0) + right_depth = self.helper(root.right, 0) + if abs(left_depth - right_depth) > 1: + # 不平衡 + return False + return self.isBalanced(root.left) and self.isBalanced(root.right) + + def helper(self, root, depth): """ - :type root: TreeNode - :rtype: bool + 返回节点高度 """ - self.depth(root) - return self.result - - def depth(self, root): if root is None: - return 0 - else: - left_depth = self.depth(root.left) - right_depth = self.depth(root.right) - if (abs(left_depth - right_depth) > 1): - self.result = False - - return max(left_depth, right_depth) + 1 + return depth + left_depth = self.helper(root.left, depth + 1) + right_depth = self.helper(root.right, depth + 1) + return max(left_depth, right_depth) +``` + +- 时间复杂度:$O(nlogn)$。最差情况需要遍历树的所有节点,判断每个节点的最大高度又需要遍历该节点的所有子节点。如果树是倾斜的,则会到达 $O(n^2)$ 复杂度。详见[题解中的复杂度分析](https://leetcode-cn.com/problems/balanced-binary-tree/solution/ping-heng-er-cha-shu-by-leetcode/)。 +- 空间复杂度:$O(n)$。如果树完全倾斜(退化成链),递归栈将包含所有节点。 + +### 解二:自底向上 + +自顶向下的高度计算存在大量冗余,每次计算高度时,同时都要计算子树的高度。 + +从底自顶返回二叉树高度,如果某子树不是平衡树,提前进行枝剪。 + + + +#### **Python** + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + res = True + def isBalanced(self, root: TreeNode) -> bool: + self.helper(root, 0) + return self.res + + def helper(self, root, depth): + if root is None: + return depth + left_depth = self.helper(root.left, depth + 1) + right_depth = self.helper(root.right, depth + 1) + if abs(left_depth - right_depth) > 1: + self.res = False + # 提前返回 + return 0 + return max(left_depth, right_depth) ``` +- 时间复杂度:$O(n)$。n 为树的节点数,最坏情况需要遍历所有节点。 +- 空间复杂度:$O(n)$。完全不平衡时需要的栈空间。 + +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +var res bool + +func isBalanced(root *TreeNode) bool { + res = true + helper(root, 0) + return res +} + +func helper(root *TreeNode, depth int) int { + if root == nil { + return depth + } + leftDepth := helper(root.Left, depth + 1) + rightDepth := helper(root.Right, depth + 1) + if leftDepth - rightDepth > 1 || leftDepth - rightDepth < -1 { + res = false + return 0 + } + if leftDepth > rightDepth { + return leftDepth + } else { + return rightDepth + } +} +``` + + + ## 111. 二叉树的最小深度 @@ -297,6 +542,10 @@ class Solution(object): - 最终相加和为 sum - False 的条件为:一直找到叶子节点了还是没有找到那个节点 + + +#### **Python** + ```python class Solution(object): def hasPathSum(self, root, sum): @@ -306,12 +555,81 @@ class Solution(object): :rtype: bool """ if root is None: + # 如果传入节点为空则直接返回 False return False if root.left is None and root.right is None and root.val == sum: return True return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val) ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func hasPathSum(root *TreeNode, sum int) bool { + if root == nil { + return false + } + // 该节点为叶子节点 + if root.Left == nil && root.Right == nil { + return sum == root.Val + } + // 递归 + return hasPathSum(root.Left, sum - root.Val) || hasPathSum(root.Right, sum - root.Val) +} +``` + + + +## 116. 填充每个节点的下一个右侧节点指针 + +[原题链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/) + +### 解一:递归 + +- `root.left.next = root.right` +- 利用已经处理好的 `next` 指针:`root.right.next = root.next.left` + + + +#### **Python** + +```python +""" +# Definition for a Node. +class Node: + def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None): + self.val = val + self.left = left + self.right = right + self.next = next +""" +class Solution: + def connect(self, root: 'Node') -> 'Node': + self.handler(root) + return root + + def handler(self, root): + if root is None or root.left is None: + return None + root.left.next = root.right + if root.next is not None: + root.right.next = root.next.left + # 关联节点 + self.connect(root.left) + # 关联右节点 + self.connect(root.right) +``` + + + ## 226. 翻转二叉树 [原题链接](https://leetcode-cn.com/problems/invert-binary-tree/description/) @@ -337,6 +655,136 @@ class Solution(object): return root ``` +## 235. 二叉搜索树的最近公共祖先 + +[原题链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) + +### 解一:递归 + +分为几种情况: + +1. `p` 与 `q` 分列 `root` 节点两个子树,则直接返回 `root` +2. `p` 与 `q` 其中之一等于 `root`,则直接返回 `root` +3. 如果 `p` 和 `q` 都在 `root` 左子树,则递归左子树 +4. 如果 `p` 和 `q` 都在 `root` 右子树,则递归右子树 + + + +#### **Python** + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if root is None: + return root + # 当前节点为 p 或 q + if p.val == root.val or q.val == root.val: + return root + # 两个节点分别在左右两个子树 + if (p.val < root.val and q.val > root.val) or (p.val > root.val and q.val < root.val): + return root + # 两个节点都在左子树 + if p.val < root.val and q.val < root.val: + return self.lowestCommonAncestor(root.left, p, q) + # 两个节点都在右子树 + if p.val > root.val and q.val > root.val: + return self.lowestCommonAncestor(root.right, p, q) +``` + + + +### 解二:迭代 + +@TODO + +## 236. 二叉树的最近公共祖先 + +[原题链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)(同 [面试题68 - II. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/)) + +### 递归法 + +因为找两个节点的公共祖先,所以从上往下递归遍历。 + +- 如果 `root` 等于 `q` 或 `p` 其中之一,则直接返回 `root` +- 接着查找 `root` 的左右子树 +- 如果 `p` 与 `q` 不在左子树中,则必定在右子树中 + + + +#### **Python** + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: + if root is None or root == p or root == q: + return root + + # 在左子树找 + left = self.lowestCommonAncestor(root.left, p, q) + # 在右子树找 + right = self.lowestCommonAncestor(root.right, p, q) + + if left is None and right is None: + return None + if left is None: + return right + if right is None: + return left + + return root +``` + +#### **Go** + +```go +/** + * Definition for TreeNode. + * type TreeNode struct { + * Val int + * Left *ListNode + * Right *ListNode + * } + */ + func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + if root == nil || root == p || root == q { + return root + } + + // 寻找左子树 + left := lowestCommonAncestor(root.Left, p, q) + // 寻找右子树 + right := lowestCommonAncestor(root.Right, p, q) + + if left == nil && right == nil { + return nil + } + if left == nil { + return right + } + if right == nil { + return left + } + + return root +} +``` + + + ## 337. 打家劫舍 III [原题链接](https://leetcode-cn.com/problems/house-robber-iii/description/) @@ -458,6 +906,106 @@ class Solution(object): return result ``` +## 450. 删除二叉搜索树中的节点 + +[原题链接](https://leetcode-cn.com/problems/delete-node-in-a-bst/) + +### 思路 + +要删除的节点共有三种情况: + +1. 是叶节点:直接删除 +2. 有右子树:找它的后继节点交换(值)并删除后继节点 +3. 有左子树:找到它的前驱节点交换(值)并删除前驱节点 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def deleteNode(self, root: TreeNode, key: int) -> TreeNode: + # 找到节点 + if root is None: + return None + if key > root.val: + # 找右子树 + root.right = self.deleteNode(root.right, key) + elif key < root.val: + # 找左子树 + root.left = self.deleteNode(root.left, key) + else: + # 找到节点 + if root.left is None and root.right is None: + # 删除的节点是叶子节点 + root = None + elif root.right is not None: + # 有右子树,找后继节点 + root.val = self.successor(root) + # 删除这个后继节点 + root.right = self.deleteNode(root.right, root.val) + else: + # 有左子树,找前驱节点 + root.val = self.predecessor(root) + root.left = self.deleteNode(root.left, root.val) + + return root + + def successor(self, root): + # 后继节点:比节点大的最小节点 + root = root.right + while root.left is not None: + root = root.left + return root.val + + def predecessor(self, root): + # 前驱节点 + root = root.left + while root.right is not None: + root = root.right + return root.val +``` + +## 538. 把二叉搜索树转换为累加树 + +[原题链接](https://leetcode-cn.com/problems/convert-bst-to-greater-tree/) + +### 思路 + +二叉搜索树有一个特性:它的中序遍历结果是递增序列。由此我们很容易知道,它的**反中序遍历**就是一个递减序列了。 + +题目要求「使得每个节点的值是原来的节点值加上所有大于它的节点值之和」,那么只要使用该树的反中序遍历,把先遍历到的节点值都加到当前节点上,即可得到一个「累加树」。 + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + + num = 0 + + def convertBST(self, root: TreeNode) -> TreeNode: + + # 递归 + def recursion(root: TreeNode): + if root is None: + return + recursion(root.right) + root.val += self.num + self.num = root.val + recursion(root.left) + + recursion(root) + return root +``` + ## 543. 二叉树的直径 [原题链接](https://leetcode-cn.com/problems/diameter-of-binary-tree/description/) @@ -466,6 +1014,10 @@ class Solution(object): 和 [104](/tree/104.md) 的套路一样,加上取 max 逻辑而已。 + + +#### **Python** + ```python class Solution(object): max_length = 0 @@ -489,6 +1041,45 @@ class Solution(object): return max(l, r) + 1 ``` +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +var res int + +func diameterOfBinaryTree(root *TreeNode) int { + helper(root) + return res +} + +func helper(root *TreeNode) int { + if root == nil { + return 0 + } + leftDepth := helper(root.Left) + rightDepth := helper(root.Right) + length := leftDepth + rightDepth + res = getMax(res, length) + return getMax(leftDepth, rightDepth) + 1 +} + +func getMax(a int, b int) int { + if a > b { + return a + } + return b +} +``` + + + ## 572. 另一个树的子树 [原题链接](https://leetcode-cn.com/problems/subtree-of-another-tree/description/) @@ -497,6 +1088,12 @@ class Solution(object): 与 [437](/tree/437.md) 递归思路类似。 +`t` 是否为 `s` 的子树,存在三种情况: + +- `t` 与 `s` 相同 +- `t` 是 `s` 的左子树 +- `t` 是 `s` 的右子树 + ```python class Solution(object): def isSubtree(self, s, t): @@ -510,6 +1107,9 @@ class Solution(object): return self.isSubtreeWithRoot(s, t) or self.isSubtree(s.left, t) or self.isSubtree(s.right, t) def isSubtreeWithRoot(self, s, t): + """ + t 与 s 是否相同 + """ if s is None and t is None: return True if s is None or t is None: @@ -519,6 +1119,36 @@ class Solution(object): return self.isSubtreeWithRoot(s.left, t.left) and self.isSubtreeWithRoot(s.right, t.right) ``` +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func isSubtree(s *TreeNode, t *TreeNode) bool { + if s == nil { + return false + } + return isSubtree(s.Left, t) || isSubtree(s.Right, t) || isSubtreeWithRoot(s, t) +} + +func isSubtreeWithRoot(s *TreeNode, t *TreeNode) bool { + if s == nil && t == nil { + return true + } + if s == nil || t == nil { + return false + } + if s.Val != t.Val { + return false + } + return isSubtreeWithRoot(s.Left, t.Left) && isSubtreeWithRoot(s.Right, t.Right) +} +``` + ## 617. 合并二叉树 [原题链接](https://leetcode-cn.com/problems/merge-two-binary-trees/description/) @@ -641,6 +1271,73 @@ class Solution(object): return l_val ``` +## 700. 二叉搜索树中的搜索 + +[原题链接](https://leetcode-cn.com/problems/search-in-a-binary-search-tree/) + +### 递归 + +递归设计: + +- 递归函数作用:返回目标节点 +- 函数返回时机: + - 节点为空 + - 找到目标节点 +- 递归调用时机:搜索左子树或右子树时 + + + +#### **Python** + +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution: + def searchBST(self, root: TreeNode, val: int) -> TreeNode: + if root is None: + return None + root_val = root.val + if root_val == val: + return root + if val < root.val: + return self.searchBST(root.left, val) + if val > root.val: + return self.searchBST(root.right, val) +``` + +#### **Go** + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func searchBST(root *TreeNode, val int) *TreeNode { + if root == nil { + return nil + } + rootVal := root.Val + if rootVal == val { + return root + } else if rootVal < val { + return searchBST(root.Right, val) + } else { + return searchBST(root.Left, val) + } +} +``` + + + ## 783. 二叉搜索树结点最小距离 [原题链接](https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes/) diff --git a/docs/data-structure/tree/trie/README.md b/docs/data-structure/tree/trie/README.md index a6661ea56..716e3e2a6 100644 --- a/docs/data-structure/tree/trie/README.md +++ b/docs/data-structure/tree/trie/README.md @@ -74,6 +74,34 @@ class Solution: - 时间复杂度:$O(\sum w_{i}^2)$($w_{i}$ 为 `words[i]` 的长度),遍历每个单词 + 截取后缀 - 空间复杂度:$O(\sum w_{i})$,所有后缀存储的空间开销 +2020.03.28 复盘: + +```python +class Solution: + def minimumLengthEncoding(self, words: List[str]) -> int: + if len(words) == 1: + return len(words[0]) + 1 + # 单词整合 + # 结果:剩余单词长度 + 剩余单词数量(# 数量) + # 倒过来比较 + reverse_words = [word[::-1] for word in words] + # 排序 + reverse_words.sort() + res = 0 + for i in range(1, len(reverse_words)): + pre_word = reverse_words[i - 1] + cur_word = reverse_words[i] + # 双指针 + for j in range(len(pre_word)): + if pre_word[j] != cur_word[j]: + # pre_word 需要单独处理 + res += len(pre_word) + 1 + break + # 需要加上最后一个 cur_word + res += len(cur_word) + 1 + return res +``` + #### **Java** ```java diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 000000000..ebfb291d5 --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,68 @@ +## 460. LFU缓存 + +[原题链接](https://leetcode-cn.com/problems/lfu-cache/) + +### 解一:简单直白法 + +存储每个页的访问频率 `cnt` 和最近访问标记 `mark`。 + +```python +class Page: + def __init__(self, val, cnt, mark): + self.val = val + self.cnt = cnt + self.mark = mark + +class LFUCache: + + def __init__(self, capacity: int): + self.cap = capacity + self.cache = dict() + self.mark = 0 + + def get(self, key: int) -> int: + if key not in self.cache: + return -1 + self.cache[key].cnt += 1 + self.mark += 1 + self.cache[key].mark = self.mark + return self.cache[key].val + + def put(self, key: int, value: int) -> None: + if key in self.cache: + cur_cnt = self.cache[key].cnt + 1 + self.mark += 1 + self.cache[key] = Page(value, cur_cnt, self.mark) + return + + # 判断是否超过 + if len(self.cache) < self.cap: + # 直接写入 + self.cache[key] = Page(value, 0, self.mark) + return + + cnt = float('inf') + mark = float('inf') + del_key = None + # 获取最近最少使用的键,cnt 最小,然后 mark 最小 + for k in self.cache: + page = self.cache[k] + if page.cnt < cnt: + cnt = page.cnt + mark = page.mark + del_key = k + if page.cnt == cnt and page.mark < mark: + mark = page.mark + del_key = k + + if del_key is not None: + self.cache.pop(del_key) + # 写入新的值 + self.mark += 1 + self.cache[key] = Page(value, 0, self.mark) + +# Your LFUCache object will be instantiated and called as such: +# obj = LFUCache(capacity) +# param_1 = obj.get(key) +# obj.put(key,value) +``` \ No newline at end of file diff --git a/docs/offer/README.md b/docs/offer/README.md new file mode 100644 index 000000000..71737b662 --- /dev/null +++ b/docs/offer/README.md @@ -0,0 +1,704 @@ +## 面试题 01.06. 字符串压缩 + +[原题链接](https://leetcode-cn.com/problems/compress-string-lcci/) + +### 思路 + + + +#### **Python** + +```python +class Solution: + def compressString(self, S: str) -> str: + s_length = len(S) + if s_length == 0: + return S + res = '' + cur = '' + count = 0 + for s in S: + if s != cur: + if cur != '': + res += cur + str(count) + cur = s + count = 1 + else: + count += 1 + # 尾部处理 + res += cur + str(count) + return res if len(res) < s_length else S +``` + +#### **Go** + +Go 字符串相关知识点: + +- `rune` -> `string`:`string(rune)` +- `int` -> `string`: `strconv.Itoa(int)` + +```go +func compressString(S string) string { + sLength := len(S) + if sLength == 0 { + return S + } + var res string + var cur rune + var empty rune + var count int + for _, s := range S { + if s != cur { + if cur != empty { + res += string(cur) + strconv.Itoa(count) + } + cur = s + count = 1 + } else { + count += 1 + } + } + res += string(cur) + strconv.Itoa(count) + if len(res) < sLength { + return res + } + return S +} +``` + + + +## 面试题 01.07. 旋转矩阵 + +[原题链接](https://leetcode-cn.com/problems/rotate-matrix-lcci/) + +### 思路 + +找出要进行顺时针旋转的四个位置分别是:`(i, j) (j, n-i-1) (n-i-1, n-j-1)` + +```python +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + n = len(matrix) + # 旋转交换的四个位置:(i, j) (j, n-i-1) (n-i-1, n-j-1) (n-j-1, i) + # 枚举范围:宽 (n + 1)/2 高 n/2 + for i in range(n // 2): + for j in range((n + 1) // 2): + temp = matrix[i][j] + matrix[i][j] = matrix[n - j - 1][i] + matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1] + matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1] + matrix[j][n - i - 1] = temp +``` + +## 面试题03. 数组中重复的数字 + +[原题链接](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) + +找出数组中重复的数字。 + + +在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 + +示例 1: + +``` +输入: +[2, 3, 1, 0, 2, 5, 3] +输出:2 或 3 +``` + +限制: + +`2 <= n <= 100000` + +### 哈希 + + + +#### **Python** + +```python +class Solution: + def findRepeatNumber(self, nums: List[int]) -> int: + m = dict() + for n in nums: + if n in m: + return n + m[n] = True +``` + +#### **Go** + +```go +func findRepeatNumber(nums []int) int { + m := make([]int, len(nums)) + for _, e := range nums { + if m[e] == 1 { + return e + } + m[e] = 1 + } + return -1 +} +``` + + + +## 面试题06. 从尾到头打印链表 + +[原题链接](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) + +输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 + +**示例 1:** + +``` +输入:head = [1,3,2] +输出:[2,3,1] +``` + +限制: + +`0 <= 链表长度 <= 10000` + +### 解一:栈 + +根据「从尾到头反过来」的描述很容易想到有着「先进后出」特征的数据结构 —— **栈**。 + +我们可以利用栈完成这一逆序过程,将链表节点值按顺序逐个压入栈中,再按各个节点值的弹出顺序返回即可。 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reversePrint(self, head: ListNode) -> List[int]: + stack = list() + while head is not None: + stack.append(head.val) + head = head.next + + return stack[::-1] +``` + +复杂度: + +- 时间复杂度:$O(n)$ +- 空间复杂度:$O(n)$ + +### 解二:递归 + +递归也能模拟栈的逆序过程。 + +我们将链表节点传入递归函数,递归函数设计如下: + +- 递归函数作用:将链表节点值逆序存入结果集 +- 结束条件:当节点为空时 +- 递归调用条件:当下一个节点不为空时 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reversePrint(self, head: ListNode) -> List[int]: + res = [] + self.reverse(head, res) + return res + + def reverse(self, head, res): + if head is None: + return + if head.next is not None: + # 下一个节点不为空:递归调用 + self.reverse(head.next, res) + res.append(head.val) +``` + +简化一下: + + + +#### **Python** + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reversePrint(self, head: ListNode) -> List[int]: + if head is None: + return [] + res = self.reversePrint(head.next) + res.append(head.val) + return res +``` + +#### **Go** + +```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func reversePrint(head *ListNode) []int { + if head == nil { + return []int{} + } + res := reversePrint(head.Next) + return append(res, head.Val) +} +``` + + + +复杂度: + +- 时间复杂度:$O(n)$ +- 空间复杂度:$O(n)$ + +## 面试题 17.16. 按摩师 + +[原题链接](https://leetcode-cn.com/problems/the-masseuse-lcci/) + +### 动态规划 + +- 用 `dp[0][i]` 表示不接 i 预约可以获得的最长预约时间 +- 用 `dp[1][i]` 表示接 i 预约可以获得的最长预约时间 + +```python +class Solution: + def massage(self, nums: List[int]) -> int: + # dp[0][i] 不接 + # dp[1][i] 接 + length = len(nums) + if length == 0: + return 0 + dp = [[0 for _ in range(length)] for _ in range(2)] + # 转移方程:dp[0][i] = max(dp[1][i - 1], dp[0][i - 1]) + # dp[1][i] = max(dp[0][i - 1] + nums[i]) + dp[1][0] = nums[0] + for i in range(1, length): + dp[0][i] = max(dp[1][i - 1], dp[0][i - 1]) + dp[1][i] = dp[0][i - 1] + nums[i] + return max(dp[0][length - 1], dp[1][length - 1]) +``` + +## 面试题 08.11. 硬币 + +[原题链接](https://leetcode-cn.com/problems/coin-lcci/) + +### 思路 + +1. 遍历所有硬币 +2. `dp[i - coin]` 表示去掉 coin 面额后的组合数量 + +```python +class Solution: + def waysToChange(self, n: int) -> int: + coins = [1, 5, 10, 25] + dp = [0 for _ in range(n + 1)] + dp[0] = 1 + for coin in coins: + for i in range(coin, n + 1): + dp[i] = (dp[i] + dp[i - coin]) % 1000000007 + return dp[n] +``` + +## 面试题 16.03. 交点 + +[原题链接](https://leetcode-cn.com/problems/intersection-lcci/) + +### 思路 + +解方程题目。。。就是代码写得太丑了。 + +```python +class Solution: + def intersection(self, start1: List[int], end1: List[int], start2: List[int], end2: List[int]) -> List[float]: + # kx + b = y + # 调换位置:x1 < x2, x3 < x4,避免后续判断 + x1, y1 = start1[0], start1[1] + x2, y2 = end1[0], end1[1] + if x1 > x2: + x1, x2 = x2, x1 + y1, y2 = y2, y1 + x3, y3 = start2[0], start2[1] + x4, y4 = end2[0], end2[1] + if x3 > x4: + x3, x4 = x4, x3 + y3, y4 = y4, y3 + + # 判断是否有交点 + if x2 < x3 or x4 < x1 or max(y1, y2) < min(y3, y4) or max(y3, y4) < min(y1, y2): + # 此时两线段不会有交点 + return ans + + # 计算斜率和参数 + b1, b2 = 0, 0 + if x1 == x2: + k1 = None + else: + k1 = (y2 - y1) / (x2 - x1) + b1 = y1 - k1 * x1 + if x3 == x4: + k2 = None + else: + k2 = (y4 - y3) / (x4 - x3) + b2 = y3 - k2 * x3 + + # print(k1, b1, k2, b2) + + # 判断具体条件 + if k1 is None and k2 is None: + if x1 == x3: + # 垂直重合,判断 y 的交点 + if min(y1, y2) <= max(y3, y4): + return [x1, min(y1, y2)] + if min(y3, y4) <= max(y1, y2): + return [x1, min(y3, y4)] + return [] + + if k1 is None: + return [x1, k2 * x1 + b2] + + if k2 is None: + return [x3, k1 * x3 + b1] + + if k1 == k2: + # 平行或重叠 + if b1 == b2: + # 相交 + # 找 x 区间的相交点 + if x3 >= x1 and x3 <= x2: + return [x3, y3] + if x4 >= x1 and x4 <= x2: + return [x4, y4] + if x1 >= x3 and x1 <= x4: + return [x1, y1] + if x2 >= x3 and x2 <= x4: + return [x2, y2] + return [] + + # 相交,求交点 + x = (b2 - b1) / (k1 - k2) + y = k1 * x + b1 + if x < x1 or x < x3 or x > x2 or x > x4 or y < min(y1, y2) or y < min(y3, y4) or y > max(y1, y2) or y > max(y3, y4): + return [] + return [x, y] +``` + +## 面试题24. 反转链表 + +[原题链接](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/) + +### 解一 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + pre = None + while head: + next_node = head.next + head.next = pre + pre = head + head = next_node + return pre +``` + +### 解二:递归法 + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + if head is None or head.next is None: + return head + # 自顶向下,把 head 的后面的头传进去翻转,得到的是翻转链表的尾巴,后面链表翻转完的尾巴就是 head.next + cur = self.reverseList(head.next) + # 翻转最后一个 head。由于链表翻转完的尾巴就是 head.next,要让 head 变为最后一个,那就是 head.next.next = head + head.next.next = head + # 断开链接 + head.next = None + return cur +``` + +## 面试题56 - I. 数组中数字出现的次数 + +[原题链接](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) + +### 思路:异或 + +1. 求出所有数异或的结果 +2. 结果中为 1 的位表示两个数不同数字的位 +3. 根据这个为 1 的位置把 `nums` 分成两组分别异或就可以得出结果 + +```python +class Solution: + def singleNumbers(self, nums: List[int]) -> List[int]: + ret = 0 + for n in nums: + ret ^= n + # 找出从右到左第一个为 1 的数 + index = 0 + while ret & 1 == 0: + # 右移 1 位 + ret >>= 1 + index += 1 + a, b = 0, 0 + for n in nums: + if (n >> index) & 1 == 0: + a ^= n + else: + b ^= n + return [a, b] +``` + +## 面试题57 - II. 和为s的连续正数序列 + +[原题链接](https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) + +### 滑动窗口法 + +```python +class Solution: + def findContinuousSequence(self, target: int) -> List[List[int]]: + s = 0 + tmp = [] + res = [] + for i in range(1, target): + s += i + tmp.append(i) + while s >= target: + if s == target: + res.append(tmp[:]) + s -= tmp[0] + del tmp[0] + return res +``` + +## 面试题59 - II. 队列的最大值 + +[原题链接](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/) + +### 思路 + +用一个双端队列,将队列最大值永远放在双端队列的队首。 + + + +#### **Python** + +```python +class MaxQueue: + + def __init__(self): + self.m = 0 + self.queue = [] + self.deque = [] + + def max_value(self) -> int: + if len(self.queue) == 0: + return -1 + return self.deque[0] + + def push_back(self, value: int) -> None: + self.queue.append(value) + # 维护 deque + while len(self.deque) > 0 and self.deque[-1] < value: + # 从尾部删除这个值 + self.deque.pop() + self.deque.append(value) + + def pop_front(self) -> int: + if len(self.queue) == 0: + return -1 + first = self.queue[0] + del self.queue[0] + if first == self.deque[0]: + del self.deque[0] + return first + +# Your MaxQueue object will be instantiated and called as such: +# obj = MaxQueue() +# param_1 = obj.max_value() +# obj.push_back(value) +# param_3 = obj.pop_front() +``` + +#### **Go** + +```go +type MaxQueue struct { + Queue []int + Deque []int +} + + +func Constructor() MaxQueue { + var maxQueue MaxQueue + maxQueue.Queue = make([]int, 0) + maxQueue.Deque = make([]int, 0) + return maxQueue +} + + +func (this *MaxQueue) Max_value() int { + if len(this.Queue) == 0 { + return -1 + } + return this.Deque[0] +} + + +func (this *MaxQueue) Push_back(value int) { + // 维护双端队列 + this.Queue = append(this.Queue, value) + for len(this.Deque) > 0 && value > this.Deque[len(this.Deque) - 1] { + // 最后一个值出队 + this.Deque = this.Deque[:len(this.Deque) - 1] + } + this.Deque = append(this.Deque, value) +} + + +func (this *MaxQueue) Pop_front() int { + if len(this.Queue) == 0 { + return -1 + } + first := this.Queue[0] + if first == this.Deque[0] { + // 删除第一个元素 + this.Deque = this.Deque[1:] + } + this.Queue = this.Queue[1:] + return first +} + + +/** + * Your MaxQueue object will be instantiated and called as such: + * obj := Constructor(); + * param_1 := obj.Max_value(); + * obj.Push_back(value); + * param_3 := obj.Pop_front(); + */ +``` + + + +## 面试题62. 圆圈中最后剩下的数字 + +[原题链接](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) + +### 思路 + +我们用 `f(n, m)` 表示从 n 个数中每次删除第 m 个数(共删除了 n - 1 次),最后留下的那个数的**序号**。 + +我们从 `f(n, m)` 场景下删除的第 1 个数是从序号 0 开始,向后数 m 个数得到的。删除第一个数后,将剩下 n - 1 个数,此时场景变为 `f(n - 1, m)`,用于表示从 n - 1 个数中每次删除第 m 个数最后留下的数的**序号**。 + +在往下看之前,我们先达成一个共识:**`f(n, m)` 与 `f(n - 1, m)` 得到的数是同一个数(最后剩下的数在每一轮中都不可能被删除),只是在它们所在的场景下,这个数字的序号不同罢了。** + +那么,何谓「所在场景,序号不同」? + +这里所说的「序号」与**所在场景下首次选取删除数字的出发点有关**,我们直接看下题目给出的 `n = 5, m = 3` 这个例子,已知答案为 3。 + +### 不同场景下的不同序号 + +#### f(n, m) 场景 + +此时 `n = 5`,由于我们从第 1 个数字出发,所以从第 1 个数字开始编号: + +``` +数字: +0 1 2 3 4 +序号: +0 1 2 3 4 +``` + +可以看到答案 3 在该场景下的序号为 3。 + +#### f(n - 1, m) 场景 + +此时,我们已经在 `f(n, m)` 场景下删除一个数了,这个数是 2,因此我们要从 3 开始重新编号: + +``` +数字: +0 1 3 4 +序号: +2 3 0 1 +``` + +答案 3 在该场景下的序号为 0。 + +### 两者序号的关系 + +我们知道,从 `f(n - m)` 场景下删除的第一个数的**序号**是 `(m - 1) % n`,那么 `f(n - 1, m)` 场景将使用被删除数字的下一个数,即序号 `m % n` 作为它的 0 序号。 + +设 `f(n - 1, m)` 的结果为 `x`,`x` 是从 `f(n, m)` 场景下序号为 `m % n` 的数字出发所获得的结果,因此,我们可以得出:`m % n + x` 是该数字在 `f (n, m)` 场景下的结果序号。即: + +``` +f(n, m) = m % n + x +``` +但由于 `m % n + x` 可能会超过 n 的范围,所以我们再取一次模: + +``` +f(n , m) = (m % n + x) % n = (m + x) % n +``` + +将 `f(n - 1, m)` 代回,得到递推公式: + +``` +f(n, m) = (m + f(n - 1, m)) % n +``` + +有了递推公式后,想递归就递归,想迭代就迭代咯~ + +### 具体实现 + +```python +sys.setrecursionlimit(100000) + +class Solution: + def lastRemaining(self, n: int, m: int) -> int: + return self.f(n, m) + + def f(self, n, m): + if n == 0: + return 0 + x = self.f(n - 1, m) + return (m + x) % n +``` + +各位大佬的图实在画得太好了,我就不献丑了(逃 + +## 面试题68 - I. 二叉搜索树的最近公共祖先 + +同:235. 二叉搜索树的最近公共祖先 \ No newline at end of file diff --git a/docs/weekly/README.md b/docs/weekly/README.md index 3ca7e013a..d81137f54 100644 --- a/docs/weekly/README.md +++ b/docs/weekly/README.md @@ -420,7 +420,7 @@ class Solution(object): [原题链接](https://leetcode-cn.com/contest/weekly-contest-124/problems/rotting-oranges/) -### 思路 +#### 思路 基本思路:从 `1` 出发搜索,返回找到 `2` 需要走的步数(即分钟数),求得该步数的最大值。 @@ -474,6 +474,82 @@ class Solution(object): return float('inf') ``` +#### 20200304 重写 + + + +#### **Python** + +```python +class Solution: + def orangesRotting(self, grid: List[List[int]]) -> int: + bad_queue = [] + bad = 0 + good = 0 + # 计算橘子情况 + for i in range(len(grid)): + for j in range(len(grid[0])): + if grid[i][j] == 1: + good += 1 + elif grid[i][j] == 2: + bad += 1 + # 入队 + bad_queue.append([i, j]) + + # 根据统计情况返回 + if good == 0: + # 没有新鲜的橘子 + return 0 + if bad == 0: + # 没有坏的橘子 + return -1 + + res = 0 + i_length = len(grid) + j_length = len(grid[0]) + while len(bad_queue) > 0: + # 分钟数 + res += 1 + # 取出第一批橘子 + for i in range(len(bad_queue)): + first = bad_queue[0] + del bad_queue[0] + # 开始感染其他橘子 + i = first[0] + j = first[1] + + if i - 1 >= 0 and grid[i - 1][j] == 1: + good -= 1 + grid[i - 1][j] = 2 + bad_queue.append([i - 1, j]) + if i + 1 < i_length and grid[i + 1][j] == 1: + good -= 1 + grid[i + 1][j] = 2 + bad_queue.append([i + 1, j]) + if j - 1 >= 0 and grid[i][j - 1] == 1: + good -= 1 + grid[i][j - 1] = 2 + bad_queue.append([i, j - 1]) + if j + 1 < j_length and grid[i][j + 1] == 1: + good -= 1 + grid[i][j + 1] = 2 + bad_queue.append([i, j + 1]) + # 是否已经没有新鲜橘子 + if good <= 0: + return res + + return -1 if good > 0 else res +``` + +#### **Go** + +```go + + +``` + + + ---- ## 第 129 场周赛 diff --git a/index.php b/index.php deleted file mode 100644 index f7c62c01e..000000000 --- a/index.php +++ /dev/null @@ -1,2 +0,0 @@ -