Skip to content

Lc offer #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,283 changes: 6,283 additions & 0 deletions LeetCode/剑指offer/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//题目链接:https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/?envType=study-plan&id=lcof
package main

//BST 的最重要的性质为:中序遍历为递增序列
//我们一定是需要对 BST 进行中序遍历的,因为要改变指针的指向,所以遍历的同时,需要记录当前节点的前驱结点,声明变量 pre 和 head 为 *TreeNode 类型,pre 记录当前节点的前驱结点,head 记录最终返回的头结点。
//中序遍历过程中,处理当前节点 cur 时,若 pre 为空,说明当前节点为中序遍历初始值,pre 为空,那自然也不需要调整 pre 和 cur 的指向,
//若 pre 非空,调整 cur.Right 和 cur.Left 的指向,pre.Right = cur,cur.Left = pre;
//处理完成后,pre = cur,当前节点为中序遍历中下一个节点的前驱结点。
//递归结束后,pre 指向 BST 中节点值最大的节点,即中序遍历最后一个节点。
//然后,调整 pre.Right 和 head.Left 的指向,形成循环链表。
//
//很棒的一道题目。
//这道题 LeetCode 上不支持 Go 语言,我去牛客网验证了一下,牛客网没有要求循环链表,代码倒数第三行和倒数第四行需要注释掉才能通过,LeetCode 要求循环,将其取消注释,理论上可以通过。

type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}

/**
*
* @param pRootOfTree TreeNode类
* @return TreeNode类
*/
func Convert( pRootOfTree *TreeNode ) *TreeNode {
// write code here
var pre,head *TreeNode
var inorder func(cur *TreeNode)
inorder = func(cur *TreeNode) {
if cur == nil{
return
}
inorder(cur.Left)
if pre == nil{
head = cur
} else {
pre.Right = cur
cur.Left = pre
}
pre = cur
inorder(cur.Right)
}
inorder(pRootOfTree)
// head.Left = pre
// pre.Right = head
return head
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//题目链接:https://leetcode.cn/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/
package main


//方法一:DFS,枚举每一条从根节点到叶子节点的路径,遍历到叶子节点时,如果路径和恰好为 target,说明找到一个满足条件的路径。
//注意代码中 copy(x,path) 操作,将 path 进行拷贝后再加入 res,若直接 res = append(res,path),之后的路径将当前路径覆盖后,res 中的切片也会发生变化,这里是个小坑,需要注意一下。



Expand All @@ -11,7 +14,7 @@ type TreeNode struct {
Right *TreeNode
}

//

func pathSum(root *TreeNode, target int) [][]int {
res := [][]int{}
path := []int{}
Expand Down Expand Up @@ -40,3 +43,74 @@ func pathSum(root *TreeNode, target int) [][]int {
}

// 本题还有BFS解法,我改了很久总是出问题,ac后再附上


// 回来了,已 ac
//方法二:BFS
//pair 结构体保存节点以及从根节点到该节点的路径值。
//BFS 必定要用到队列,队列初始化时,加入根节点的 pair,sum 属性为 root.Val,同时,为了获取到叶子节点到根节点的路径,
//我们声明一个 parent 字典,保存节点的父节点,在进行 BFS 的同时更新 parent。
type pair struct {
node *TreeNode
sum int
}

func pathSum_2(root *TreeNode, target int) [][]int {
res := [][]int{}
// 判断 root 是否为空,对 root 为空的情况提前处理
if root == nil{
return res
}
// 队列初始化只保存根节点,利用队列实现 BFS
q := []pair{{root,root.Val}}
// parent字典帮助寻找叶子节点到根节点的路径
parent := map[*TreeNode]*TreeNode{}
// getPath利用parent寻找叶子节点到根节点的路径
getPath := func(node *TreeNode) []int {
path := []int{}
// 不断向根节点回退
for ;node!=nil;node=parent[node]{
path = append(path,node.Val)
}
// path倒序,变为从根节点到叶子节点的路径
left,right := 0,len(path)-1
for left < right{
path[left],path[right] = path[right],path[left]
left ++
right --
}
return path
}
// BFS
for len(q) != 0{
// 取队首元素
cur := q[0]
node := cur.node
// 先更新 parent,将孩子节点指向该节点
if node.Left != nil{
parent[node.Left] = node
}
if node.Right != nil{
parent[node.Right] = node
}
// 走到叶节点
if node.Left==nil && node.Right==nil{
// 且路径和等于target,更新res
if cur.sum == target{
res = append(res,getPath(node))
}
} else {
// 若未到根节点
// 将孩子节点加入队列
if node.Left != nil{
q = append(q,pair{node.Left,cur.sum+node.Left.Val})
}
if node.Right != nil{
q = append(q,pair{node.Right,cur.sum+node.Right.Val})
}
}
// 队首元素出队
q = q[1:]
}
return res
}
122 changes: 122 additions & 0 deletions LeetCode/剑指offer/day17_排序(中等)/数据流的中位数.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 题目链接:https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/?envType=study-plan&id=lcof
// day17/31
// 第 17 天主题为:排序(中等)
// 包含两道题目:
// 剑指offer40.最小的k个数
// 剑指offer41.数据流的中位数

package main

import "container/heap"

//解题思路:用两个堆来维护数据流,将数据流根据元素的大小一分为二
//大根堆维护数据流元素值较小的一半,小根堆维护数据流元素值较大的一半
//当数据流长度为 偶数 时,大小根堆长度相同,当数据流长度为奇数时,我们规定,小根堆存储中位数。

//向数据流添加元素 num 时,可分为两种情况
//
//- 1.大根堆与小根堆长度相等,此时,应向小根堆添加元素,但添加的元素并不一定是 num
//- 若 num 大于小根堆堆顶元素值,则 num 属于元素值较大的一部分,num 直接插入大根堆
//- 否则,先将 num 插入大根堆,然后取出小根堆堆顶元素,插入大根堆
//- 2.大根堆与小根堆长度不相等,此时,应向大根堆添加元素,但添加的元素同样并不一定是 num
//- 若 num 大于小根堆堆顶元素值,将 num 插入小根堆,然后取出小根堆堆顶元素,插入大根堆
//- 否则,num 直接插入大根堆


type maxHeap []int // 大顶堆
type minHeap []int // 小顶堆

// 每个堆都要heap.Interface的五个方法:Len, Less, Swap, Push, Pop
// 其实只有Less的区别。

// Len 返回堆的大小
func (m maxHeap) Len() int {
return len(m)
}
func (m minHeap) Len() int {
return len(m)
}

// Less 决定是大优先还是小优先
func (m maxHeap) Less(i, j int) bool { // 大根堆
return m[i] > m[j]
}
func (m minHeap) Less(i, j int) bool { // 小根堆
return m[i] < m[j]
}

// Swap 交换下标i, j元素的顺序
func (m maxHeap) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func (m minHeap) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}

// Push 在堆的末尾添加一个元素,注意和heap.Push(heap.Interface, interface{})区分
func (m *maxHeap) Push(v interface{}) {
*m = append(*m, v.(int))
}
func (m *minHeap) Push(v interface{}) {
*m = append(*m, v.(int))
}

// Pop 删除堆尾的元素,注意和heap.Pop()区分
func (m *maxHeap) Pop() interface{} {
old := *m
n := len(old)
v := old[n - 1]
*m = old[:n - 1]
return v
}
func (m *minHeap) Pop() interface{} {
old := *m
n := len(old)
v := old[n - 1]
*m = old[:n - 1]
return v
}

// MedianFinder 维护两个堆,前一半是大顶堆,后一半是小顶堆,中位数由两个堆顶决定
type MedianFinder struct {
maxH *maxHeap
minH *minHeap
}

// Constructor 建两个空堆
func Constructor() MedianFinder {
return MedianFinder{
new(maxHeap),
new(minHeap),
}
}


func (m *MedianFinder) AddNum(num int) {
if m.maxH.Len() == m.minH.Len() {
if m.minH.Len() == 0 || num >= (*m.minH)[0] {
heap.Push(m.minH, num)
} else {
heap.Push(m.maxH, num)
top := heap.Pop(m.maxH).(int)
heap.Push(m.minH, top)
}
} else {
if num > (*m.minH)[0] {
heap.Push(m.minH, num)
bottle := heap.Pop(m.minH).(int)
heap.Push(m.maxH, bottle)
} else {
heap.Push(m.maxH, num)
}
}
}

// FindMediam 输出中位数
func (m *MedianFinder) FindMedian() float64 {
if m.minH.Len() == m.maxH.Len() {
return float64((*m.maxH)[0]) / 2.0 + float64((*m.minH)[0]) / 2.0
} else {
return float64((*m.minH)[0])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 题目链接:https://leetcode.cn/problems/nge-tou-zi-de-dian-shu-lcof/?envType=study-plan&id=lcof
// day29/31
// 第 29 天主题为:动态规划(困难)
// 包含三道题目:
// 剑指offer19.正则表达式匹配
// 剑指offer49.丑数
// 剑指offer60.n个骰子的点数
package main

//这道题我的第一反应是做排列组合问题。看题解才知道是 dp 问题。

//1 个骰子共有 6 中可能的点数,两个骰子点数范围为 [2,12],共有 11 种可能的点数,对 n 个骰子,点数范围为 [n,6*n],共有 5 * n + 1 种可能的点数。
//
//如果我们知道了 x-1 个骰子所有的点数及其概率,那 x 个骰子的点数及其概率可以通过其推出。对 x 个骰子,也就是在 x-1 的基础上添加一个骰子,
//该骰子的点数可为 1-6,每个点数的概率均为 1/6,那我们可以对 x-1 个骰子的点数进行遍历,再对 1-6 开启第二层遍历,设当前遍历到 x-1 个骰子的点数为 m,
//第 x 个骰子遍历到的点数为 n,那么 dp[i][m+n] += dp[x-1][m]/6
//
//动态规划三步:
//1. 确定dp数组及下标含义:dp[i][j] 代表 i 个骰子 点数为 j 的概率,则 len(dp) = n+1, len(dp[0]) = 6*n+1
//2. 数组初始化:一个骰子的点数及其概率我们是已知的,dp[1][1] - dp[1][6] 的值均为 1/6,dp 数组中其余值初始化为 0 即可,其余有效信息将由 1 个骰子的点数及其概率得出。
//3. 状态转移方程
//
//dp[n][x]=\sum_{i=1}^6dp[n-1][x-i]*1/6
//
//可以看出,该状态转移方程是一个理论上合理的方案,但用此公式求解的话,需要考虑越界的问题,是一种逆向的递推公式,我们将其改为正向即可,
//通过 x-1 个骰子的点数推导 x 个骰子的点数。

func dicesProbability(n int) []float64 {
// dp数组初始化
dp := make([][]float64,n+1)
for i:=0;i<n+1;i++{
dp[i] = make([]float64,6*n+1)
}
for i:=1;i<=6;i++{
dp[1][i] = float64(1)/float64(6)
}
// 已知1个骰子的点数及其概率,向后推导至n个骰子的点数
for i:=2;i<=n;i++{
// 通过 i-1 个骰子的点数,求第 i 个骰子的点数
for j:=i-1;j<=6*(i-1);j++{
// 每个骰子的点数为1-6,概率均为1/6
for k:=1;k<=6;k++{
dp[i][j+k] += (dp[i-1][j])/6
}
}
}
return dp[n][n:]
}

//另外,感觉这道题是 dp 思路很好的练习题目
//一个阶段有个多个状态,且当前阶段的每个状态会影响到下一个阶段的多个状态(至多6个)
Loading