算法总结

This commit is contained in:
estomm
2021-03-18 17:15:09 +08:00
parent 81ba261f49
commit 0da81d0db5
11 changed files with 514 additions and 104 deletions

View File

@@ -1,11 +1,34 @@
## 1 string容器的操作
## 1 string字符串容器和泛型算法
### 1.1 STL顺序容器
> 参考顺序容器部分
### 1.2 STL泛型算法
* string 对象也可以看作一个顺序容器,它支持随机访问迭代器,也有 begin 和 end 等成员函数。STL 中的许多算法也适用于 string 对象。下面是用 STL 算法操作 string 对象的程序示例。
```C++
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
string s("afgcbed");
string::iterator p = find(s.begin(), s.end(), 'c');
if (p!= s.end())
cout << p - s.begin() << endl; //输出 3
sort(s.begin(), s.end());
cout << s << endl; //输出 abcdefg
next_permutation(s.begin(), s.end());
cout << s << endl; //输出 abcdegf
return 0;
}
```
## 2 string字符串操作
## 2.1 构造函数
## 2.1 字符串创建
* string 类有多个构造函数,用法示例如下:
```C++
string s1(); // si = ""
@@ -35,21 +58,18 @@ s2.assign(s1, 1, 2); // s2 = "23",即 s1 的子串(1, 2)
s2.assign(4, 'K'); // s2 = "KKKK"
s2.assign("abcde", 2, 3); // s2 = "cde",即 "abcde" 的子串(2, 3)
```
## 2.3 字符串长度
## 2.3 字符串交换
* swap 成员函数可以交换两个 string 对象的内容。例如:
```C++
string s1("West”), s2("East");
s1.swap(s2); // s1 = "East"s2 = "West"
```
## 2.4 字符串长度
* int length() 成员函数返回字符串的长度。
* int size() 成员函数可以实现同样的功能。
## 2.4 字符串拼接
* +和+=运算符对string 对象执行字符串的连接操作
* append 成员函数可以用来向字符串后面添加内容。append 成员函数返回对象自身的引用。
```C++
string s1("123"), s2("abc");
s1.append(s2); // s1 = "123abc"
s1.append(s2, 1, 2); // s1 = "123abcbc"
s1.append(3, 'K'); // s1 = "123abcbcKKK"
s1.append("ABCDE", 2, 3); // s1 = "123abcbcKKKCDE",添加 "ABCDE" 的子串(2, 3)
```
## 2.5 字符串比较
* 可以用 <、<=、==、!=、>=、> 运算符比较 string 对象
* compare 成员函数可用于比较字符串。compare 成员函数有以下返回值:
@@ -66,39 +86,26 @@ n = s1.compare("Hello");
n = s1.compare(1, 2, "Hello"); //比较 s1 的子串(1,2)和"Hello”
n = s1.compare(1, 2, "Hello", 1, 2); //比较 s1 的子串(1,2)和 "Hello" 的子串(1,2)
```
## 2.6 字符串子串
* substr 成员函数可以用于求子串 (n, m),原型如下:
```C++
string substr(int n = 0, int m = string::npos) const;
```
* 调用时,如果省略 m 或 m 超过了字符串的长度,则求出来的子串就是从下标 n 开始一直到字符串结束的部分。例如:
```C++
string s1 = "this is ok";
string s2 = s1.substr(2, 4); // s2 = "is i"
s2 = s1.substr(2); // s2 = "is is ok"
```
## 2.7 字符串交换
* swap 成员函数可以交换两个 string 对象的内容。例如:
```C++
string s1("West”), s2("East");
s1.swap(s2); // s1 = "East"s2 = "West"
```
## 2.8 查找字符串(字符)
## 2.6 查找字符串(字符)
* string 类有一些查找子串和字符的成员函数,它们的返回值都是子串或字符在 string 对象字符串中的位置(即下标)。
* 如果查不到,则返回 **string::npos**。string: :npos 是在 string 类中定义的一个静态常量。这些函数如下:
* `find`:从前往后查找子串或字符出现的位置。
* `rfind`:从后往前查找子串或字符出现的位置。
* `find_first_of`:从前往后查找何处出现另一个字符串中包含的字符。
* `find_last_of`:从后往前查找何处出现另一个字符串中包含的字符。
* `find_first_not_of`:从前往后查找何处出现另一个字符串中没有包含的字符。
* `find_last_not_of`:从后往前查找何处出现另一个字符串中没有包含的字符。
* `find`:从前往后查找子串或字符出现的位置。
* `rfind`:从后往前查找子串或字符出现的位置。
* `find_first_of`:从前往后查找何处出现另一个字符串中包含的字符。
* `find_last_of`:从后往前查找何处出现另一个字符串中包含的字符。
* `find_first_not_of`:从前往后查找何处出现另一个字符串中没有包含的字符。
* `find_last_not_of`:从后往前查找何处出现另一个字符串中没有包含的字符。
* 所有的字符串查找方法重载了以下四种类型pos表示开始查找的位置。n表示查找匹配的次数。返回值是整数索引。
* string (1) size_t find (const string& str, size_t pos = 0) const noexcept;
* c-string (2) size_t find (const char* s, size_t pos = 0) const;
* buffer (3) size_t find (const char* s, size_t pos, size_type n) const;
* character (4) size_t find (char c, size_t pos = 0) const noexcept;
```C++
#include <iostream>
#include <string>
@@ -131,8 +138,46 @@ int main()
return 0;
}
```
## 2.7 字符串拼接
* +和+=运算符对string 对象执行字符串的连接操作
* append 成员函数可以用来向字符串后面添加内容。append 成员函数返回对象自身的引用。
* string& append (const string& str);
* string& append (const string& str, size_t subpos, size_t sublen);
* string& append (const char* s);
* string& append (const char* s, size_t n);
* string& append (size_t n, char c);
* string& append (InputIterator first, InputIterator last);
```C++
string s1("123"), s2("abc");
s1.append(s2); // s1 = "123abc"
s1.append(s2, 1, 2); // s1 = "123abcbc"
s1.append(3, 'K'); // s1 = "123abcbcKKK"
s1.append("ABCDE", 2, 3); // s1 = "123abcbcKKKCDE",添加 "ABCDE" 的子串(2, 3)
```
## 2.8 字符串剪切
* substr 成员函数可以用于求子串 (n, m),原型如下。调用时,如果省略 m 或 m 超过了字符串的长度,则求出来的子串就是从下标 n 开始一直到字符串结束的部分。例如:
* string substr (size_t pos = 0, size_t len = npos) const;
```C++
string s1 = "this is ok";
string s2 = s1.substr(2, 4); // s2 = "is i"
s2 = s1.substr(2); // s2 = "is is ok"
```
## 2.9 字符串替换
* replace 成员函数可以对 string 对象中的子串进行替换,返回值为对象自身的引用。例如:
* replace 成员函数可以对 string 对象中的子串进行替换,返回值为对象自身的引用。
* string (1) string& replace (size_t pos, size_t len, const string& str);
* string& replace (const_iterator i1, const_iterator i2, const string& str);
* substring (2) string& replace (size_t pos, size_t len, const string& str,
size_t subpos, size_t sublen);
* c-string (3) string& replace (size_t pos, size_t len, const char* s);
* string& replace (const_iterator i1, const_iterator i2, const char* s);
* buffer (4) string& replace (size_t pos, size_t len, const char* s, size_t n);
* string& replace (const_iterator i1, const_iterator i2, const char* s, size_t n);
* fill (5) string& replace (size_t pos, size_t len, size_t n, char c);
* string& replace (const_iterator i1, const_iterator i2, size_t n, char c);
* range (6) string& replace (const_iterator i1, const_iterator i2, InputIterator first, InputIterator last);
* initializer list (7) string& replace (const_iterator i1, const_iterator i2, initializer_list<char> il);
```C++
string s1("Real Steel");
@@ -145,65 +190,37 @@ int n = s2.find("OOOOO"); //查找子串 "00000" 的位置n=2
s2.replace(n, 5, "XXX"); //将子串(n,5)替换为"XXX"
cout << s2 < < endl; //输出 HaXXX Potter
```
## 2.10 字符串删除
* erase 成员函数可以删除 string 对象中的子串,返回值为对象自身的引用。例如:
```C++
string s1("Real Steel");
s1.erase(1, 3); //删除子串(1, 3),此后 s1 = "R Steel"
s1.erase(5); //删除下标5及其后面的所有字符此后 s1 = "R Ste"
```
## 2.11 字符串插入
## 2.10 字符串插入
* insert 成员函数可以在 string 对象中插入另一个字符串,返回值为对象自身的引用。例如:
* string& insert (size_t pos, const string& str);
* string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
* string& insert (size_t pos, const char* s);
* string& insert (size_t pos, const char* s, size_t n);
* string& insert (size_t pos, size_t n, char c);
* void insert (iterator p, size_t n, char c);
* iterator insert (iterator p, char c);
* void insert (iterator p, InputIterator first, InputIterator last);
```C++
string s1("Limitless"), s2("00");
s1.insert(2, "123"); //在下标 2 处插入字符串"123"s1 = "Li123mitless"
s1.insert(3, s2); //在下标 2 处插入 s2 , s1 = "Li10023mitless"
s1.insert(3, 5, 'X'); //在下标 3 处插入 5 个 'X's1 = "Li1XXXXX0023mitless"
```
## 2.12 字符串流对象
* 使用流对象 istringstream 和 ostringstream可以将 string 对象当作一个流进行输入输出。使用这两个类需要包含头文件 sstream。
## 2.11 字符串删除
* erase 成员函数可以删除 string 对象中的子串,返回值为对象自身的引用。如果使用了迭代器。则返回值为指向删除序列后的第一个字符的迭代器。
* sequence (1)string& erase (size_t pos = 0, size_t len = npos);
* character (2)iterator erase (const_iterator p);
* range (3)iterator erase (const_iterator first, const_iterator last);
```C++
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
string src("Avatar 123 5.2 Titanic K");
istringstream istrStream(src); //建立src到istrStream的联系
string s1, s2;
int n; double d; char c;
istrStream >> s1 >> n >> d >> s2 >> c; //把src的内容当做输入流进行读取
ostringstream ostrStream;
ostrStream << s1 << endl << s2 << endl << n << endl << d << endl << c <<endl;
cout << ostrStream.str();
return 0;
}
string s1("Real Steel");
s1.erase(1, 3); //删除子串(1, 3),此后 s1 = "R Steel"
s1.erase(5); //删除下标5及其后面的所有字符此后 s1 = "R Ste"
```
## 2.13 STL泛型算法
* string 对象也可以看作一个顺序容器,它支持随机访问迭代器,也有 begin 和 end 等成员函数。STL 中的许多算法也适用于 string 对象。下面是用 STL 算法操作 string 对象的程序示例。
```C++
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
string s("afgcbed");
string::iterator p = find(s.begin(), s.end(), 'c');
if (p!= s.end())
cout << p - s.begin() << endl; //输出 3
sort(s.begin(), s.end());
cout << s << endl; //输出 abcdefg
next_permutation(s.begin(), s.end());
cout << s << endl; //输出 abcdegf
return 0;
}
```
## 3 字符串转换
## 3.1 数值转换
@@ -239,11 +256,104 @@ string s("helloworld");
const char * str = s.c_str();
```
## 4 string相关的外部算法
## 4 字符串流对象
* 将string视为特殊的字符串流。使用流对象的方法处理字符串。
* 使用流对象 istringstream 和 ostringstream可以将 string 对象当作一个流进行输入输出。使用这两个类需要包含头文件 sstream。
> string因为支持迭代器所以支持所有的容器模板算法。
```C++
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
string src("Avatar 123 5.2 Titanic K");
istringstream istrStream(src); //建立src到istrStream的联系
string s1, s2;
int n; double d; char c;
istrStream >> s1 >> n >> d >> s2 >> c; //把src的内容当做输入流进行读取
ostringstream ostrStream;
ostrStream << s1 << endl << s2 << endl << n << endl << d << endl << c <<endl;
cout << ostrStream.str();
return 0;
}
```
## 5 正则匹配
> 在正则表达式部分有专门针对string的正则匹配搜索算法。
## 6 C字符串
## 7 字符串分割的两种处理方式
## 8 字符串格式化的两种处理方式
## 9 字符串匹配match和查找search的多种实现方法
* match整个字符串符合要求
* search找到符合要求的字符串子串。
### 循环暴力查找
### 成员函数find查找
### 模板函数find查找
### 正则表达式匹配
## 10 字符串替换的多重实现方法
### 暴力替换
```
string replaceSpace1(string s) {
string result;
for(int i=0;i<s.size();i++){
if(s[i]==' '){
result.append("%20");
}
else{
result.push_back(s[i]);
}
}
return result;
}
```
### 容器库的erase和insert替换
```
// 使用字符串库STL容器库的方法试一下
string replaceSpace2(string s) {
auto beg = s.begin();
auto end = s.end();
while(beg<end){
if(*beg==' '){
beg = s.erase(beg);
beg = s.insert(beg,{'%','2','0'});
end = s.end();
}
else{
beg++;
}
}
return s;
}
```
### string对象的find和replace替换
```
// 使用字符串库STL容器库的find和replace试一下
string replaceSpace(string s) {
int pos=0;
string str="%20";
pos=s.find(" ",pos);
while(pos!=string::npos){
s.replace(pos,1,str);
pos=s.find(" ",pos);
}
return s;
}
```

View File

@@ -1,14 +1,15 @@
# 计划
1. 对刷题的内容进行总结。首先进行这个总结。
2. 学习、复习分治法(把相关思想下的问题和算法补充完整
3. 学习、复习动态规划
4. 学习、复习贪心
5. 学习、复习回溯剪枝
6. 学习、复习分支限界
7. 学习C++容器。对容器的构造函数进行总结。
8. √学习C++容器。对每个容器的基础操作进行总结。
9. √学习C++模板算法。对算法的第三个参数进行总结。
10. 需要重新看一下补码、原码相关的加减法操作
2. 实现字符串分割和字符串格式化的方法C++库string篇
3. 学习、复习分治法(把相关思想下的问题和算法补充完整)
4. 学习、复习动态规划
5. 学习、复习贪心
6. 学习、复习回溯剪枝
7. 学习、复习分支限界
8. √学习C++容器。对容器的构造函数进行总结。
9. √学习C++容器。对每个容器的基础操作进行总结。
10. √学习C++模板算法。对算法的第三个参数进行总结
11. 需要重新看一下补码、原码相关的加减法操作。
# 收获

View File

@@ -9,7 +9,8 @@
## 收获
* 使用递归不能解决动态规划问题。适应为动态规划的子问题有重复。使用递归的方法。会导致重复计算的问题。
* 使用回溯法解决迷宫问题。是因为回溯法和深度优先搜索,在某一时间,只处理一条路径。不需要记住多个搜索方案的搜索状态。如果是广度优先搜索,则需要记录当前所有的路径的另状态。
## 月总结

View File

@@ -7,6 +7,7 @@
## 1 深度搜索
* 深度优先搜索属于图算法的一种是一个针对图和树的遍历算法英文缩写为DFS即Depth First Search。深度优先搜索是图论中的经典算法利用深度优先搜索算法可以产生目标图的相应拓扑排序表利用拓扑排序表可以方便的解决很多相关的图论问题如最大路径问题等等。一般用堆数据结构来辅助实现DFS算法。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止而且每个节点只能访问一次。
* 深度优先搜索与回溯剪枝这两个思想有一个非常重要的优势。它是一条路经走到黑。如果这条路径合适的话,就可以直接返回。另外。当前所有的搜索选项中,只有一条路径在工作。也就是说只需要记录当前一条路径的状态。所以解决迷宫问题的时候,不需要复杂的状态记录方法。只需要记录当前这一个分支的路径状态即可。
## 2 广度搜索
@@ -17,15 +18,15 @@
### 与蛮力法
* **蛮力法的深搜和广搜**。用来搜索树和图中满足条件的节点值。与前置节点或者后置节点没有必然的关系。即一般**无后效性**,前直接点不会影响后置节点的解。
* **蛮力法的深搜和广搜**。用来搜索树和图中**满足条件的节点值**。与前置节点或者后置节点没有必然的关系。即一般**无后效性**,前直接点不会影响后置节点的解。
### 与回溯剪枝的结合
* **回溯剪枝与深搜结合**。一般用来搜索满足条件的路径。与前置节点和后置节点有必然联系。一般具有**多米诺性质**,叶子节点的解一定满足其父节点。叶子结点为真则父节点一定为真。同理父节点为假则叶子结点一定为假(逆否命题)。用父节点为假的情况进行剪枝操作。
* **回溯剪枝与深搜结合**。一般用来搜索**满足条件的路径**。与前置节点和后置节点有必然联系。一般具有**多米诺性质**,叶子节点的解一定满足其父节点。叶子结点为真则父节点一定为真。同理父节点为假则叶子结点一定为假(逆否命题)。用父节点为假的情况进行剪枝操作。
### 与分治限界的结合
* **分支限界与广搜结合**。一般用来搜索满足条件的路径。与前置节点和后置节点有必然联系。一般具有多米诺性质。
* **分支限界与广搜结合**。一般用来搜索**满足条件的路径**。与前置节点和后置节点有必然联系。一般具有**多米诺性质**
### 与递归和迭代思想的结合

View File

@@ -1,2 +1 @@
> 等到以后再处理
> 等到以后再处理

View File

@@ -0,0 +1,62 @@
# 斐波那契数列
## 1 斐波那契数列
### 问题描述
* 写一个函数,输入 n 求斐波那契Fibonacci数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
```
F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
```
* 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。答案需要取模 1e9+71000000007如计算初始结果为1000000008请返回 1。
* [链接](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof)
### 问题分析
### 问题分类
* 数组
* 动态规划+迭代
### 选择策略
* 数组
* 动态规划
* 原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)f(n+1)=f(n)+f(n1) 为转移方程。
### 算法设计
* 状态定义: 设$dp$为一维数组,其中 $dp[i]$ 的值代表 斐波那契数列第 $i$个数字 。
* 转移方程: $dp[i + 1] = dp[i] + dp[i - 1]$,即对应数列定义 $f(n + 1) = f(n) + f(n - 1)$
* 初始状态: $dp[0] = 0 dp[1] = 1$,即初始化前两个数字;
* 返回值:$dp[n]$ ,即斐波那契数列的第 n 个数字。
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(n)
### 算法实现
```
int fib(int n) {
// 递归法,时间复杂度太高。
if(n==0){
return 0;
}
if(n==1){
return 1;
}
// return fib(n-1)+fib(n-2);
long long a[n+1];
a[0]=0;
a[1]=1;
for(int i=2;i<n+1;i++){
a[i]=(a[i-1]+a[i-2])%(1000000007);
}
return a[n];
}
```

View File

@@ -7,6 +7,8 @@
* 与剪枝相结合,也称为**回溯-剪枝法**。
* 深度优先搜索与回溯剪枝这两个思想有一个非常重要的优势。它是一条路经走到黑。如果这条路径合适的话,就可以直接返回。另外。当前所有的搜索选项中,**只有一条路径**在工作。也就是说只需要记录当前一条路径的状态。所以解决迷宫问题的时候,不需要复杂的状态记录方法。**只需要记录当前这一个分支的路径状态**即可。
### 要素
* **解向量**-问题解的表示:
回溯法将问题的解表示成n元式$(x_1,x_2,\cdots,x_n)$。

View File

@@ -0,0 +1,72 @@
# 迷宫问题
## 1 矩阵中的路径
### 问题描述
* 请设计一个函数用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格那么该路径不能再次进入该格子。例如在下面的3×4的矩阵中包含一条字符串“bfce”的路径路径中的字母用加粗标出
```
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
```
* 但矩阵中不包含字符串“abfb”的路径因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后路径不能再次进入这个格子。
* [链接](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof)
### 问题分析
* 最典型的回溯剪枝算法。
### 问题分类
* 二维数组,矩阵
* 回溯剪枝、dfs、递归
* 迷宫问题
### 策略选择
* 深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
* 剪枝: 在搜索中,遇到 这条路不可能和目标字符串匹配成功 的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为 可行性剪枝
### 算法设计
* 递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。
* 终止条件:
* 返回 false (1) 行或列索引越界 或 (2) 当前矩阵元素与目标字符不同 或 (3) 当前矩阵元素已访问过 (3) 可合并至 (2)
* 返回 true k = len(word) - 1 ,即字符串 word 已全部匹配。
* 递推工作:
* 标记当前矩阵元素: 将 board[i][j] 修改为 空字符 '' ,代表此元素已访问过,防止之后搜索时重复访问。
* 搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
* 还原当前矩阵元素: 将 board[i][j] 元素还原至初始值,即 word[k] 。
* 返回值: 返回布尔量 res ,代表是否搜索到目标字符串。
### 算法分析
* 时间复杂度 O(3^K MN)
* 空间复杂度 O(K)
### 算法实现
```
bool exist(vector<vector<char>>& board, string word) {
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(dfs(board,word,i,j,0)==true){
return true;
}
}
}
return false;
}
bool dfs(vector<vector<char>> & board,string &word,int row,int col,int k){
if(row<0 || col <0 ||row>=board.size()||col>=board[0].size())return false;
// cout<<word[k]<<" "<<board[row][col]<<endl;
if(word[k]!=board[row][col])return false;
if(k==word.size()-1)return true;
// cout<<row<<" "<<col<<endl;
char temp = board[row][col];
board[row][col]='\0';
bool result = dfs(board,word,row+1,col,k+1)
|| dfs(board,word,row-1,col,k+1)
|| dfs(board,word,row,col-1,k+1)
|| dfs(board,word,row,col+1,k+1);
board[row][col]=temp;
return result;
}
```

View File

@@ -86,4 +86,28 @@
### 算法设计
* 每次交换
* 利用数组的下标作为键。每次将元素归位。
* 如果数组下标与元素值相等。则下一个
* 如果数组下边与元素值不想等,则一直交换本元素值。直到相等
* 如果要交换的两个元素值相等。则检测到重复元素。返回
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(1)
### 算法实现
```
// 原地置换的方法
int findRepeatNumber(vector<int>& nums) {
for(int i=0;i<nums.size();i++){
if(i==nums[i])continue;
if(nums[i]==nums[nums[i]])return nums[i];
else {
swap(nums[i],nums[nums[i]]);
i--;
}
}
return 0;
}
```

View File

@@ -0,0 +1,53 @@
## 1 二维数组的查找
### 问题描述
* 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
* [链接](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof)
### 问题分析
* 与一维查找类似。找到中间数字,每次移动可以舍弃掉一半的数字。由于给定的二维数组具备每行从左到右递增以及每列从上到下递增的特点,当访问到一个元素时,可以排除数组中的部分元素。
* 从二维数组的右上角开始查找。如果当前元素等于目标值,则返回 true。如果当前元素大于目标值则移到左边一列。如果当前元素小于目标值则移到下边一行。
### 问题分类
* 线性数据结构
* 枚举法
* 查找问题
### 策略选择
### 算法设计
* 若数组为空,返回 false
* 初始化行下标为 0列下标为二维数组的列数减 1
* 重复下列步骤,直到行下标或列下标超出边界
* 获得当前下标位置的元素 num
* 如果 num 和 target 相等,返回 true
* 如果 num 大于 target列下标减 1
* 如果 num 小于 target行下标加 1
* 循环体执行完毕仍未找到元素等于 target ,说明不存在这样的元素,返回 false`
### 算法分析
* 时间复杂度O(m+n)
* 空间复杂度O(1)
### 算法实现
```
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
int i = matrix.size() - 1, j = 0;
while(i >= 0 && j < matrix[0].size())
{
if(matrix[i][j] > target) i--;
else if(matrix[i][j] < target) j++;
else return true;
}
return false;
}
```

View File

@@ -0,0 +1,85 @@
# 反转链表
## 1 从尾到头打印链表内容
### 问题描述
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
### 问题分类
* 线性数据结构
* 枚举法
* 排序问题
### 问题分析
## 1.1 从尾到头打印链表内容——辅助栈
### 策略选择
* 数据结构:链表、栈、数组
* 算法思想:枚举法
* 可以利用栈的先进后出特性。
### 算法设计
1. 入栈: 遍历链表,将各节点值 push 入栈。Python 使用 append() 方法Java借助 LinkedList 的addLast()方法)。
2. 出栈: 将各节点值 pop 出栈存储于数组并返回。Python 直接返回 stack 的倒序列表Java ​新建一个数组,通过 popLast() 方法将各元素存入数组,实现倒序输出)。
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(n)
### 算法实现
```
vector<int> reversePrint1(ListNode* head) {
stack<int> s;
vector<int> v;
ListNode * temp=head;
while(temp){
s.push(temp->val);
temp=temp->next;
}
while(!s.empty()){
v.push_back(s.top());
s.pop();
}
return v;
}
```
## 1.2 从尾到头打印链表内容——递归法
### 策略选择
* 数据结构:链表
* 算法思想:枚举法
* 利用递归后续遍历的特性(子节点在父节点之前除了)
### 算法设计
1. 递推阶段: 每次传入 head.next ,以 head == None即走过链表尾部节点为递归终止条件此时返回空列表 [] 。
2. 回溯阶段: 利用 Python 语言特性,递归回溯时每次返回 当前 list + 当前节点值 [head.val] ,即可实现节点的倒序输出。
### 算法分析
* 时间复杂度O(n)
* 空闲复杂度O(n)
### 算法实现
```
vector<int> reversePrint(ListNode* head) {
vector<int> vec;
rp(head,vec);
return vec;
}
void rp(ListNode* head,vector<int>&vec) {
if(head==nullptr)return ;
rp(head->next,vec);
vec.push_back(head->val);
return ;
}
```