diff --git a/week01/92.reverseBetween.go b/week01/92.reverseBetween.go
new file mode 100644
index 0000000..3084519
--- /dev/null
+++ b/week01/92.reverseBetween.go
@@ -0,0 +1,90 @@
+package week01
+
+// 92. 反转链表 II
+// 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表 。
+// 示例 1:
+// 输入:head = [1,2,3,4,5], left = 2, right = 4
+// 输出:[1,4,3,2,5]
+// @lc: https://leetcode-cn.com/problems/reverse-linked-list-ii/
+
+// 方法一:找到[left,right]子区间,反转子链表后再拼接
+// 1. 从虚拟头节点走 left-1 步,来到 left 节点的前一个节点
+// 2. 从 pre 再走 right-left+1 步,来到 right 节点(注意此时是right节点,而不是right的前一个 或 后一个节点)
+// 3. 切断出一个子链表(截取链表),同第 206 题,反转链表的子区间
+// 4. 接回到原来的链表中
+// 时间复杂度:O(n)
+// 空间复杂度:O(1)
+func reverseBetween1(head *ListNode, left, right int) *ListNode {
+ dummy := &ListNode{Val: -1} // 哑结点,链表通用解法
+ dummy.Next = head
+ // 1. 从虚拟头节点走 left-1 步,来到 left 节点的前一个节点
+ pre := dummy
+ for i := 0; i < left-1; i++ {
+ pre = pre.Next
+ }
+
+ // 2. 从 pre 再走 right-left+1 步,来到 right 节点
+ rightNode := pre
+ for i := 0; i < right-left+1; i++ {
+ rightNode = rightNode.Next
+ }
+
+ // 3. 切断出一个子链表(截取链表),并反转
+ leftNode := pre.Next // 子链表的头结点
+ cur := rightNode.Next // right的下一个节点,先保存起来,便于第4步拼接
+
+ // 切断
+ pre.Next = nil
+ rightNode.Next = nil
+ // 反转子链表
+ reverseList(leftNode)
+
+ // 4. 接回到原来的链表中
+ pre.Next = rightNode
+ leftNode.Next = cur
+
+ return dummy.Next
+}
+
+// reverseList 反转单链表
+// 双指针,先保存下一个节点为tmp,然后把cur反转(cur.Next指向前一个),最后再同时更新pre和cur
+func reverseList(head *ListNode) {
+ var pre *ListNode = nil
+ cur := head
+ for cur != nil {
+ tmp := cur.Next // 先保存下一个节点,因为马上要断开
+ cur.Next = pre // 反转操作
+ pre = cur // pre指针后移
+ cur = tmp // cur指针后移
+ }
+}
+
+// 方法二:头部插入法,方法一的问题在于:如果left=1,right=n(链表长度)时,会遍历2次链表
+// 1. 从虚拟头节点走 left-1 步,来到 left 节点的前一个节点
+// 2. 从 left-1..right 依次将当前节点插入到 子区间的头部,也就是拼接到 pre后面
+// 第二步的具体步骤:
+// 2.1. pre指针永远不动,指向的是left 的前一个节点
+// 2.2. cur指针指向 待反转区域的第一个节点 left
+// 2.3. next指针永远指向 cur的下一个节点,循环过程中,cur 变化后 next 会随着变化
+// @ref: https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/fan-zhuan-lian-biao-ii-by-leetcode-solut-teyq/
+// 时间复杂度:O(n)
+// 空间复杂度:O(1)
+func reverseBetween2(head *ListNode, left, right int) *ListNode {
+ dummy := &ListNode{Val: -1} // 哑结点,链表通用解法
+ dummy.Next = head
+
+ // 1. 从虚拟头节点走 left-1 步,来到 left 节点的前一个节点
+ pre := dummy
+ for i := 0; i < left-1; i++ {
+ pre = pre.Next
+ }
+
+ cur := pre.Next
+ for i := 0; i < right-left; i++ {
+ next := cur.Next
+ cur.Next = next.Next
+ next.Next = pre.Next
+ pre.Next = next
+ }
+ return dummy.Next
+}
diff --git a/week01/README.md b/week01/README.md
index 2a029ef..09150b2 100644
--- a/week01/README.md
+++ b/week01/README.md
@@ -64,6 +64,7 @@ todos
[61. 旋转链表](https://leetcode-cn.com/problems/rotate-list/)|[61.rotateRight.go](61.rotateRight.go)|M|双指针、取模|
[66. 加一](https://leetcode-cn.com/problems/plus-one/)|[66.plusOne.go](66.plusOne)|S|x|
[88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)|[88.merge.go](88.merge.go)|S|双指针|
+[92. 反转链表 II](https://leetcode-cn.com/problems/reverse-linked-list-ii/)|[92.reverseBetween.go](92.reverseBetween.go)|M|双指针|
[155. 最小栈](https://leetcode-cn.com/problems/min-stack/)|[155.minStack.go](155.minStack.go)|S|辅助栈|
[189. 旋转数组](https://leetcode-cn.com/problems/rotate-array/)|[189.rotate.go](189.rotate.go)|S|取模、交换|
[283. 移动零](https://leetcode-cn.com/problems/move-zeroes/)|[283.moveZeroes.go](283.moveZeroes.go)|S|双指针、快排思想|
diff --git a/week04/200.numIslands.go b/week04/200.numIslands.go
index 83d9146..56c5430 100644
--- a/week04/200.numIslands.go
+++ b/week04/200.numIslands.go
@@ -22,8 +22,15 @@ func numIslands(grid [][]byte) int {
return 0
}
- // 行、列
- row, col := len(grid), len(grid[0])
+ // 行、列数
+ rows, cols := len(grid), len(grid[0])
+ // 上下左右 与当前位置的offset
+ directions := [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
+
+ // 判断当前位置是否在网格内
+ inGrid := func(i, j int) bool {
+ return i >= 0 && i < rows && j >= 0 && j < cols
+ }
// dfsMarking 的作用是如果当前位置是'1',则把它的前后左右都标记为'0',
// 以此类推,递归把他的子子孙孙都标记为'0',也就是把相邻的陆地都夷为平地,
@@ -31,22 +38,29 @@ func numIslands(grid [][]byte) int {
var dfsMarking func(int, int) // 谨遵泛型递归四部曲
dfsMarking = func(i, j int) {
// 1. terminaort 递归终止条件,i、j越界或当前位置不是'1'
- if i < 0 || j < 0 || i >= row || j >= col || '1' != grid[i][j] {
+ if !inGrid(i, j) || '1' != grid[i][j] {
return
}
+
// 2. process current logic
grid[i][j] = '0'
// 3. drill down 递推下探将它的上下左右以及子子孙孙的上下左右都做dfsMarking操作
- dfsMarking(i-1, j) // 上
- dfsMarking(i+1, j) // 下
- dfsMarking(i, j-1) // 左
- dfsMarking(i, j+1) // 右
+ // dfsMarking(i-1, j) // 上
+ // dfsMarking(i+1, j) // 下
+ // dfsMarking(i, j-1) // 左
+ // dfsMarking(i, j+1) // 右
+
+ // 以上4行代码可以简化,预先定义一个方向偏离量数组,防止循环里写即可
+ for _, direct := range directions {
+ dfsMarking(i+direct[0], j+direct[1])
+ }
+
// 4. revert states, nothing todo
}
var count int
- for i := 0; i < row; i++ {
- for j := 0; j < col; j++ {
+ for i := 0; i < rows; i++ {
+ for j := 0; j < cols; j++ {
if '1' == grid[i][j] {
count++
dfsMarking(i, j)
diff --git a/week04/46.permute.go b/week04/46.permute.go
index 58b6769..3d788cd 100644
--- a/week04/46.permute.go
+++ b/week04/46.permute.go
@@ -45,6 +45,7 @@ func permute(nums []int) [][]int {
// 只有长度相等时,才是全排列
if len(path) == len(nums) {
res = append(res, append([]int{}, path...)) // 注意:需要拷贝
+ return
}
// 选择+标记,处理结果、再撤销选择+撤销标记
for i := 0; i < len(nums); i++ {
diff --git a/week04/47.permuteUnique.go b/week04/47.permuteUnique.go
index 9bb13b0..9be2e8b 100644
--- a/week04/47.permuteUnique.go
+++ b/week04/47.permuteUnique.go
@@ -42,6 +42,7 @@ func permuteUnique(nums []int) [][]int {
backtrack = func(path []int) {
if len(path) == len(nums) {
res = append(res, append([]int{}, path...)) // 注意:需要拷贝
+ return
}
// 选择、处理逻辑、回撤选择
for i := 0; i < len(nums); i++ {
@@ -49,7 +50,9 @@ func permuteUnique(nums []int) [][]int {
if visited[i] {
continue
}
- // 上一个元素和当前相同,并且没有访问过就跳过,注意这里:去重逻辑
+ // 当前值等于前一个值: 两种情况:
+ // 1. nums[i-1] 没用过 说明回溯到了同一层 此时接着用num[i] 则会与 同层用num[i-1] 重复
+ // 2. nums[i-1] 用过了 说明此时在num[i-1]的下一层 相等不会重复
if i != 0 && nums[i] == nums[i-1] && !visited[i-1] {
continue
}
diff --git a/week08/208.trie.go b/week08/208.trie.go
new file mode 100644
index 0000000..bcbc7c6
--- /dev/null
+++ b/week08/208.trie.go
@@ -0,0 +1,52 @@
+package week08
+
+// 208. 实现 Trie (前缀树)
+// Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
+// 请你实现 Trie 类:
+// Trie() 初始化前缀树对象。
+// void insert(String word) 向前缀树中插入字符串 word。
+// boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
+// boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
+
+// Trie是便于word插入和查找的数据结构
+type Trie struct {
+ children [26]*Trie
+ isLeaf bool
+}
+
+func Constructor() *Trie {
+ return &Trie{}
+}
+
+func (t *Trie) Insert(word string) {
+ node := t
+ for _, char := range word {
+ char -= 'a' // ASCII code
+ if node.children[char] == nil {
+ node.children[char] = &Trie{}
+ }
+ node = node.children[char] // 一层层进入子节点
+ }
+ node.isLeaf = true // 最终的子节点
+}
+
+func (t *Trie) SearchPrefix(prefix string) *Trie {
+ node := t
+ for _, char := range prefix {
+ char -= 'a'
+ if node.children[char] == nil {
+ return nil
+ }
+ node = node.children[char]
+ }
+ return node
+}
+
+func (t *Trie) Search(word string) bool {
+ node := t.SearchPrefix(word)
+ return node != nil && node.isLeaf
+}
+
+func (t *Trie) StartsWith(prefix string) bool {
+ return t.SearchPrefix(prefix) != nil
+}
diff --git a/week08/208.trie_test.go b/week08/208.trie_test.go
new file mode 100644
index 0000000..50523c9
--- /dev/null
+++ b/week08/208.trie_test.go
@@ -0,0 +1,21 @@
+package week08
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// run: go test -v -run Test_trie
+func Test_trie(t *testing.T) {
+ assert := assert.New(t)
+ trie := Constructor()
+
+ trie.Insert("apple")
+ assert.True(trie.Search("apple"), "Search apple")
+ assert.False(trie.Search("app"), "Search app")
+ assert.True(trie.StartsWith("app"), "StartsWith app")
+
+ trie.Insert("app")
+ assert.True(trie.Search("app"), "Search app")
+}
diff --git a/week08/212.findWords.go b/week08/212.findWords.go
new file mode 100644
index 0000000..017d45b
--- /dev/null
+++ b/week08/212.findWords.go
@@ -0,0 +1,9 @@
+package week08
+
+// 212. 单词搜索 II
+// 给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words,找出所有同时在二维网格和字典中出现的单词。
+// 单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
+// @lc: https://leetcode-cn.com/problems/word-search-ii/
+func findWords(board [][]byte, words []string) []string {
+ return nil
+}
diff --git a/week08/695.maxAreaOfIsland.go b/week08/695.maxAreaOfIsland.go
new file mode 100644
index 0000000..0847d53
--- /dev/null
+++ b/week08/695.maxAreaOfIsland.go
@@ -0,0 +1,55 @@
+package week08
+
+// 695. 岛屿的最大面积
+// 给定一个包含了一些 0 和 1 的非空二维数组 grid 。
+// 一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
+// 找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
+// @lc: https://leetcode-cn.com/problems/max-area-of-island/
+
+// dfs递归
+func maxAreaOfIsland(grid [][]int) int {
+ // 合法性判断
+ if len(grid) == 0 || len(grid[0]) == 0 {
+ return 0
+ }
+
+ // 行、列数
+ rows, cols := len(grid), len(grid[0])
+ // 上下左右 与当前位置的offset
+ directions := [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
+ // 判断当前位置是否在网格内
+ inGrid := func(i, j int) bool {
+ return i >= 0 && i < rows && j >= 0 && j < cols
+ }
+
+ var dfsMarking func(i, j int) int
+ dfsMarking = func(i, j int) int {
+ // terminator, 跳出网格 或 当前位置不是陆地 '1',即被mark过了
+ if !inGrid(i, j) || 1 != grid[i][j] {
+ return 0
+ }
+
+ // mark 为'2',且递归把他的上下左右已经子子孙孙都mark
+ grid[i][j] = 2
+ area := 1
+ for _, direct := range directions {
+ area += dfsMarking(i+direct[0], j+direct[1])
+ }
+ return area
+ }
+
+ max := func(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+ }
+ // 遍历网格,求 maxArea
+ var res int
+ for i := 0; i < rows; i++ {
+ for j := 0; j < cols; j++ {
+ res = max(res, dfsMarking(i, j))
+ }
+ }
+ return res
+}
diff --git a/week08/79.exist.go b/week08/79.exist.go
new file mode 100644
index 0000000..5369569
--- /dev/null
+++ b/week08/79.exist.go
@@ -0,0 +1,66 @@
+package week08
+
+// 79. 单词搜索
+// 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
+// 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
+// 示例 1:
+// 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
+// 输出:true
+// @lc: https://leetcode-cn.com/problems/word-search/
+
+// DFS+回溯
+func exist(board [][]byte, word string) bool {
+ // 分别为 当前位置与 上、下、左、右 的offset
+ directions := [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
+ if len(board) == 0 || len(board[0]) == 0 {
+ return false
+ }
+
+ // 初始化,rows,cols,visited
+ rows, cols := len(board), len(board[0])
+ visited := make([][]bool, rows)
+ for i := 0; i < rows; i++ {
+ visited[i] = make([]bool, cols)
+ }
+
+ // 坐标是否在 board 内,防止越界
+ inArea := func(x, y int) bool {
+ return x >= 0 && x < rows && y >= 0 && y < cols
+ }
+
+ // dfs marking func
+ n := len(word)
+ var dfs func(int, int, int) bool
+ dfs = func(i, j, begin int) bool {
+ if begin == n-1 { // terminator
+ return board[i][j] == word[begin]
+ }
+ if board[i][j] == word[begin] {
+ visited[i][j] = true // marking
+ for _, direction := range directions { // process
+ x := i + direction[0]
+ y := j + direction[1]
+ if inArea(x, y) && !visited[x][y] {
+ if dfs(x, y, begin+1) {
+ return true
+ }
+ }
+ }
+ visited[i][j] = false // revert
+ }
+ return false
+ }
+
+ // dfs marking
+ for i := 0; i < rows; i++ {
+ for j := 0; j < cols; j++ {
+ if dfs(i, j, 0) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// AC过了,懒得写单测了
+// @lc https://leetcode-cn.com/problems/word-search/solution/79-dan-ci-sou-suo-dfshui-su-golangban-be-ekcn/
diff --git a/week08/README.md b/week08/README.md
new file mode 100644
index 0000000..3769509
--- /dev/null
+++ b/week08/README.md
@@ -0,0 +1,20 @@
+# 字典树、并查集、红黑树和AVL树、位运算
+
+## 字典树
+
+## 并查集
+
+## 红黑树和AVL树
+
+## 位运算
+
+## 练习题
+
+| Title | Code | Difficulty | Points |
+| ----- | ---- | -------------------------------- |--------|
+|[208. 实现 Trie (前缀树)](https://leetcode-cn.com/problems/implement-trie-prefix-tree/)|[208.trie.go](208.trie.go)|M|trie|
+|[212. 单词搜索 II](https://leetcode-cn.com/problems/word-search-ii/)|[212.findWords.go](212.findWords.go)|H|trie、dfs+回溯|
+|为了解决212 Hard问题
刻意练几个 `dfs+回溯` 题找找感觉
其中岛屿问题、排列、组合是典型的回溯思想|
+|[79. 单词搜索](https://leetcode-cn.com/problems/word-search/)|[79.exist.go](79.exist.go)|M|dfs+回溯|
+|[剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/)|[offer38.permutation.go](offer38.permutation.go)|M|dfs+回溯|
+|[695. 岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island/)|[695.maxAreaOfIsland.go](695.maxAreaOfIsland.go)|M|dfs+回溯|
diff --git a/week08/offer38.permutation.go b/week08/offer38.permutation.go
new file mode 100644
index 0000000..eb92f19
--- /dev/null
+++ b/week08/offer38.permutation.go
@@ -0,0 +1,56 @@
+package week08
+
+import "sort"
+
+// 剑指 Offer 38. 字符串的排列
+// 输入一个字符串,打印出该字符串中字符的所有排列。
+// 你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
+// 示例:
+// 输入:s = "abc"
+// 输出:["abc","acb","bac","bca","cab","cba"]
+// @lc: https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/
+
+// dfs+回溯,类似数组全排列,[46.全排列](https://leetcode-cn.com/problems/permutations)
+// 子集、排列 等问题首先要想到 dfs+回溯
+func permutation(s string) []string {
+ res, n := []string{}, len(s)
+ if n == 0 {
+ return res
+ }
+
+ // 先排序,便于去重
+ bs := []byte(s)
+ sort.Slice(bs, func(i, j int) bool { return bs[i] < bs[j] })
+ s = string(bs)
+
+ // 标记已访问过的字符
+ visited := make([]bool, n)
+ var backtracking func(string)
+ backtracking = func(path string) {
+ // terminator,长度等于n的才是全排列字串
+ if len(path) == n {
+ res = append(res, path)
+ return
+ }
+ // 选择,处理,回撤
+ for i := 0; i < n; i++ {
+ if visited[i] {
+ continue
+ }
+ // 当前字符等于前一个字符: 有两种情况:
+ // 1. s[i-1] 没用过 说明回溯到了同一层 此时接着用num[i] 则会与 同层用num[i-1] 重复
+ // 2. s[i-1] 用过了 说明此时在num[i-1]的下一层 相等不会重复
+ if i > 0 && s[i] == s[i-1] && !visited[i-1] {
+ continue
+ }
+
+ visited[i] = true // mark
+ path += string(s[i]) // process
+ backtracking(path) // drill down
+ visited[i] = false
+ path = path[:len(path)-1]
+ }
+ }
+ backtracking("")
+ return res
+}
diff --git a/week08/offer38.permutation_test.go b/week08/offer38.permutation_test.go
new file mode 100644
index 0000000..bf16c41
--- /dev/null
+++ b/week08/offer38.permutation_test.go
@@ -0,0 +1,32 @@
+package week08
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// run: go test -run Test_permutation
+func Test_permutation(t *testing.T) {
+ cases := []struct {
+ name string
+ input string
+ expect []string
+ }{
+ {
+ name: "x1",
+ input: "abc",
+ expect: []string{"abc", "acb", "bac", "bca", "cab", "cba"},
+ },
+ {
+ name: "x2",
+ input: "aba",
+ expect: []string{"aab", "aba", "baa"},
+ },
+ }
+
+ assert := assert.New(t)
+ for _, c := range cases {
+ assert.Equal(c.expect, permutation(c.input), c.name)
+ }
+}