Skip to content

Commit 88da39b

Browse files
committed
新增堆栈部分练习
1 parent 7222ec6 commit 88da39b

File tree

14 files changed

+765
-52
lines changed

14 files changed

+765
-52
lines changed

README.md

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,18 @@
8484

8585
## [哈希算法](./hash.md)
8686

87+
下面这四大基本算法,贪心,回溯,DP属于一类,这三个算法解决问题的模型都可以定义为“多阶段最优解问题”。
8788

88-
<!--# 解决多阶段决策最优解模型的算法
89-
解决问题的过程中,需要经过多个决策阶段,每个决策都会对应一个状态。我们寻找一组决策序列,经过这组决策序列,能够产生最终期望的最优值。我们把这种问题模型称为<font size=5 color=red>多阶段决策最优解模型</font>. DP,回溯,贪心都可以解决这类问题.
89+
回溯算法属于万金油,基本能用DP和贪心解决的题目都能用回溯暴力解决,不过回溯算法穷举的做法使得它的复杂度非常高,是指数级别的,所以只能适用于小规模数据的问题。
9090

91-
利用动态规划解决的问题,需要满足三个特征:
91+
DP比回溯更高效,但并不是所有问题都可以通过DP解决的。能用DP解决的问题,需要满足最优子结构,无后效性,重复子问题这三个特征。DP之所以如此高效,原因就在于它通过合并重复的状态,来达到剪枝的目的(比如矩阵从左上角移动到右下角,求最短路径这个题目,要么下一步向右走,要么下一步向下走,我们选择那条之前状态是最短路径的那条,这样我们就打到了剪枝的目的).
9292

93-
1. 最优子结构, 就是说后面的状态可以通过前面的状态推导出来
94-
2. 无后效性, 就是说一旦状态确定,就不会更改
95-
3. 重复子问题, 就是说不同的决策序列,到达某个相同的阶段时,可能会产生不同的状态。
9693

97-
贪心算法实际上是DP的一种特殊情况,它能解决的问题更加有限。需要满足三个条件:
94+
贪心算法能解决的问题更加局限。需要满足三个条件,最优子结构,无后效性和贪心选择性,即通过局部最优可以产生全局的最优选择。
9895

99-
1. 最优子结构
100-
2. 无后效性
101-
3. 贪心选择性, 意思就是局部最优选择,能产生全局最优选择。
10296

103-
所以贪心算法能否解决算法问题的关键在于: 局部最优能不能达到全局最优?
10497

10598

106-
回溯算法是”万金油“。基本上贪心和dp能解决的问题,回溯都能解决。回溯相当于穷举搜索,列举出所有的情况,然后对比得到最优解。不过回溯的复杂度一般都是指数级的,只能用来解决小规模数据的问题。-->
107-
10899

109100
## [贪心](./greed.md)
110101
## [回溯算法](./backtracking.md)

alg-cpp.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
0925CDE722F67BB100DB3191 /* Permutation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Permutation.h; sourceTree = "<group>"; };
186186
0929C38122FE7804006905B4 /* deleteDuplicates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = deleteDuplicates.h; sourceTree = "<group>"; };
187187
0929C38422FE8DFE006905B4 /* getIntersectionNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = getIntersectionNode.h; sourceTree = "<group>"; };
188+
092A72A02358BA02005B0DC2 /* hanoiProblem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hanoiProblem.h; sourceTree = "<group>"; };
188189
093D6BFB22FBA0D000771DEC /* pascals_triangle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pascals_triangle.h; sourceTree = "<group>"; };
189190
093D6BFE22FBA5D200771DEC /* pascals_triangle_ii.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pascals_triangle_ii.h; sourceTree = "<group>"; };
190191
093D6C0122FC704700771DEC /* best_time_to_buy_and_sell_stock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = best_time_to_buy_and_sell_stock.h; sourceTree = "<group>"; };
@@ -254,6 +255,7 @@
254255
099EAEF822FFC4C5006437BD /* palindrome_number.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = palindrome_number.h; sourceTree = "<group>"; };
255256
09A208B02308D96E00094088 /* zigzag_conversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zigzag_conversion.h; sourceTree = "<group>"; };
256257
09A208B32309990C00094088 /* string_to_integer_atoi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = string_to_integer_atoi.h; sourceTree = "<group>"; };
258+
09A7AC1D234E34EA00DFECF0 /* matrix_move.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = matrix_move.h; sourceTree = "<group>"; };
257259
09ABED832300E22800113589 /* romanToInt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = romanToInt.h; sourceTree = "<group>"; };
258260
09ABED872300EE0900113589 /* longestCommonPrefix.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = longestCommonPrefix.h; sourceTree = "<group>"; };
259261
09ACF02B2316DB0B004A4A61 /* divideandconquer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = divideandconquer; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -286,6 +288,10 @@
286288
09E18E5D22F58301002D0227 /* GetNumberOfK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GetNumberOfK.h; sourceTree = "<group>"; };
287289
09E18E6022F588E8002D0227 /* FindNumsAppearOnce.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FindNumsAppearOnce.h; sourceTree = "<group>"; };
288290
09E18E6422F59065002D0227 /* replaceSpace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = replaceSpace.h; sourceTree = "<group>"; };
291+
09EF0F4C235F43A4008E90DC /* getMaxWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = getMaxWindow.h; sourceTree = "<group>"; };
292+
09F082AF235768BD000F84C0 /* sortStackByStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sortStackByStack.h; sourceTree = "<group>"; };
293+
09F2A5E52354CF320026F4A2 /* TwoStacksQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TwoStacksQueue.h; sourceTree = "<group>"; };
294+
09F32137235614E5005E29E2 /* reverseStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = reverseStack.h; sourceTree = "<group>"; };
289295
09F38FCD22FCE719001B4618 /* best_time_to_buy_and_sell_stock_ii.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = best_time_to_buy_and_sell_stock_ii.h; sourceTree = "<group>"; };
290296
09F38FD022FCEC64001B4618 /* twoSum_ii.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = twoSum_ii.h; sourceTree = "<group>"; };
291297
09F38FD322FCEF88001B4618 /* majorityElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = majorityElement.h; sourceTree = "<group>"; };
@@ -716,6 +722,7 @@
716722
3A04E7432341F1B7006770AF /* double11advance.h */,
717723
094524BA234A32D000CCBFB9 /* levenshtein_distance.h */,
718724
09665E9C234CD7ED003D0FAE /* pascals_triangle.h */,
725+
09A7AC1D234E34EA00DFECF0 /* matrix_move.h */,
719726
);
720727
path = dp;
721728
sourceTree = "<group>";
@@ -826,6 +833,18 @@
826833
path = "coding-interviews";
827834
sourceTree = "<group>";
828835
};
836+
09F2A5E42354CF0D0026F4A2 /* itinterviews */ = {
837+
isa = PBXGroup;
838+
children = (
839+
09F2A5E52354CF320026F4A2 /* TwoStacksQueue.h */,
840+
09F32137235614E5005E29E2 /* reverseStack.h */,
841+
09F082AF235768BD000F84C0 /* sortStackByStack.h */,
842+
092A72A02358BA02005B0DC2 /* hanoiProblem.h */,
843+
09EF0F4C235F43A4008E90DC /* getMaxWindow.h */,
844+
);
845+
path = itinterviews;
846+
sourceTree = "<group>";
847+
};
829848
09FC391C2304D98D00F0A2AE /* easy */ = {
830849
isa = PBXGroup;
831850
children = (
@@ -889,6 +908,7 @@
889908
3A5C8B9522E0109200354740 /* stack+queue */ = {
890909
isa = PBXGroup;
891910
children = (
911+
09F2A5E42354CF0D0026F4A2 /* itinterviews */,
892912
0946B02122F6B49E0043469D /* coding-interviews */,
893913
3A5C8BA422E05ABF00354740 /* leetcode */,
894914
3A5C8B9622E0109200354740 /* main.cpp */,
19.7 KB
Binary file not shown.

dp.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,24 @@
1111
## 什么样的问题适合使用动态规划来解决?
1212

1313

14-
动态规划适合用于<font size=5 color=red>多阶段决策最优解模型</font>, 就是说动态规划一般是用来解决最优解问题,而解决问题的过程需要经历多个阶段,每个阶段决策之后会对应一组状态,然后我们寻找一组决策序列,经过这组决策序列,最终求出问题的最优解。
14+
step1: 首先看它是否属于<font size=5 color=red>多阶段决策最优解模型</font>,就是说解决问题的过程需要经历多个阶段,每个阶段决策之后会对应一组状态,然后我们寻找一组决策序列,经过这组决策序列,最终求出问题的最优解。
1515

16+
step2: 是否满足最优子结构,即是否能够从前面的状态推导出后面的状态。
17+
18+
step3: 是否满足无后效性,即前面状态一旦确定,就不会受之后阶段决策的影响。并且我们通过推到后面的状态的时候,只关心前面状态的值,而并不用关心它是怎么一步步推导出来的。
19+
20+
step4: 是否满足重复子问题,即当到达同一个阶段时是否存在多个状态.
21+
22+
23+
## 动态规划的解题思路
24+
25+
26+
关键点在于,<font size=5 color=red>如何定义状态,如何通过之前的状态推导出后面阶段的状态。</font>
27+
28+
对照着下面整个具体的例子,可以更好的去理解上面这些概念.
29+
30+
31+
<font size=5 color=red>[matrix_move](./dp/matrix_move.h)</font>
1632

1733

1834

@@ -24,6 +40,7 @@
2440
| 双11打折优惠问题 | [double11advance](./dp/double11advance.h)||
2541
| 硬币找零问题 | [coinChange2](./dp/coinChange.h)||
2642
| 杨辉三角变种 | [pascals_triangle](./dp/pascals_triangle.h)||
43+
| 矩阵从左上角移动到右下角 | [matrix_move](./dp/matrix_move.h)||
2744
| 莱文斯坦最短编辑距离 | -- ||
2845
| 两个字符串的最长公共子序列 | -- ||
2946
| 数据序列的最长递增子序列 | -- ||

dp/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "coinChange.h"
1515
#include "levenshtein_distance.h"
1616
#include "pascals_triangle.h"
17+
#include "matrix_move.h"
1718
int main(int argc, const char * argv[]) {
1819
// insert code here...
1920
using namespace std;
@@ -22,5 +23,6 @@ int main(int argc, const char * argv[]) {
2223
test_coinChange();
2324
test_levenshtein_distance();
2425
test_pascals_triangle();
26+
test_matrix_move();
2527
return 0;
2628
}

dp/matrix_move.h

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//
2+
// matrix_move.h
3+
// dp
4+
//
5+
// Created by junl on 2019/10/9.
6+
// Copyright © 2019 junl. All rights reserved.
7+
//
8+
9+
#ifndef matrix_move_hpp
10+
#define matrix_move_hpp
11+
12+
#include <stdio.h>
13+
#include <vector>
14+
/*
15+
和前面的杨辉三角变种有点类似:
16+
问题是有一个n*n的矩阵,矩阵存储的都是正整数,旗子最开始位于左上角,终止位置在右下角。中间经过的数字是它们的路径和,求最短的路径和.
17+
18+
1,3,5,9
19+
2,1,3,4
20+
5,2,6,7
21+
6,8,4,3
22+
*/
23+
using namespace std;
24+
class matrix_move {
25+
public:
26+
int solve(vector<vector<int>> &matrix){
27+
if (matrix.empty() || matrix.size() != matrix[0].size()) {
28+
return 0;
29+
}
30+
solve(matrix, 0, 0, matrix[0][0]);
31+
return result;
32+
}
33+
private:
34+
int result = INT_MAX;
35+
36+
void solve(vector<vector<int>> &matrix, int row,int col ,int pathLen){
37+
size_t N = matrix.size()-1;
38+
if (row == N && col == N) {
39+
result = min(result, pathLen);
40+
return;
41+
}
42+
if (row < N) {
43+
solve(matrix, row+1, col, pathLen+matrix[row+1][col]);
44+
}
45+
if (col < N) {
46+
solve(matrix, row, col+1, pathLen+matrix[row][col+1]);
47+
}
48+
}
49+
};
50+
51+
52+
class matrix_move_dp {
53+
public:
54+
/*
55+
思路:
56+
假设矩阵大小为N*N,那么从左上角移动到右下角一共要移动2*(N-1)次.
57+
每一次要么向下移动,要么向右移动,每次移动之后,状态(路径和)都会发生变化。该问题符合动态规划的“一个模型三个条件”
58+
59+
怎么定义状态呢?
60+
-- int min_path[i][j] 里面保存的是从左上角到matrix[i][j]的最小路径和
61+
状态转移方程是什么?
62+
-- 走法只有两种,那么到达matrix[i][j]的位置,只有可能是min_path[i-1][j]或者min_path[i][j-1]中的一条.
63+
-- min_path[i][j] = min(min_path[i-1][j], min_path[i][j-1]) + matrix[i][j];
64+
*/
65+
int solve(vector<vector<int>> &matrix){
66+
if (matrix.empty()) {
67+
return 0;
68+
}
69+
size_t N = matrix.size();
70+
int min_path[N][N];
71+
memset(min_path, 0, sizeof(min_path));
72+
73+
//设置初始状态
74+
int sum=0;
75+
int i;
76+
for (i=0; i<N; i++) {//第一排的初始状态
77+
sum+=matrix[0][i];
78+
min_path[0][i]=sum;
79+
}
80+
81+
sum=0;
82+
for (i=0; i<N; i++) { //第一列的初始状态
83+
sum+=matrix[i][0];
84+
min_path[i][0]=sum;
85+
}
86+
87+
//通过初始状态动态向前推导,按照一行行的向下推导
88+
for (i=1; i<N; i++) {
89+
for (int j=1; j<N; j++) {
90+
min_path[i][j] = min(min_path[i-1][j], min_path[i][j-1]) + matrix[i][j];
91+
}
92+
}
93+
return min_path[N-1][N-1];
94+
};
95+
};
96+
97+
void test_matrix_move(){
98+
vector<vector<int>> matrix;
99+
matrix.push_back({1,3,5,9});
100+
matrix.push_back({2,1,3,4});
101+
matrix.push_back({5,2,6,7});
102+
matrix.push_back({6,8,4,3});
103+
class matrix_move so;
104+
cout << "----------矩阵移动-----------" << endl;
105+
cout << so.solve(matrix) << endl;
106+
107+
class matrix_move_dp so_dp;
108+
cout << so_dp.solve(matrix) << endl;
109+
110+
}
111+
112+
113+
#endif /* matrix_move_hpp */
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// TwoStacksQueue.h
3+
// stack+queue
4+
//
5+
// Created by junl on 2019/10/14.
6+
// Copyright © 2019 junl. All rights reserved.
7+
//
8+
9+
#ifndef TwoStacksQueue_hpp
10+
#define TwoStacksQueue_hpp
11+
12+
#include <stdio.h>
13+
#include <stack>
14+
/*
15+
思路:
16+
两个栈正好可以实现倒序.关键点把pushStack的数据倒序放到popStack中:
17+
1.如果popStack包含元素了的话,那么不能将数据从pushStack移动到popStack
18+
2.必须一次性的把pushStackd里面的元素移动到popStack中
19+
*/
20+
class TwoStacksQueue {
21+
public:
22+
typedef int Value_t;
23+
void add(Value_t x)//添加元素
24+
{
25+
pushStack.push(x);
26+
}
27+
void poll()//删除队列首元素
28+
{
29+
move();
30+
if (popStack.empty()) {
31+
throw stackEmpty();
32+
}
33+
popStack.pop();
34+
}
35+
Value_t peek()//获取首元素
36+
{
37+
if (popStack.empty() && pushStack.empty()) {
38+
throw stackEmpty();
39+
}
40+
move();
41+
return popStack.top();
42+
}
43+
private:
44+
std::stack<Value_t> pushStack;
45+
std::stack<Value_t> popStack;
46+
void move(){
47+
if (popStack.empty()) {
48+
//如果popStack为空,则把pushStack的所有元素倒序加入.
49+
while (!pushStack.empty()) {
50+
popStack.push(pushStack.top());
51+
pushStack.pop();
52+
}
53+
}
54+
}
55+
};
56+
57+
58+
void test_TwoStacksQueue(){
59+
std::cout << "-----------test_TwoStacksQueue-----------" << std::endl;
60+
TwoStacksQueue queue;
61+
queue.add(1);
62+
queue.add(2);
63+
queue.add(3);
64+
std::cout << "peek: "<< queue.peek() << std::endl;//1
65+
queue.poll();
66+
std::cout << "peek: "<< queue.peek() << std::endl;//2
67+
68+
}
69+
70+
#endif /* TwoStacksQueue_hpp */

0 commit comments

Comments
 (0)