Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
coolalex776
Fucking Algorithm
提交
666dced7
F
Fucking Algorithm
项目概览
coolalex776
/
Fucking Algorithm
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
F
Fucking Algorithm
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
666dced7
编写于
3月 09, 2021
作者:
B
BruceCat
提交者:
GitHub
3月 09, 2021
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'master' into master
上级
022fe8af
dd200989
变更
26
隐藏空白更改
内联
并排
Showing
26 changed file
with
1212 addition
and
67 deletion
+1212
-67
README.md
README.md
+21
-8
出版推广1.jpeg
出版推广1.jpeg
+0
-0
动态规划系列/动态规划之KMP字符匹配算法.md
动态规划系列/动态规划之KMP字符匹配算法.md
+37
-2
动态规划系列/动态规划之博弈问题.md
动态规划系列/动态规划之博弈问题.md
+114
-3
动态规划系列/动态规划设计:最长递增子序列.md
动态规划系列/动态规划设计:最长递增子序列.md
+93
-7
动态规划系列/动态规划详解进阶.md
动态规划系列/动态规划详解进阶.md
+46
-3
动态规划系列/团灭股票问题.md
动态规划系列/团灭股票问题.md
+2
-2
动态规划系列/抢房子.md
动态规划系列/抢房子.md
+63
-2
动态规划系列/最优子结构.md
动态规划系列/最优子结构.md
+1
-1
动态规划系列/最长公共子序列.md
动态规划系列/最长公共子序列.md
+62
-3
动态规划系列/高楼扔鸡蛋问题.md
动态规划系列/高楼扔鸡蛋问题.md
+1
-1
数据结构系列/二叉搜索树操作集锦.md
数据结构系列/二叉搜索树操作集锦.md
+66
-3
数据结构系列/单调栈.md
数据结构系列/单调栈.md
+26
-5
数据结构系列/单调队列.md
数据结构系列/单调队列.md
+48
-2
数据结构系列/设计Twitter.md
数据结构系列/设计Twitter.md
+119
-2
算法思维系列/二分查找详解.md
算法思维系列/二分查找详解.md
+2
-2
算法思维系列/双指针技巧.md
算法思维系列/双指针技巧.md
+28
-3
算法思维系列/滑动窗口技巧.md
算法思维系列/滑动窗口技巧.md
+2
-2
高频面试系列/LRU算法.md
高频面试系列/LRU算法.md
+118
-2
高频面试系列/koko偷香蕉.md
高频面试系列/koko偷香蕉.md
+42
-2
高频面试系列/二分查找判定子序列.md
高频面试系列/二分查找判定子序列.md
+64
-2
高频面试系列/合法括号判定.md
高频面试系列/合法括号判定.md
+48
-2
高频面试系列/打印素数.md
高频面试系列/打印素数.md
+38
-2
高频面试系列/接雨水.md
高频面试系列/接雨水.md
+97
-2
高频面试系列/消失的元素.md
高频面试系列/消失的元素.md
+47
-2
高频面试系列/缺失和重复的元素.md
高频面试系列/缺失和重复的元素.md
+27
-2
未找到文件。
README.md
浏览文件 @
666dced7
...
...
@@ -3,7 +3,7 @@ English version repo and Gitbook is on [english branch](https://github.com/labul
# labuladong 的算法小抄
<p
align=
'center'
>
<a
href=
"https://labuladong.git
book.io/algo"
target=
"_blank"
><img
alt=
"Website"
src=
"https://img.shields.io/website?label=%E5%9C%A8%E7%BA%BF%E7%94%B5%E5%AD%90%E4%B9%A6&style=flat-square&down_color=blue&down_message=%E7%82%B9%E8%BF%99%E9%87%8C&up_color=blue&up_message=%E7%82%B9%E8%BF%99%E9%87%8C&url=https%3A%2F%2Flabuladong.gitbook
.io%2Falgo&logo=Gitea"
></a>
<a
href=
"https://labuladong.git
ee.io/algo"
target=
"_blank"
><img
alt=
"Website"
src=
"https://img.shields.io/website?label=%E5%9C%A8%E7%BA%BF%E7%94%B5%E5%AD%90%E4%B9%A6&style=flat-square&down_color=blue&down_message=%E7%82%B9%E8%BF%99%E9%87%8C&up_color=blue&up_message=%E7%82%B9%E8%BF%99%E9%87%8C&url=https%3A%2F%2Flabuladong.gitee
.io%2Falgo&logo=Gitea"
></a>
<a
href=
"https://github.com/labuladong/fucking-algorithm"
target=
"_blank"
><img
alt=
"GitHub"
src=
"https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"
></a>
</p>
...
...
@@ -14,6 +14,10 @@ English version repo and Gitbook is on [english branch](https://github.com/labul
<a href="https://space.bilibili.com/14089380" target="_blank"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>
![](
pictures/souyisou.png
)
好消息,《labuladong 的算法小抄》纸质书出版啦!关注公众号查看详情👆
<p
align=
'center'
>
<img
src=
"https://gitee.com/labuladong/pictures/raw/master/starHistory.png"
width =
"600"
/>
</p>
...
...
@@ -25,27 +29,36 @@ English version repo and Gitbook is on [english branch](https://github.com/labul
只想要答案的话很容易,题目评论区五花八门的答案,动不动就秀 python 一行代码解决,有那么多人点赞。问题是,你去做算法题,是去学习编程语言的奇技淫巧的,还是学习算法思维的呢?你的快乐,到底源自复制别人的一行代码通过测试,已完成题目 +1,还是源自自己通过逻辑推理和算法框架不看答案写出解法?
网上总有大佬喷我,说我写这玩意太基础了,根本没必要啰嗦。我只能说大家刷算法就是找工作吃饭的,不是打竞赛的,我也是一路摸爬滚打过来的,我们要的是清楚明白有所得,不是故弄玄虚无所指。不想办法做到通俗易懂,难道要上来先把《算法导论》吹上天,然后把人家都心怀敬仰地劝退?
网上总有大佬喷我,说我写的东西太基础,要么说不能借助框架思维来学习算法。我只能说大家刷算法就是找工作吃饭的,不是打竞赛的,我也是一路摸爬滚打过来的,我们要的是清楚明白有所得,不是故弄玄虚无所指。
不想办法做到通俗易懂,难道要上来先把《算法导论》吹上天,然后把人家都心怀敬仰地劝退?
**做啥事情做多了,都能发现套路的,我把各种算法套路框架总结出来,相信可以帮助其他人少走弯路**
。我这个纯靠自学的小童鞋,花了一年时间刷题和总结,自己写了一份算法小抄,后面有目录,这里就不废话了。
### 使用方法
1、
**先给本仓库点个 star,满足一下我的虚荣心**
,文章质量绝对值你一个 star。我还在继续创作,给我一点继续写文的动力,感谢。
**1、先给本仓库点个 star,满足一下我的虚荣心**
,文章质量绝对值你一个 star。我还在继续创作,给我一点继续写文的动力,感谢。
**2、建议收藏我的在线网站,每篇文章开头都有对应的力扣题目链接,可以边看文章边刷题**
:
Gitbook 地址:https://labuladong.gitbook.io/algo
GitBook 在国内访问速度很慢,且常被攻击,我特意部署了两个镜像站点,大家可根据网络情况自行选择:
GitHub Pages 地址:https://labuladong.github.io/algo
2、
**建议收藏我的 Gitbook 网站,每篇文章开头都有对应的力扣题目链接,可以边看文章边刷题**
:
Gitee Pages 地址:https://labuladong.gitee.io/algo
Gitbook 地址:https://labuladong.gitbook.io/algo/
3、建议关注我的公众号
**labuladong**
,坚持高质量原创,说是最良心最硬核的技术公众号都不为过。本仓库的文章就是从公众号里整理出来的
**一部分**
内容,公众号后台回复关键词【电子书】可以获得这份小抄的完整版本;回复
【加群】可以加入我们的刷题群,和大家一起讨论算法问题,分享内推机会:
**3、建议关注我的公众号 labuladong,坚持高质量原创,说是最良心最硬核的技术公众号都不为过**
。本仓库的文章就是从公众号里整理出来的
**一部分**
内容,公众号可以查看更多内容;公众号后台回复关键词
【加群】可以加入我们的刷题群,和大家一起讨论算法问题,分享内推机会:
<p
align=
'center'
>
<img
src=
"https://gitee.com/labuladong/pictures/raw/master/qrcode.jpg"
width =
"200"
/>
</p>
4、欢迎关注
[
我的知乎
](
https://www.zhihu.com/people/labuladong
)
。
**4、欢迎关注 [我的知乎](https://www.zhihu.com/people/labuladong)**
。
我一直在写优质文章,但是后续的文章只发布到公众号/
gitbook
/知乎,不能开放到 GitHub。因为本仓库太火了,很多人直接拿我的文章去开付费专栏,价格还不便宜,我这免费写给您看,何必掏冤枉钱呢?所以多多关注本作者,多多宣传,谁也不希望劣币驱逐良币不是么?
我一直在写优质文章,但是后续的文章只发布到公众号/
网站
/知乎,不能开放到 GitHub。因为本仓库太火了,很多人直接拿我的文章去开付费专栏,价格还不便宜,我这免费写给您看,何必掏冤枉钱呢?所以多多关注本作者,多多宣传,谁也不希望劣币驱逐良币不是么?
其他的先不多说了,直接上干货吧,我们一起搞定 LeetCode,感受一下支配算法的乐趣。
...
...
出版推广1.jpeg
0 → 100644
浏览文件 @
666dced7
120.0 KB
动态规划系列/动态规划之KMP字符匹配算法.md
浏览文件 @
666dced7
...
...
@@ -431,4 +431,40 @@ KMP 算法也就是动态规划那点事,我们的公众号文章目录有一
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
MoguCloud
](
https://github.com/MoguCloud
)
提供 实现 strStr() 的 Python 完整代码:
```
py
class
Solution
:
def
strStr
(
self
,
haystack
:
str
,
needle
:
str
)
->
int
:
# 边界条件判断
if
not
needle
:
return
0
pat
=
needle
txt
=
haystack
M
=
len
(
pat
)
# dp[状态][字符] = 下个状态
dp
=
[[
0
for
_
in
range
(
256
)]
for
_
in
pat
]
# base case
dp
[
0
][
ord
(
pat
[
0
])]
=
1
# 影子状态 X 初始化为 0
X
=
0
for
j
in
range
(
1
,
M
):
for
c
in
range
(
256
):
dp
[
j
][
c
]
=
dp
[
X
][
c
]
dp
[
j
][
ord
(
pat
[
j
])]
=
j
+
1
# 更新影子状态
X
=
dp
[
X
][
ord
(
pat
[
j
])]
N
=
len
(
txt
)
# pat 初始状态为 0
j
=
0
for
i
in
range
(
N
):
# 计算 pat 的下一个状态
j
=
dp
[
j
][
ord
(
txt
[
i
])]
# 到达终止态,返回结果
if
j
==
M
:
return
i
-
M
+
1
# 没到达终止态,匹配失败
return
-
1
```
动态规划系列/动态规划之博弈问题.md
浏览文件 @
666dced7
...
...
@@ -22,7 +22,7 @@
上一篇文章
[
几道智力题
](
https://labuladong.gitbook.io/algo
)
中讨论到一个有趣的「石头游戏」,通过题目的限制条件,这个游戏是先手必胜的。但是智力题终究是智力题,真正的算法问题肯定不会是投机取巧能搞定的。所以,本文就借石头游戏来讲讲「假设两个人都足够聪明,最后谁会获胜」这一类问题该如何用动态规划算法解决。
博弈类问题的套路都差不多,下文
举例
讲解,其核心思路是在二维 dp 的基础上使用元组分别存储两个人的博弈结果。掌握了这个技巧以后,别人再问你什么俩海盗分宝石,俩人拿硬币的问题,你就告诉别人:我懒得想,直接给你写个算法算一下得了。
博弈类问题的套路都差不多,下文
参考
[
这个 YouTube 视频
](
https://www.youtube.com/watch?v=WxpIHvsu1RI
)
的思路
讲解,其核心思路是在二维 dp 的基础上使用元组分别存储两个人的博弈结果。掌握了这个技巧以后,别人再问你什么俩海盗分宝石,俩人拿硬币的问题,你就告诉别人:我懒得想,直接给你写个算法算一下得了。
我们「石头游戏」改的更具有一般性:
...
...
@@ -215,4 +215,116 @@ int stoneGame(int[] piles) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
*
python3版本
由
[
SCUHZS
](
https://github.com/brucecat
)
提供
这里采取的是三维的做法
```
python
class
Solution
:
def
stoneGame
(
self
,
piles
:
List
[
int
])
->
bool
:
n
=
len
(
piles
)
# 初始化一个n*n的矩阵 dp数组
dp
=
[[
None
]
*
n
for
i
in
range
(
0
,
n
)]
# 在三角区域填充
for
i
in
range
(
n
):
for
j
in
range
(
i
,
n
):
dp
[
i
][
j
]
=
[
0
,
0
]
# 填入base case
for
i
in
range
(
0
,
n
):
dp
[
i
][
i
][
0
]
=
piles
[
i
]
dp
[
i
][
i
][
1
]
=
0
# 斜着遍历数组
for
l
in
range
(
2
,
n
+
1
):
for
i
in
range
(
0
,
n
-
l
+
1
):
j
=
l
+
i
-
1
# 先手选择最左边或最右边的分数
left
=
piles
[
i
]
+
dp
[
i
+
1
][
j
][
1
]
right
=
piles
[
j
]
+
dp
[
i
][
j
-
1
][
1
]
# 套用状态转移方程
if
left
>
right
:
dp
[
i
][
j
][
0
]
=
left
dp
[
i
][
j
][
1
]
=
dp
[
i
+
1
][
j
][
0
]
else
:
dp
[
i
][
j
][
0
]
=
right
dp
[
i
][
j
][
1
]
=
dp
[
i
][
j
-
1
][
0
]
res
=
dp
[
0
][
n
-
1
]
return
res
[
0
]
-
res
[
1
]
>
0
```
压缩成一维数组,以减小空间复杂度,做法如下。
```
python
class
Solution
:
def
stoneGame
(
self
,
piles
:
List
[
int
])
->
bool
:
dp
=
piles
.
copy
()
for
i
in
range
(
len
(
piles
)
-
1
,
-
1
,
-
1
):
# 从下往上遍历
for
j
in
range
(
i
,
len
(
piles
)):
# 从前往后遍历
dp
[
j
]
=
max
(
piles
[
i
]
-
dp
[
j
],
piles
[
j
]
-
dp
[
j
-
1
])
# 计算之后覆盖一维数组的对应位置
return
dp
[
len
(
piles
)
-
1
]
>
0
```
*
C++ 版本
由
[
TCeason
](
https://github.com/TCeason
)
提供
这里采用 hash map 来解决问题
```
cpp
class
Solution
{
public:
unordered_map
<
int
,
int
>
memo
;
int
dfs
(
vector
<
int
>
&
piles
,
int
index
)
{
// 从两边向中间获取
// index 值为 1/2 piles.size() 时可以停止算法
if
(
index
==
piles
.
size
()
/
2
)
return
0
;
// 减少计算,快速返回已有结果
if
(
memo
.
count
(
index
))
return
memo
[
index
];
// 防止第一次取最右时越界
int
n
=
piles
.
size
()
-
1
;
// 先手选择最左边或最右边后的分数
int
l
=
piles
[
index
]
+
dfs
(
piles
,
index
+
1
);
int
r
=
piles
[
n
-
index
]
+
dfs
(
piles
,
index
+
1
);
// 返回先手左或右边的最高分
return
memo
[
index
]
=
max
(
l
,
r
);
}
bool
stoneGame
(
vector
<
int
>&
piles
)
{
// 最佳发挥时:
// 先手得分 * 2 > 总大小 则先手者胜利
return
dfs
(
piles
,
0
)
*
2
>
accumulate
(
begin
(
piles
),
end
(
piles
),
0
);
}
};
```
动态规划系列/动态规划设计:最长递增子序列.md
浏览文件 @
666dced7
...
...
@@ -10,8 +10,8 @@
![](
../pictures/souyisou.png
)
相关推荐:
*
[
动态规划设计:最大子数组
](
../动态规划系列/最大子数组.md
)
*
[
一文学会递归解题
](
../投稿/一文学会递归解题.md
)
*
[
动态规划设计:最大子数组
](
https://labuladong.gitbook.io/algo
)
*
[
一文学会递归解题
](
https://labuladong.gitbook.io/algo
)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
...
...
@@ -19,7 +19,7 @@
**-----------**
也许有读者看了前文
[
动态规划详解
](
../动态规划系列/动态规划详解进阶.md
)
,学会了动态规划的套路:找到了问题的「状态」,明确了
`dp`
数组/函数的含义,定义了 base case;但是不知道如何确定「选择」,也就是不到状态转移的关系,依然写不出动态规划解法,怎么办?
也许有读者看了前文
[
动态规划详解
](
https://labuladong.gitbook.io/algo
)
,学会了动态规划的套路:找到了问题的「状态」,明确了
`dp`
数组/函数的含义,定义了 base case;但是不知道如何确定「选择」,也就是不到状态转移的关系,依然写不出动态规划解法,怎么办?
不要担心,动态规划的难点本来就在于寻找正确的状态转移方程,本文就借助经典的「最长递增子序列问题」来讲一讲设计动态规划的通用技巧:
**数学归纳思想**
。
...
...
@@ -43,7 +43,7 @@
**我们的定义是这样的:`dp[i]` 表示以 `nums[i]` 这个数结尾的最长递增子序列的长度。**
PS:为什么这样定义呢?这是解决子序列问题的一个套路,后文
[
动态规划之子序列问题解题模板
](
../动态规划系列/子序列问题模板.md
)
总结了几种常见套路。你读完本章所有的动态规划问题,就会发现
`dp`
数组的定义方法也就那几种。
PS:为什么这样定义呢?这是解决子序列问题的一个套路,后文
[
动态规划之子序列问题解题模板
](
https://labuladong.gitbook.io/algo
)
总结了几种常见套路。你读完本章所有的动态规划问题,就会发现
`dp`
数组的定义方法也就那几种。
根据这个定义,我们就可以推出 base case:
`dp[i]`
初始值为 1,因为以
`nums[i]`
结尾的最长递增子序列起码要包含它自己。
...
...
@@ -164,7 +164,7 @@ public int lengthOfLIS(int[] nums) {
我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是
**有序**
吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。
PS:旧文
[
二分查找算法详解
](
../算法思维系列/二分查找详解.md
)
详细介绍了二分查找的细节及变体,这里就完美应用上了,如果没读过强烈建议阅读。
PS:旧文
[
二分查找算法详解
](
https://labuladong.gitbook.io/algo
)
详细介绍了二分查找的细节及变体,这里就完美应用上了,如果没读过强烈建议阅读。
```
java
public
int
lengthOfLIS
(
int
[]
nums
)
{
...
...
@@ -215,4 +215,91 @@ public int lengthOfLIS(int[] nums) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
```
python 动态规划
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
f = [1] * (n)
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
f[i] = max(f[i], f[j] + 1)
res = 0
for i in range(n):
res = max(res, f[i])
return res
```
```
python 二分查找
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
stack = []
def find_index(num):
l, r = 0, len(stack)
while l < r:
mid = l + r >> 1
if stack[mid] >= num:
r = mid
else:
l = mid + 1
return r
for num in nums:
if not stack or num > stack[-1]:
stack.append(num)
else:
position = find_index(num)
stack[position] = num
return len(stack)
```
[
Kian
](
https://github.com/KianKw/
)
提供 C++ 代码
```
c++
class
Solution
{
public:
int
lengthOfLIS
(
vector
<
int
>&
nums
)
{
/* len 为牌的数量 */
int
len
=
nums
.
size
();
vector
<
int
>
top
(
len
,
0
);
/* 牌堆数初始化为0 */
int
piles
=
0
;
for
(
int
i
=
0
;
i
<
len
;
i
++
)
{
/* nums[i] 为要处理的扑克牌 */
int
poker
=
nums
[
i
];
/***** 搜索左侧边界的二分查找 *****/
int
left
=
0
,
right
=
piles
;
while
(
left
<
right
)
{
int
mid
=
left
+
(
right
-
left
)
/
2
;
if
(
top
[
mid
]
>
poker
)
{
right
=
mid
;
}
else
if
(
top
[
mid
]
<
poker
)
{
left
=
mid
+
1
;
}
else
if
(
top
[
mid
]
==
poker
)
{
right
=
mid
;
}
}
/*********************************/
/* 没找到合适的牌堆,新建一堆 */
if
(
left
==
piles
)
piles
++
;
/* 把这张牌放到牌堆顶 */
top
[
left
]
=
poker
;
}
/* 牌堆数就是 LIS 长度 */
return
piles
;
}
};
```
动态规划系列/动态规划详解进阶.md
浏览文件 @
666dced7
...
...
@@ -146,6 +146,8 @@ int helper(vector<int>& memo, int n) {
```
cpp
int
fib
(
int
N
)
{
if
(
N
==
0
)
return
0
;
if
(
N
==
1
)
return
1
;
vector
<
int
>
dp
(
N
+
1
,
0
);
// base case
dp
[
1
]
=
dp
[
2
]
=
1
;
...
...
@@ -200,7 +202,7 @@ int coinChange(int[] coins, int amount);
比如说
`k = 3`
,面值分别为 1,2,5,总金额
`amount = 11`
。那么最少需要 3 枚硬币凑出,即 11 = 5 + 5 + 1。
你认为计算机应该如何解决这个问题?显然,就是把所有
肯
能的凑硬币方法都穷举出来,然后找找看最少需要多少枚硬币。
你认为计算机应该如何解决这个问题?显然,就是把所有
可
能的凑硬币方法都穷举出来,然后找找看最少需要多少枚硬币。
**1、暴力递归**
...
...
@@ -366,4 +368,45 @@ PS:为啥 `dp` 数组初始化为 `amount + 1` 呢,因为凑成 `amount` 金
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
DapangLiu
](
https://github.com/DapangLiu
)
提供 509. 斐波那契数 Python3 解法代码:
递归写法
```
python
class
Solution
:
def
fib
(
self
,
N
:
int
)
->
int
:
if
N
<=
1
:
return
N
return
self
.
fib
(
N
-
1
)
+
self
.
fib
(
N
-
2
)
```
动态规划写法
```
python
class
Solution
:
def
fib
(
self
,
N
:
int
)
->
int
:
if
N
==
0
:
return
0
# init
result
=
[
0
for
i
in
range
(
N
+
1
)]
result
[
1
]
=
1
# status transition
for
j
in
range
(
2
,
N
+
1
):
result
[
j
]
=
result
[
j
-
1
]
+
result
[
j
-
2
]
return
result
[
-
1
]
```
动态规划写法 (状态压缩)
```
python
class
Solution
:
def
fib
(
self
,
n
:
int
)
->
int
:
# current status only depends on two previous status
dp_0
,
dp_1
=
0
,
1
for
_
in
range
(
n
):
dp_0
,
dp_1
=
dp_1
,
dp_0
+
dp_1
return
dp_0
```
\ No newline at end of file
动态规划系列/团灭股票问题.md
浏览文件 @
666dced7
...
...
@@ -16,7 +16,7 @@
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
[
买卖股票的最佳时机
](
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
/solution/
)
[
买卖股票的最佳时机
](
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
)
[
买卖股票的最佳时机 II
](
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
)
...
...
@@ -32,7 +32,7 @@
很多读者抱怨 LeetCode 的股票系列问题奇技淫巧太多,如果面试真的遇到这类问题,基本不会想到那些巧妙的办法,怎么办?
**所以本文拒绝奇技淫巧,而是稳扎稳打,只用一种通用方法解决所用问题,以不变应万变**
。
这篇文章用状态机的技巧来解决,可以全部提交通过。不要觉得这个名词高大上,文学词汇而已,实际上就是 DP table,看一眼就明白了。
这篇文章
参考
[
英文版高赞题解
](
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems
)
的思路,
用状态机的技巧来解决,可以全部提交通过。不要觉得这个名词高大上,文学词汇而已,实际上就是 DP table,看一眼就明白了。
先随便抽出一道题,看看别人的解法:
...
...
动态规划系列/抢房子.md
浏览文件 @
666dced7
...
...
@@ -258,4 +258,66 @@ int[] dp(TreeNode root) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
Shantom
](
https://github.com/Shantom
)
提供 198. House Robber I Python3 解法代码:
```
Python
class Solution:
def rob(self, nums: List[int]) -> int:
# 当前,上一间,上上间
cur, pre1, pre2 = 0, 0, 0
for num in nums:
# 当前 = max(上上间+(抢当前),上间(放弃当前))
cur = max(pre2 + num, pre1)
pre2 = pre1
pre1 = cur
return cur
```
[
Shantom
](
https://github.com/Shantom
)
提供 213. House Robber II Python3 解法代码:
```
Python
class Solution:
def rob(self, nums: List[int]) -> int:
# 只有一间时不成环
if len(nums) == 1:
return nums[0]
# 该函数同198题
def subRob(nums: List[int]) -> int:
# 当前,上一间,上上间
cur, pre1, pre2 = 0, 0, 0
for num in nums:
# 当前 = max(上上间+(抢当前),上间(放弃当前))
cur = max(pre2 + num, pre1)
pre2 = pre1
pre1 = cur
return cur
# 不考虑第一间或者不考虑最后一间
return max(subRob(nums[:-1]), subRob(nums[1:]))
```
[
Shantom
](
https://github.com/Shantom
)
提供 337. House Robber III Python3 解法代码:
```
Python
class Solution:
def rob(self, root: TreeNode) -> int:
# 返回值0项为不抢该节点,1项为抢该节点
def dp(root):
if not root:
return 0, 0
left = dp(root.left)
right = dp(root.right)
# 抢当前,则两个下家不抢
do = root.val + left[0] + right[0]
# 不抢当前,则下家随意
do_not = max(left) + max(right)
return do_not, do
return max(dp(root))
```
动态规划系列/最优子结构.md
浏览文件 @
666dced7
...
...
@@ -54,7 +54,7 @@ return result;
当然,上面这个例子太简单了,不过请读者回顾一下,我们做动态规划问题,是不是一直在求各种最值,本质跟我们举的例子没啥区别,无非需要处理一下重叠子问题。
前文不
[
同定义不同解法
](
../动态规划系列/动态规划之四键键盘.md
)
和
[
高楼扔鸡蛋进阶
](
../动态规划系列/高楼扔鸡蛋问题.md
)
就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
前文不
[
同定义不同解法
](
https://labuladong.gitbook.io/algo
)
和
[
高楼扔鸡蛋进阶
](
https://labuladong.gitbook.io/algo
)
就展示了如何改造问题,不同的最优子结构,可能导致不同的解法和效率。
再举个常见但也十分简单的例子,求一棵二叉树的最大值,不难吧(简单起见,假设节点中的值都是非负数):
...
...
动态规划系列/最长公共子序列.md
浏览文件 @
666dced7
# 最长公共子序列
# 最长公共子序列
<p
align=
'center'
>
...
...
@@ -147,4 +147,64 @@ else:
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
Edwenc
](
https://github.com/Edwenc
)
提供 C++ 代码:
```
C++
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
// 先计算两条字符串的长度
int m = text1.size();
int n = text2.size();
// 构建dp矩阵 默认初始值0
// 这里会多扩建一边和一列
// 因为dp[i][j]的含义是:对于 s1[1..i] 和 s2[1..j],它们的LCS长度是 dp[i][j]。
// 所以当i或者j为零时 LCS的长度默认为0
vector< vector<int> > dp ( m+1 , vector<int> ( n+1 , 0 ) );
// 状态转移
// i、j都从1开始遍历 因为下面的操作中都会-1 相当于从0开始
for ( int i=1 ; i<m+1 ; i++ ){
for ( int j=1 ; j<n+1 ; j++ ){
// 如果text1和text2相同
// 就在它们的前一位基础上加一
// 如果不同 只能在之前的两者中去最大
dp[i][j] = (text1[i-1] == text2[j-1]) ? dp[i-1][j-1] + 1 : max( dp[i-1][j] , dp[i][j-1] );
}
}
// 返回最终右下角的值
return dp[m][n];
}
};
```
[
Shawn
](
https://github.com/Shawn-Hx
)
提供 Java 代码:
```
java
public
int
longestCommonSubsequence
(
String
text1
,
String
text2
)
{
// 字符串转为char数组以加快访问速度
char
[]
str1
=
text1
.
toCharArray
();
char
[]
str2
=
text2
.
toCharArray
();
int
m
=
str1
.
length
,
n
=
str2
.
length
;
// 构建dp table,初始值默认为0
int
[][]
dp
=
new
int
[
m
+
1
][
n
+
1
];
// 状态转移
for
(
int
i
=
1
;
i
<=
m
;
i
++)
for
(
int
j
=
1
;
j
<=
n
;
j
++)
if
(
str1
[
i
-
1
]
==
str2
[
j
-
1
])
// 找到LCS中的字符
dp
[
i
][
j
]
=
dp
[
i
-
1
][
j
-
1
]
+
1
;
else
dp
[
i
][
j
]
=
Math
.
max
(
dp
[
i
-
1
][
j
],
dp
[
i
][
j
-
1
]);
return
dp
[
m
][
n
];
}
```
动态规划系列/高楼扔鸡蛋问题.md
浏览文件 @
666dced7
...
...
@@ -243,7 +243,7 @@ def superEggDrop(self, K: int, N: int) -> int:
return
dp
(
K
,
N
)
```
这里就不展开其他解法了,留在下一篇文章
[
高楼扔鸡蛋进阶
](
../动态规划系列/高楼扔鸡蛋进阶.md
)
这里就不展开其他解法了,留在下一篇文章
[
高楼扔鸡蛋进阶
](
https://labuladong.gitbook.io/algo
)
我觉得吧,我们这种解法就够了:找状态,做选择,足够清晰易懂,可流程化,可举一反三。掌握这套框架学有余力的话,再去考虑那些奇技淫巧也不迟。
...
...
数据结构系列/二叉搜索树操作集锦.md
浏览文件 @
666dced7
...
...
@@ -310,7 +310,70 @@ void BST(TreeNode root, int target) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
======其他语言代码======
### c++
[
dekunma
](
https://www.linkedin.com/in/dekun-ma-036a9b198/
)
提供第98题C++代码:
```
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isValidBST(TreeNode* root) {
// 用helper method求解
return isValidBST(root, nullptr, nullptr);
}
bool isValidBST(TreeNode* root, TreeNode* min, TreeNode* max) {
// base case, root为nullptr
if (!root) return true;
// 不符合BST的条件
if (min && root->val <= min->val) return false;
if (max && root->val >= max->val) return false;
// 向左右子树分别递归求解
return isValidBST(root->left, min, root)
&& isValidBST(root->right, root, max);
}
};
```
### python
[
ChenjieXu
](
https://github.com/ChenjieXu
)
提供第98题Python3代码:
```
python
def
isValidBST
(
self
,
root
):
# 递归函数
def
helper
(
node
,
lower
=
float
(
'-inf'
),
upper
=
float
(
'inf'
)):
if
not
node
:
return
True
val
=
node
.
val
if
val
<=
lower
or
val
>=
upper
:
return
False
# 右节点
if
not
helper
(
node
.
right
,
val
,
upper
):
return
False
# 左节点
if
not
helper
(
node
.
left
,
lower
,
val
):
return
False
return
True
return
helper
(
root
)
```
[
lixiandea
](
https://github.com/lixiandea
)
提供第100题Python3代码:
```
python3
...
...
@@ -335,4 +398,5 @@ class Solution:
return False
# 当前节点相同且左子树和右子树分别相同
return p.val==q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
```
\ No newline at end of file
```
数据结构系列/单调栈.md
浏览文件 @
666dced7
...
...
@@ -11,7 +11,7 @@
![](
../pictures/souyisou.png
)
相关推荐:
*
[
回溯算法解题套路框架
](
https://labuladong.gitbook.io/algo
)
*
[
回溯算法解题套路框架
](
https://labuladong.gitbook.io/algo
)
*
[
动态规划解题套路框架
](
https://labuladong.gitbook.io/algo
)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
...
...
@@ -20,7 +20,7 @@
[
503.下一个更大元素II
](
https://leetcode-cn.com/problems/next-greater-element-ii
)
[
1118.一月有多少天
](
https://leetcode-cn.com/problems/number-of-days-in-a-month
)
[
739.每日温度
](
https://leetcode-cn.com/problems/daily-temperatures/
)
**-----------**
...
...
@@ -82,7 +82,7 @@ vector<int> nextGreaterElement(vector<int>& nums) {
### 问题变形
单调栈的使用技巧差不多了,来一个简单的变形,力扣第
1118 题「一月有多少天
」:
单调栈的使用技巧差不多了,来一个简单的变形,力扣第
739 题「每日温度
」:
给你一个数组
`T`
,这个数组存放的是近几天的天气气温,你返回一个等长的数组,计算:
**对于每一天,你还要至少等多少天才能等到一个更暖和的气温;如果等不到那一天,填 0**
。
...
...
@@ -181,4 +181,26 @@ vector<int> nextGreaterElements(vector<int>& nums) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
### java
```
java
// 739. Daily Temperatures
class
Solution
{
public
int
[]
dailyTemperatures
(
int
[]
T
)
{
Stack
<
Integer
>
stack
=
new
Stack
<>();
int
[]
ans
=
new
int
[
T
.
length
];
for
(
int
i
=
0
;
i
<
T
.
length
;
i
++)
{
// 如果压栈之后不满足单调递减,弹出元素,直至保持单调性
while
(!
stack
.
isEmpty
()
&&
T
[
i
]
>
T
[
stack
.
peek
()])
{
int
index
=
stack
.
pop
();
// 被弹出的元素(T[index])都是小于当前的元素(T[i]),由于栈内元素单调递减,大于被弹出元素(index)的最近的就是当前元素(i)
ans
[
index
]
=
i
-
index
;
}
stack
.
push
(
i
);
}
return
ans
;
}
}
```
数据结构系列/单调队列.md
浏览文件 @
666dced7
...
...
@@ -210,4 +210,51 @@ vector<int> maxSlidingWindow(vector<int>& nums, int k) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
```
java
class
Solution
{
public
int
[]
maxSlidingWindow
(
int
[]
nums
,
int
k
)
{
int
len
=
nums
.
length
;
// 判断数组或者窗口长度为0的情况
if
(
len
*
k
==
0
)
{
return
new
int
[
0
];
}
/*
采用两端扫描的方法
将数组分成大小为 k 的若干个窗口, 对每个窗口分别从左往右和从右往左扫描, 记录扫描的最大值
left[] 记录从左往右扫描的最大值
right[] 记录从右往左扫描的最大值
*/
int
[]
left
=
new
int
[
len
];
int
[]
right
=
new
int
[
len
];
for
(
int
i
=
0
;
i
<
len
;
i
=
i
+
k
)
{
// 每个窗口中的第一个值
left
[
i
]
=
nums
[
i
];
// 窗口的最后边界
int
index
=
i
+
k
-
1
>=
len
?
len
-
1
:
i
+
k
-
1
;
// 每个窗口的最后一个值
right
[
index
]
=
nums
[
index
];
// 对该窗口从左往右扫描
for
(
int
j
=
i
+
1
;
j
<=
index
;
j
++)
{
left
[
j
]
=
Math
.
max
(
left
[
j
-
1
],
nums
[
j
]);
}
// 对该窗口从右往左扫描
for
(
int
j
=
index
-
1
;
j
>=
i
;
j
--)
{
right
[
j
]
=
Math
.
max
(
right
[
j
+
1
],
nums
[
j
]);
}
}
int
[]
arr
=
new
int
[
len
-
k
+
1
];
// 对于第 i 个位置, 它一定是该窗口从右往左扫描数组中的最后一个值, 相对的 i + k - 1 是该窗口从左向右扫描数组中的最后一个位置
// 对两者取最大值即可
for
(
int
i
=
0
;
i
<
len
-
k
+
1
;
i
++)
{
arr
[
i
]
=
Math
.
max
(
right
[
i
],
left
[
i
+
k
-
1
]);
}
return
arr
;
}
}
```
数据结构系列/设计Twitter.md
浏览文件 @
666dced7
...
...
@@ -302,4 +302,121 @@ PS:本文前两张图片和 GIF 是我第一次尝试用平板的绘图软件
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
happy-yuxuan
](
https://github.com/happy-yuxuan
)
提供 C++ 代码:
```
c++
static
int
timestamp
=
0
;
class
Tweet
{
private:
int
id
;
int
time
;
public:
Tweet
*
next
;
// id为推文内容,time为发文时间
Tweet
(
int
id
,
int
time
)
{
this
->
id
=
id
;
this
->
time
=
time
;
next
=
nullptr
;
}
int
getId
()
const
{
return
this
->
id
;
}
int
getTime
()
const
{
return
this
->
time
;
}
};
class
User
{
private:
int
id
;
public:
Tweet
*
head
;
// 发布的Twitter,用链表表示
unordered_set
<
int
>
followed
;
// 用户关注了那些人
User
(
int
userId
)
{
this
->
id
=
userId
;
head
=
nullptr
;
// 要先把自己关注了
followed
.
insert
(
id
);
}
void
follow
(
int
userId
)
{
followed
.
insert
(
userId
);
}
void
unfollow
(
int
userId
)
{
// 不可以取关自己
if
(
userId
!=
this
->
id
)
followed
.
erase
(
userId
);
}
void
post
(
int
contentId
)
{
Tweet
*
twt
=
new
Tweet
(
contentId
,
timestamp
);
timestamp
++
;
// 将新建的推文插入链表头
// 越靠前的推文 timestamp 值越大
twt
->
next
=
head
;
head
=
twt
;
}
};
class
Twitter
{
private:
// 映射将 userId 和 User 对象对应起来
unordered_map
<
int
,
User
*>
userMap
;
// 判断该用户存不存在系统中,即userMap中存不存在id
inline
bool
contain
(
int
id
)
{
return
userMap
.
find
(
id
)
!=
userMap
.
end
();
}
public:
Twitter
()
{
userMap
.
clear
();
}
/* user 发表一条 tweet 动态 */
void
postTweet
(
int
userId
,
int
tweetId
)
{
if
(
!
contain
(
userId
))
userMap
[
userId
]
=
new
User
(
userId
);
userMap
[
userId
]
->
post
(
tweetId
);
}
/* 返回该 user 关注的人(包括他自己)最近的动态 id,
最多 10 条,而且这些动态必须按从新到旧的时间线顺序排列。*/
vector
<
int
>
getNewsFeed
(
int
userId
)
{
vector
<
int
>
ret
;
if
(
!
contain
(
userId
))
return
ret
;
// 构造一个自动通过Tweet发布的time属性从大到小排序的二叉堆
typedef
function
<
bool
(
const
Tweet
*
,
const
Tweet
*
)
>
Compare
;
Compare
cmp
=
[](
const
Tweet
*
a
,
const
Tweet
*
b
)
{
return
a
->
getTime
()
<
b
->
getTime
();
};
priority_queue
<
Tweet
*
,
vector
<
Tweet
*>
,
Compare
>
q
(
cmp
);
// 关注列表的用户Id
unordered_set
<
int
>
&
users
=
userMap
[
userId
]
->
followed
;
// 先将所有链表头节点插入优先级队列
for
(
int
id
:
users
)
{
if
(
!
contain
(
id
))
continue
;
Tweet
*
twt
=
userMap
[
id
]
->
head
;
if
(
twt
==
nullptr
)
continue
;
q
.
push
(
twt
);
}
while
(
!
q
.
empty
())
{
Tweet
*
t
=
q
.
top
();
q
.
pop
();
ret
.
push_back
(
t
->
getId
());
if
(
ret
.
size
()
==
10
)
return
ret
;
// 最多返回 10 条就够了
if
(
t
->
next
)
q
.
push
(
t
->
next
);
}
return
ret
;
}
/* follower 关注 followee */
void
follow
(
int
followerId
,
int
followeeId
)
{
// 若 follower 不存在,则新建
if
(
!
contain
(
followerId
))
userMap
[
followerId
]
=
new
User
(
followerId
);
// 若 followee 不存在,则新建
if
(
!
contain
(
followeeId
))
userMap
[
followeeId
]
=
new
User
(
followeeId
);
userMap
[
followerId
]
->
follow
(
followeeId
);
}
/* follower 取关 followee,如果 Id 不存在则什么都不做 */
void
unfollow
(
int
followerId
,
int
followeeId
)
{
if
(
contain
(
followerId
))
userMap
[
followerId
]
->
unfollow
(
followeeId
);
}
};
```
\ No newline at end of file
算法思维系列/二分查找详解.md
浏览文件 @
666dced7
...
...
@@ -65,7 +65,7 @@ int binarySearch(int[] nums, int target) {
### 一、寻找一个数(基本的二分搜索)
这个场景是最简单的,
肯
能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。
这个场景是最简单的,
可
能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。
```
java
int
binarySearch
(
int
[]
nums
,
int
target
)
{
...
...
@@ -104,7 +104,7 @@ int binarySearch(int[] nums, int target) {
`while(left <= right)`
的终止条件是
`left == right + 1`
,写成区间的形式就是
`[right + 1, right]`
,或者带个具体的数字进去
`[3, 2]`
,可见
**这时候区间为空**
,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
`while(left < right)`
的终止条件是
`left == right`
,写成区间的形式就是
`[
lef
t, right]`
,或者带个具体的数字进去
`[2, 2]`
,
**这时候区间非空**
,还有一个数 2,但此时 while 循环终止了。也就是说这区间
`[2, 2]`
被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
`while(left < right)`
的终止条件是
`left == right`
,写成区间的形式就是
`[
righ
t, right]`
,或者带个具体的数字进去
`[2, 2]`
,
**这时候区间非空**
,还有一个数 2,但此时 while 循环终止了。也就是说这区间
`[2, 2]`
被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
当然,如果你非要用
`while(left < right)`
也可以,我们已经知道了出错的原因,就打个补丁好了:
...
...
算法思维系列/双指针技巧.md
浏览文件 @
666dced7
...
...
@@ -20,7 +20,7 @@
[
141.环形链表II
](
https://leetcode-cn.com/problems/linked-list-cycle-ii
)
[
167.两数之和 II - 输入有序数组
](
https://leetcode-cn.com/problems/two-sum
)
[
167.两数之和 II - 输入有序数组
](
https://leetcode-cn.com/problems/two-sum
-ii-input-array-is-sorted
)
**-----------**
...
...
@@ -80,6 +80,11 @@ ListNode detectCycle(ListNode head) {
if
(
fast
==
slow
)
break
;
}
// 上面的代码类似 hasCycle 函数
if
(
fast
==
null
||
fast
.
next
==
null
)
{
// fast 遇到空指针说明没有环
return
null
;
}
slow
=
head
;
while
(
slow
!=
fast
)
{
fast
=
fast
.
next
;
...
...
@@ -230,4 +235,25 @@ void reverse(int[] nums) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
ryandeng32
](
https://github.com/ryandeng32/
)
提供 Python 代码
```
python
class
Solution
:
def
hasCycle
(
self
,
head
:
ListNode
)
->
bool
:
# 检查链表头是否为None,是的话则不可能为环形
if
head
is
None
:
return
False
# 快慢指针初始化
slow
=
fast
=
head
# 若链表非环形则快指针终究会遇到None,然后退出循环
while
fast
.
next
and
fast
.
next
.
next
:
# 更新快慢指针
slow
=
slow
.
next
fast
=
fast
.
next
.
next
# 快指针追上慢指针则链表为环形
if
slow
==
fast
:
return
True
# 退出循环,则链表有结束,不可能为环形
return
False
```
算法思维系列/滑动窗口技巧.md
浏览文件 @
666dced7
...
...
@@ -12,8 +12,8 @@
**最新消息:关注公众号参与活动,有机会成为 [70k star 算法仓库](https://github.com/labuladong/fucking-algorithm) 的贡献者,机不可失时不再来**
!
相关推荐:
*
[
东哥吃葡萄时竟然吃出一道算法题!
](
../高频面试系列/吃葡萄.md
)
*
[
如何寻找缺失的元素
](
../高频面试系列/消失的元素.md
)
*
[
东哥吃葡萄时竟然吃出一道算法题!
](
https://labuladong.gitbook.io/algo
)
*
[
如何寻找缺失的元素
](
https://labuladong.gitbook.io/algo
)
读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:
...
...
高频面试系列/LRU算法.md
浏览文件 @
666dced7
...
...
@@ -346,4 +346,121 @@ class LRUCache {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
gowufang
](
https://github.com/gowufang
)
提供第146题C++代码:
```
cpp
class
LRUCache
{
public:
struct
node
{
int
val
;
int
key
;
node
*
pre
;
//当前节点的前一个节点
node
*
next
;
//当前节点的后一个节点
node
(){}
node
(
int
key
,
int
val
)
:
key
(
key
),
val
(
val
),
pre
(
NULL
),
next
(
NULL
){}
};
LRUCache
(
int
size
)
{
this
->
size
=
size
;
head
=
new
node
();
tail
=
new
node
();
head
->
next
=
tail
;
tail
->
pre
=
head
;
}
void
movetohead
(
node
*
cur
)
//相当于一个insert操作,在head 和 head的next之间插入一个节点
{
node
*
next
=
head
->
next
;
//head的next先保存起来
head
->
next
=
cur
;
//将当前节点移动到head的后面
cur
->
pre
=
head
;
//当前节点cur的pre指向head
next
->
pre
=
cur
;
cur
->
next
=
next
;
}
node
*
deletecurrentnode
(
node
*
cur
)
//移除当前节点
{
cur
->
pre
->
next
=
cur
->
next
;
cur
->
next
->
pre
=
cur
->
pre
;
return
cur
;
}
void
makerecently
(
node
*
cur
)
{
node
*
temp
=
deletecurrentnode
(
cur
);
// 删除 cur,要重新插入到对头
movetohead
(
temp
);
//cur放到队头去
}
int
get
(
int
key
)
{
int
ret
=
-
1
;
if
(
map
.
count
(
key
))
{
node
*
temp
=
map
[
key
];
makerecently
(
temp
);
// 将 key 变为最近使用
ret
=
temp
->
val
;
}
return
ret
;
}
void
put
(
int
key
,
int
value
)
{
if
(
map
.
count
(
key
))
{
// 修改 key 的值
node
*
temp
=
map
[
key
];
temp
->
val
=
value
;
// 将 key 变为最近使用
makerecently
(
temp
);
}
else
{
node
*
cur
=
new
node
(
key
,
value
);
if
(
map
.
size
()
==
size
)
{
// 链表头部就是最久未使用的 key
node
*
temp
=
deletecurrentnode
(
tail
->
pre
);
map
.
erase
(
temp
->
key
);
}
movetohead
(
cur
);
map
[
key
]
=
cur
;
}
}
unordered_map
<
int
,
node
*>
map
;
int
size
;
node
*
head
,
*
tail
;
};
```
```
python3
"""
所谓LRU缓存,根本的难点在于记录最久被使用的键值对,这就设计到排序的问题,
在python中,天生具备排序功能的字典就是OrderDict。
注意到,记录最久未被使用的键值对的充要条件是将每一次put/get的键值对都定义为
最近访问,那么最久未被使用的键值对自然就会排到最后。
如果你深入python OrderDict的底层实现,就会知道它的本质是个双向链表+字典。
它内置支持了
1. move_to_end来重排链表顺序,它可以让我们将最近访问的键值对放到最后面
2. popitem来弹出键值对,它既可以弹出最近的,也可以弹出最远的,弹出最远的就是我们要的操作。
"""
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity # cache的容量
self.visited = OrderedDict() # python内置的OrderDict具备排序的功能
def get(self, key: int) -> int:
if key not in self.visited:
return -1
self.visited.move_to_end(key) # 最近访问的放到链表最后,维护好顺序
return self.visited[key]
def put(self, key: int, value: int) -> None:
if key not in self.visited and len(self.visited) == self.capacity:
# last=False时,按照FIFO顺序弹出键值对
# 因为我们将最近访问的放到最后,所以最远访问的就是最前的,也就是最first的,故要用FIFO顺序
self.visited.popitem(last=False)
self.visited[key]=value
self.visited.move_to_end(key) # 最近访问的放到链表最后,维护好顺序
```
高频面试系列/koko偷香蕉.md
浏览文件 @
666dced7
...
...
@@ -169,4 +169,45 @@ for (int i = 0; i < n; i++)
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
tonytang731
](
https://https://github.com/tonytang731
)
提供 Python3 代码:
```
python
import
math
class
Solution
:
def
minEatingSpeed
(
self
,
piles
,
H
):
# 初始化起点和终点, 最快的速度可以一次拿完最大的一堆
start
=
1
end
=
max
(
piles
)
# while loop进行二分查找
while
start
+
1
<
end
:
mid
=
start
+
(
end
-
start
)
//
2
# 如果中点所需时间大于H, 我们需要加速, 将起点设为中点
if
self
.
timeH
(
piles
,
mid
)
>
H
:
start
=
mid
# 如果中点所需时间小于H, 我们需要减速, 将终点设为中点
else
:
end
=
mid
# 提交前确认起点是否满足条件,我们要尽量慢拿
if
self
.
timeH
(
piles
,
start
)
<=
H
:
return
start
# 若起点不符合, 则中点是答案
return
end
def
timeH
(
self
,
piles
,
K
):
# 初始化时间
H
=
0
#求拿每一堆需要多长时间
for
pile
in
piles
:
H
+=
math
.
ceil
(
pile
/
K
)
return
H
```
高频面试系列/二分查找判定子序列.md
浏览文件 @
666dced7
...
...
@@ -168,4 +168,67 @@ boolean isSubsequence(String s, String t) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
dekunma
](
https://www.linkedin.com/in/dekun-ma-036a9b198/
)
提供C++代码
**解法一:遍历(也可以用双指针):**
```
C++
class Solution {
public:
bool isSubsequence(string s, string t) {
// 遍历s
for(int i = 0; i < s.size(); i++) {
// 找到s[i]字符在t中的位置
size_t pos = t.find(s[i]);
// 如果s[i]字符不在t中,返回false
if(pos == std::string::npos) return false;
// 如果s[i]在t中,后面就只看pos以后的字串,防止重复查找
else t = t.substr(pos + 1);
}
return true;
}
};
```
**解法二:二分查找:**
```
C++
class Solution {
public:
bool isSubsequence(string s, string t) {
int m = s.size(), n = t.size();
// 对 t 进行预处理
vector<int> index[256];
for (int i = 0; i < n; i++) {
char c = t[i];
index[c].push_back(i);
}
// 串 t 上的指针
int j = 0;
// 借助 index 查找 s[i]
for (int i = 0; i < m; i++) {
char c = s[i];
// 整个 t 压根儿没有字符 c
if (index[c].empty()) return false;
int pos = left_bound(index[c], j);
// 二分搜索区间中没有找到字符 c
if (pos == index[c].size()) return false;
// 向前移动指针 j
j = index[c][pos] + 1;
}
return true;
}
// 查找左侧边界的二分查找
int left_bound(vector<int> arr, int tar) {
int lo = 0, hi = arr.size();
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (tar > arr[mid]) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
};
```
高频面试系列/合法括号判定.md
浏览文件 @
666dced7
...
...
@@ -112,4 +112,51 @@ char leftOf(char c) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
### Python3
```
python
def
isValid
(
self
,
s
:
str
)
->
bool
:
left
=
[]
leftOf
=
{
')'
:
'('
,
']'
:
'['
,
'}'
:
'{'
}
for
c
in
s
:
if
c
in
'([{'
:
left
.
append
(
c
)
elif
left
and
leftOf
[
c
]
==
left
[
-
1
]:
# 右括号 + left不为空 + 和最近左括号能匹配
left
.
pop
()
else
:
# 右括号 + (left为空 / 和堆顶括号不匹配)
return
False
# left中所有左括号都被匹配则return True 反之False
return
not
left
```
```
java
//基本思想:每次遇到左括号时都将相对应的右括号')',']'或'}'推入堆栈
//如果在字符串中出现右括号,则需要检查堆栈是否为空,以及顶部元素是否与该右括号相同。如果不是,则该字符串无效。
//最后,我们还需要检查堆栈是否为空
public
boolean
isValid
(
String
s
)
{
Deque
<
Character
>
stack
=
new
ArrayDeque
<>();
for
(
char
c
:
s
.
toCharArray
()){
//是左括号就将相对应的右括号入栈
if
(
c
==
'('
)
{
stack
.
offerLast
(
')'
);
}
else
if
(
c
==
'{'
){
stack
.
offerLast
(
'}'
);
}
else
if
(
c
==
'['
){
stack
.
offerLast
(
']'
);
}
else
if
(
stack
.
isEmpty
()
||
stack
.
pollLast
()!=
c
){
//出现右括号,检查堆栈是否为空,以及顶部元素是否与该右括号相同
return
false
;
}
}
return
stack
.
isEmpty
();
}
```
高频面试系列/打印素数.md
浏览文件 @
666dced7
...
...
@@ -178,4 +178,41 @@ int countPrimes(int n) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
C++解法:
采用的算法是埃拉托斯特尼筛法
埃拉托斯特尼筛法的具体内容就是:
**要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。**
同时考虑到大于2的偶数都不是素数,所以可以进一步优化成:
**要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的奇数倍剔除,剩下的奇数就是素数。**
此算法其实就是上面的Java解法所采用的。
这里提供C++的代码:
```
C++
class Solution {
public:
int countPrimes(int n) {
int res = 0;
bool prime[n+1];
for(int i = 0; i < n; ++i)
prime[i] = true;
for(int i = 2; i <= sqrt(n); ++i) //计数过程
{ //外循环优化,因为判断一个数是否为质数只需要整除到sqrt(n),反推亦然
if(prime[i])
{
for(int j = i * i; j < n; j += i) //内循环优化,i*i之前的比如i*2,i*3等,在之前的循环中已经验证了
{
prime[j] = false;
}
}
}
for (int i = 2; i < n; ++i)
if (prime[i]) res++; //最后遍历统计一遍,存入res
return res;
}
};
```
高频面试系列/接雨水.md
浏览文件 @
666dced7
...
...
@@ -211,4 +211,99 @@ if (l_max < r_max) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
Yifan Zhang
](
https://github.com/FanFan0919
)
提供 java 代码
**双指针解法**
:时间复杂度 O(N),空间复杂度 O(1)
对cpp版本的解法有非常微小的优化。
因为我们每次循环只会选 left 或者 right 处的柱子来计算,因此我们并不需要在每次循环中同时更新
`maxLeft`
和
`maxRight`
。
我们可以先比较
`maxLeft`
和
`maxRight`
,决定这次选择计算的柱子是
`height[left]`
或者
`height[right]`
后再更新对应的
`maxLeft`
或
`maxRight`
。
当然这并不会在时间上带来什么优化,只是提供一种思路。
```
java
class
Solution
{
public
int
trap
(
int
[]
height
)
{
if
(
height
==
null
||
height
.
length
==
0
)
return
0
;
int
left
=
0
,
right
=
height
.
length
-
1
;
int
maxLeft
=
height
[
left
],
maxRight
=
height
[
right
];
int
res
=
0
;
while
(
left
<
right
)
{
// 比较 maxLeft 和 maxRight,决定这次计算 left 还是 right 处的柱子
if
(
maxLeft
<
maxRight
)
{
left
++;
maxLeft
=
Math
.
max
(
maxLeft
,
height
[
left
]);
// update maxLeft
res
+=
maxLeft
-
height
[
left
];
}
else
{
right
--;
maxRight
=
Math
.
max
(
maxRight
,
height
[
right
]);
// update maxRight
res
+=
maxRight
-
height
[
right
];
}
}
return
res
;
}
}
```
附上暴力解法以及备忘录解法的 java 代码
**暴力解法**
:时间复杂度 O(N^2),空间复杂度 O(1)
```
java
class
Solution
{
public
int
trap
(
int
[]
height
)
{
if
(
height
==
null
||
height
.
length
==
0
)
return
0
;
int
n
=
height
.
length
;
int
res
=
0
;
// 跳过最左边和最右边的柱子,从第二个柱子开始
for
(
int
i
=
1
;
i
<
n
-
1
;
i
++)
{
int
maxLeft
=
0
,
maxRight
=
0
;
// 找右边最高的柱子
for
(
int
j
=
i
;
j
<
n
;
j
++)
{
maxRight
=
Math
.
max
(
maxRight
,
height
[
j
]);
}
// 找左边最高的柱子
for
(
int
j
=
i
;
j
>=
0
;
j
--)
{
maxLeft
=
Math
.
max
(
maxLeft
,
height
[
j
]);
}
// 如果自己就是最高的话,
// maxLeft == maxRight == height[i]
res
+=
Math
.
min
(
maxLeft
,
maxRight
)
-
height
[
i
];
}
return
res
;
}
}
```
**备忘录解法**
:时间复杂度 O(N),空间复杂度 O(N)
```
java
class
Solution
{
public
int
trap
(
int
[]
height
)
{
if
(
height
==
null
||
height
.
length
==
0
)
return
0
;
int
n
=
height
.
length
;
int
res
=
0
;
// 数组充当备忘录
int
[]
maxLeft
=
new
int
[
n
];
int
[]
maxRight
=
new
int
[
n
];
// 初始化 base case
maxLeft
[
0
]
=
height
[
0
];
maxRight
[
n
-
1
]
=
height
[
n
-
1
];
// 从左向右计算 maxLeft
for
(
int
i
=
1
;
i
<
n
;
i
++)
{
maxLeft
[
i
]
=
Math
.
max
(
maxLeft
[
i
-
1
],
height
[
i
]);
}
// 从右向左计算 maxRight
for
(
int
i
=
n
-
2
;
i
>=
0
;
i
--)
{
maxRight
[
i
]
=
Math
.
max
(
maxRight
[
i
+
1
],
height
[
i
]);
}
// 计算答案
for
(
int
i
=
1
;
i
<
n
;
i
++)
{
res
+=
Math
.
min
(
maxLeft
[
i
],
maxRight
[
i
])
-
height
[
i
];
}
return
res
;
}
}
```
\ No newline at end of file
高频面试系列/消失的元素.md
浏览文件 @
666dced7
...
...
@@ -89,6 +89,7 @@ int missingNumber(int[] nums) {
for
(
int
x
:
nums
)
sum
+=
x
;
return
expect
-
sum
;
}
```
你看,这种解法应该是最简单的,但说实话,我自己也没想到这个解法,而且我去问了几个大佬,他们也没想到这个最简单的思路。相反,如果去问一个初中生,他也许很快就能想到。
...
...
@@ -132,4 +133,49 @@ public int missingNumber(int[] nums) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
happy-yuxuan
](
https://github.com/happy-yuxuan
)
提供 三种方法的 C++ 代码:
```
c++
// 方法:异或元素和索引
int
missingNumber
(
vector
<
int
>&
nums
)
{
int
n
=
nums
.
size
();
int
res
=
0
;
// 先和新补的索引异或一下
res
^=
n
;
// 和其他的元素、索引做异或
for
(
int
i
=
0
;
i
<
n
;
i
++
)
res
^=
i
^
nums
[
i
];
return
res
;
}
```
```
c++
// 方法:等差数列求和
int
missingNumber
(
vector
<
int
>&
nums
)
{
int
n
=
nums
.
size
();
// 公式:(首项 + 末项) * 项数 / 2
int
expect
=
(
0
+
n
)
*
(
n
+
1
)
/
2
;
int
sum
=
0
;
for
(
int
x
:
nums
)
sum
+=
x
;
return
expect
-
sum
;
}
```
```
c++
// 方法:防止整型溢出
int
missingNumber
(
vector
<
int
>&
nums
)
{
int
n
=
nums
.
size
();
int
res
=
0
;
// 新补的索引
res
+=
n
-
0
;
// 剩下索引和元素的差加起来
for
(
int
i
=
0
;
i
<
n
;
i
++
)
res
+=
i
-
nums
[
i
];
return
res
;
}
```
高频面试系列/缺失和重复的元素.md
浏览文件 @
666dced7
...
...
@@ -139,4 +139,30 @@ vector<int> findErrorNums(vector<int>& nums) {
<img
src=
"../pictures/qrcode.jpg"
width=
200
>
</p>
======其他语言代码======
\ No newline at end of file
======其他语言代码======
[
zhuli
](
https://github.com/1097452462
"zhuli"
)
提供的Java代码:
```
java
class
Solution
{
public
int
[]
findErrorNums
(
int
[]
nums
)
{
int
n
=
nums
.
length
;
int
dup
=
-
1
;
for
(
int
i
=
0
;
i
<
n
;
i
++)
{
// 元素是从 1 开始的
int
index
=
Math
.
abs
(
nums
[
i
])
-
1
;
// nums[index] 小于 0 则说明重复访问
if
(
nums
[
index
]
<
0
)
dup
=
Math
.
abs
(
nums
[
i
]);
else
nums
[
index
]
*=
-
1
;
}
int
missing
=
-
1
;
for
(
int
i
=
0
;
i
<
n
;
i
++)
// nums[i] 大于 0 则说明没有访问
if
(
nums
[
i
]
>
0
)
// 将索引转换成元素
missing
=
i
+
1
;
return
new
int
[]{
dup
,
missing
};
}
}
```
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录