Skip to content

Commit

Permalink
add week08
Browse files Browse the repository at this point in the history
  • Loading branch information
yehong committed May 25, 2021
1 parent 4a22b72 commit 57f8da3
Show file tree
Hide file tree
Showing 13 changed files with 430 additions and 10 deletions.
90 changes: 90 additions & 0 deletions week01/92.reverseBetween.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions week01/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|双指针、快排思想|
Expand Down
32 changes: 23 additions & 9 deletions week04/200.numIslands.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,45 @@ 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',也就是把相邻的陆地都夷为平地,
// 只保留一个'1',然后陆地个数count加一
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)
Expand Down
1 change: 1 addition & 0 deletions week04/46.permute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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++ {
Expand Down
5 changes: 4 additions & 1 deletion week04/47.permuteUnique.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ 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++ {
// 元素已经添加过,跳过
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
}
Expand Down
52 changes: 52 additions & 0 deletions week08/208.trie.go
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 21 additions & 0 deletions week08/208.trie_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
9 changes: 9 additions & 0 deletions week08/212.findWords.go
Original file line number Diff line number Diff line change
@@ -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
}
55 changes: 55 additions & 0 deletions week08/695.maxAreaOfIsland.go
Original file line number Diff line number Diff line change
@@ -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
}
66 changes: 66 additions & 0 deletions week08/79.exist.go
Original file line number Diff line number Diff line change
@@ -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/
Loading

0 comments on commit 57f8da3

Please sign in to comment.