This commit is contained in:
yinkanglong_lab
2021-03-18 23:05:16 +08:00
parent 0da81d0db5
commit 4fa84c33f1
18 changed files with 693 additions and 100 deletions

View File

@@ -26,10 +26,10 @@ n*2 | 等价于 左移一位 n << 1
## 3 常见算法
### 快速幂
* 使用二进制方法,将幂转换成二进制。二进制每个位的权重就是可以递推计算,与二分法效果相同。
```
double myPow(double x, int n) {
//使用二进制方法,将幂转换成二进制。二进制每个位的权重就是可以递推计算,与二分法效果相同。
long long N=n;
if(N<0){
x=1/x;
@@ -43,10 +43,23 @@ double myPow(double x, int n) {
N= N>>1;
}
return result;
```
* 使用二分法、递归的方式求解快速幂。
```
//数学类题目。使用快速幂
long long multi(long long x,int n){
if(n == 0) return 1;
if(n == 1) return x;
long long half = multi(x, n / 2);
long long mod = multi(x, n % 2);
return (half * half * mod)%1000000007;
}
```
### 快速乘法
* 二进制的列竖式思想。
```
int quickMulti(int A, int B) {
int ans = 0;
@@ -61,6 +74,8 @@ int quickMulti(int A, int B) {
### 快速加法
* 利用按位与和按位异或运算。求解加法。
* 循环加余法。
```
int add(int a,int b){
cout<<(unsigned int)-1<<endl;

View File

@@ -54,34 +54,36 @@ $$
3. 写出状态转移方程如果给定第k段状态变量xk的值则该段的决策变量uk一经确定第k+1段状态变量xk+1的值也就完全确定。
4. 列出指标函数Vk,n 关系,并要满足递推性。
### 动态规划最关键的部分
* 确定规模的增长方向。一般在动态规划问题中。规模可变的并不只有一个。比如在正则表达式与字符串的匹配问题中。字符串的规模可以变化,正则表达式的规模也可以变化。
## 1 常见问题
### 最长公共子序列
### 矩阵连乘问题
### 1矩阵连乘问题
### 凸多边形最优三角剖分
### 2凸多边形最优三角剖分
### 最长公共子序列
### 3 最长公共子序列
### 图像压缩问题
### 4 斐波那契数列
### 最大子段和问题
### 5图像压缩问题
### 流水作业调度问题
### 6最大子段和问题
### 投资问题
### 7流水作业调度问题
### 01背包问题
### 8投资问题
### 0n背包问题
### 9 01背包问题
### 最优二叉搜索树问题
### 10 0n背包问题
### 序列匹配问题
### 11最优二叉搜索树问题
### 12 序列匹配问题
----

View File

@@ -0,0 +1,248 @@
# 反转链表
## 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)
### 算法实现
```C++
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 ;
}
```
## 2 反转链表
### 问题描述
* 给你单链表的头指针 head 和两个整数 left 和 right 其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
[链接](https://leetcode-cn.com/problems/reverse-linked-list-ii)
## 2.1 反转链表——普通循环反转
### 策略选择
* 循环
### 算法设计
* 在每次循环的时候。到达一个节点。
* 记录本节点的下一个节点
* 记录本节点的上一个几点
* 反转本次节点指向上一个节点
* 复制本层节点= 下一个节点。开始下一次循环。
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(n)
### 算法实现
```C++
ListNode* reverseBetween(ListNode* head, int left, int right) {
int i=1;
ListNode* node=head;
ListNode* last_node=nullptr;
ListNode* before_left=nullptr;
if(left==1){
before_left=new ListNode();
before_left->next=head;
}
ListNode* after_node;
while(true){
after_node=node->next;
if(i==left-1){
before_left=node;
}
if(i>left && i<=right){
node->next=last_node;
}
if(i==right){
before_left->next->next = after_node;
before_left->next=node;
break;
}
last_node=node;
node=after_node;
i++;
}
if(left==1)return before_left->next;
return head;
}
```
## 2.2 反转链表——头插法反转
### 策略选择
* 循环
* 插入反转
### 算法设计
* 每次循环的时候。到达本节点。将本节点的下一个节点插入到左节点前的下一个节点。从left开始
* temp保留该节点的下一个几点。
* 当前节点指向下一个节点的下一个节点(删除下一个节点)
* temp指向左前节点的下一个节点
* 左前节点指向该节点(插入下一个几点)
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(1)
### 算法实现
```
// 头插法
ListNode* reverseBetween2(ListNode* head, int left, int right) {
ListNode* before_head = new ListNode();
before_head->next=head;
ListNode* before_left;
ListNode* node=before_head;
ListNode* temp;
for(int i=0;i<=right;i++){
if(i==left-1)before_left=node;
if(i>=left && i<right){
temp = node->next;
node->next = temp->next;
temp->next=before_left->next;
before_left->next=temp;
}
else{
node=node->next;
}
}
return before_head->next;
}
```
## 链表反转——递归法
### 策略选择
* 递归法
* 等价于使用栈保存了节点的路径。不会在反转后回不到过去的节点
### 算法设计
* 递归
* 当到右节点,使用全局变量记录右节点和右节点的下一个节点。
* 当在左右之间时,直接使自身的下一个节点指向自己。
* 当到达左节点时。与有节点和有节点的下一个反转。
* 递归的参数
* 递归的返回值
* 递归的执行
* 递归的终止条件
* 递归前的处理和递归后的处理。
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(n)
### 算法实现
```C++
// 递归法,把它当做只有一侧的树。后续遍历。取得时候将节点路径放到栈里。回来的时候直接反转也能返回最初的节点。
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* before_head = new ListNode();
before_head->next=head;
dfs(before_head,left,right,0);
return before_head->next;
}
ListNode* right;
ListNode* right_next;
void dfs(ListNode* head,int left,int right,int i){
if(i>right)return ;
// cout<<i<<endl;
dfs(head->next,left,right,i+1);
if(i>=left && i<right){
head->next->next=head;//下一个节点指向自己
}
if(i==right){
this->right=head;
this->right_next=head->next;
// cout<<head->val<<"feji"<<endl;
}
if(i==left-1){
head->next->next=this->right_next;
head->next=this->right;
}
return ;
}
```

View File

@@ -0,0 +1,47 @@
# 链表与双指针
## 1 链表中的倒数第k个节点
### 问题描述
* 输入一个链表输出该链表中倒数第k个节点。为了符合大多数人的习惯本题从1开始计数即链表的尾节点是倒数第1个节点。例如一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
### 问题分析
* 典型的双指针问题
### 问题分类
* 数组与链表
* 双指针问题
### 策略选择
* 蛮力法
### 算法流程
* 两个一样快的指针。相距k个距离。
* 一个到达终点另一个则为倒数第k个节点
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(1)
### 算法实现
```C++
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode* first=head;
ListNode* second=head;
while(--k){
second=second->next;
}
while(second->next){
second = second->next;
first = first->next;
}
return first;
}
```

View File

@@ -1,4 +1,3 @@
## 1 重复数字问题
### 问题描述

View File

@@ -1,85 +0,0 @@
# 反转链表
## 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 ;
}
```

View File

@@ -0,0 +1,139 @@
# 正则表达式匹配问题
## 1 正则表达式匹配
### 问题描述
* 请实现一个函数用来匹配包含'\.'和'\*'的正则表达式。模式中的字符'\.'表示任意一个字符,而'\*'表示它前面的字符可以出现任意次含0次。在本题中匹配是指字符串的所有字符匹配整个模式。例如字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。
### 问题分析
* 可以使用动态规划。将字符串的规模增长的方向,作为动态变化的方向。状态变量表示当前的正则表达式,能够与之匹配。当规模发生变化时,保证字符串-1和正则表达式-1能够匹配。其实在这里感觉规模增长的方向确实是有两个。但是状态变量的设置比较精巧。
### 问题分类
* 字符串匹配问题
* 动态规划
* 递归
## 1.1 正则表达式匹配——动态规划
### 选择策略
* 动态规划
### 算法设计
* 设s的长度为n pp 的长度为 m ;将 s 的第 i 个字符记为 s_ip 的第 j个字符记为 p_j ,将 s 的前 i 个字符组成的子字符串记为s[:i] , 同理将 p 的前 j 个字符组成的子字符串记为 p[:j]p[:j] 。
* 因此本题可转化为求s[:n] 是否能和p[:m] 匹配。
* 总体思路是从 s[:1]和 p[:1]是否能匹配开始判断每轮添加一个字符并判断是否能匹配直至添加完整个字符串s 和p 。展开来看,假设 s[:i]与 p[:j]可以匹配,那么下一状态有两种:
1. 添加一个字符 $s_{i+1}$后是否能匹配?
2. 添加字符 $p_{j+1}$后是否能匹配?
* 本题的状态共有 m \times nm×n 种,应定义状态矩阵 dpdp dp[i][j]代表 s[:i]与 p[:j]是否可以匹配。做好状态定义,接下来就是根据 「普通字符」 , 「.」 , 「*」三种字符的功能定义,分析出动态规划的转移方程。
1. **状态定义** 设动态规划矩阵 dp dp[i][j] 代表字符串 s 的前 i 个字符和 p 的前 j 个字符能否匹配。
2. **转移方程** 需要注意,由于 dp[0][0] 代表的是空字符的状态, 因此 dp[i][j] 对应的添加字符是 s[i - 1] 和 p[j - 1] 。
* 当 p[j - 1] = '*' 时, dp[i][j] 在当以下任一情况为 truetrue 时等于 truetrue
1. dp[i][j - 2] 即将字符组合 p[j - 2] * 看作出现 0 次时,能否匹配.
2. dp[i - 1][j] 且 s[i - 1] = p[j - 2]: 即让字符 p[j - 2] 多出现 1 次时,能否匹配;
3. dp[i - 1][j] 且 p[j - 2] = '.': 即让字符 '.' 多出现 1 次时,能否匹配;
* 当 p[j - 1] != '*' 时, dp[i][j] 在当以下任一情况为 truetrue 时等于 truetrue
1. dp[i - 1][j - 1] 且 s[i - 1] = p[j - 1] 即让字符 p[j - 1] 多出现一次时,能否匹配;
2. dp[i - 1][j - 1] 且 p[j - 1] = '.' 即将字符 . 看作字符 s[i - 1] 时,能否匹配;
* **初始化** 需要先初始化 dp 矩阵首行,以避免状态转移时索引越界。
* **返回值** dp 矩阵右下角字符,代表字符串 s 和 p 能否匹配。
![](image/2021-03-18-20-39-26.png)
### 算法分析
* 时间复杂度O(M*N)
* 空间复杂度O(M*N)
### 算法实现
```C++
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size() + 1, n = p.size() + 1;
vector<vector<bool>> dp(m, vector<bool>(n, false));
dp[0][0] = true;
for(int j = 2; j < n; j += 2)
dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
dp[i][j] = p[j - 1] == '*' ?
dp[i][j - 2] || dp[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == '.'):
dp[i - 1][j - 1] && (p[j - 1] == '.' || s[i - 1] == p[j - 1]);
}
}
return dp[m - 1][n - 1];
}
};
```
## 1.2 正则表达式匹配——递归
### 选择策略
* 递归
### 算法设计
* 递归的参数字符串string、模式pattern、int i、int j分别表示当前字符串匹配到的位置和模式行进到的位置。
* 递归的返回值返回值是当前分支是否成功。如果最后一个检测成果了则返回true。如果最后没有检测成功则返回false
* 递归的执行:
* 如果下一个是*号。进行多分支递归讨论。
1. 匹配0次i=i,j=j+2.进行下一轮递归。
2. 匹配1次判断这一次是否成功如果成功i=i+1,j=j
* 如果下一个不是*号。进行单分支递归。
1. 匹配正常字符或者.。匹配成功。i=i+1,j=j+1
2. 匹配不成功。return false
* 递归的终止条件
* i和j匹配都完成。return true
* 其他情况return false
* 递归前和递归后的处理
### 算法分析
* 时间复杂度O(M+N)
* 空间复杂度O(M)
### 算法实现
```C++
bool isMatch(string s, string p) {
return isMatchR(s,p,0,0);
}
// 递归和循环混搭,果然很恶心。直接使用递归好了。
// 递归的条件判断。正则表达式。
// 算法设计的关键是,选择一个好的算法技术(递归或循环,两个是冗余的。)
// 然后设计好的算法路程。关键在于分类讨论的方式。如何合并类别。
bool isMatchR(string &s,string &p,int i,int j){
//递归终止的条件
if(i>=s.size() && j>=p.size()){
return true;
}
// cout<<i<<j<<endl;
// *判断。
if(j+1<p.size() && p[j+1]=='*'){
// 匹配零次 || 匹配一次
return isMatchR(s,p,i,j+2) || ((i<s.size()) && (p[j]==s[i]||p[j]=='.')&&isMatchR(s,p,i+1,j));
}
// 如果下一个不是*
if(i<s.size() && j<p.size() && p[j]==s[i]){
return isMatchR(s,p,i+1,j+1);
}
if(i<s.size() && j<p.size() && p[j]=='.'){
return isMatchR(s,p,i+1,j+1);
}
// 不匹配
return false;
}
```

View File

@@ -0,0 +1,147 @@
# 表示数值的字符串
## 1 表示数值的字符串
### 问题描述
* 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"-1E-16"、"0123"都表示数值,但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是。
* [链接](https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof)
### 问题分析
* 这题一看,明显就是有限状态自动机。词法分析过程中用来判断关键字、数值的。属于暴力破解。使用优先状态自动机。确定分类讨论的情况。找到符合规则的所有的路。
* 另外提供了另外一种暴力破解的思路。通过讨论违反规则的情况。找到违反规则的所有的路。
* 提供了两种截然不同的分类讨论的思路。在某些情况下,第二种思路反而会简单很多。
* 在 C++ 文档 中,描述了一个合法的数值字符串应当具有的格式。具体而言,它包含以下部分:
* 符号位,即 +、− 两种符号
* 整数部分,即由若干字符 0-9组成的字符串
* 小数点
* 小数部分,其构成与整数部分相同
* 指数部分,其中包含开头的字符 e大写小写均可、可选的符号位和整数部分
### 问题分类
* 枚举法
* 正向分类讨论和反向分类讨论
## 1.1 表示数值的字符串——有限状态自动机DFA
### 选择策略
* 有限状态自动机
* 正向分类讨论
### 算法设计
* 字符类型:空格 「 」、数字「 0—9 」 、正负号 「 + 」 、小数点 「 .」 、幂符号 「 eE 」
* 状态定义:按照字符串从左到右的顺序,定义以下 9 种状态。
1. 开始的空格
2. 幂符号前的正负号
3. 小数点前的数字
4. 小数点、小数点后的数字
5. 当小数点前为空格时,小数点、小数点后的数字
6. 幂符号
7. 幂符号后的正负号
8. 幂符号后的数字
9. 结尾的空格
* 状态转移图
![](image/2021-03-18-21-06-06.png)
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(n)
### 算法实现
```C++
// 方法一有限状态自动机DFA时间复杂度 O(N)
typedef pair<char,int> charint;
typedef unordered_map<char,int> unmap;
bool isNumber(string s) {
vector<unmap> states = {
unmap{charint(' ',0),charint('s',1),charint('d',2),charint('.',4)},
unmap{charint('d',2),charint('.',4)},
unmap{charint('d',2),charint('.',3),charint('e',5),charint(' ',8)},
unmap{charint('d',3),charint('e',5),charint(' ',8)},
unmap{charint('d',3)},
unmap{charint('s',6),charint('d',7)},
unmap{charint('d',7)},
unmap{charint('d',7),charint(' ',8)},
unmap{charint(' ',8)}
};
int p = 0;
char t;
for(char c:s){
if(c >= '0' && c <= '9')
t = 'd';
else if(c == '+' || c == '-')
t = 's';
else if(c == 'e' || c == 'E')
t = 'e';
else if(c == '.' || c == ' ')
t = c;
else
t = '?';
if(!states[p].count(t))
return false;
p = (int) states[p][t];
}
return p == 2 || p == 3 || p == 7 || p == 8;
}
```
## 1.2 表示数值的字符串——反向分类讨论
### 算法设计
* 讨论所有可能出现的反例
* e/E 分割为指数和底数
* 底数:
* 只能有一个+-号位于第一位
* 只能有一个小数点
* 指数
* 只能有一个+-号位于第一位
* 不能有小数点
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O()

View File

@@ -0,0 +1,81 @@
# 和积最大
## 1 剪绳子
### 问题描述
* 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m m、n都是整数n>1并且m>1每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少例如当绳子的长度是8时我们把它剪成长度分别为2、3、3的三段此时得到的最大乘积是18。
* 答案需要取模 1e9+71000000007如计算初始结果为1000000008请返回 1。
[链接](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof)
### 问题分析
### 问题分类
* 贪心思想
* 数值问题
* 无数据结构
### 策略选择
* n尽可能多的包含因子3
### 算法设计
1. 如果 n == 2返回1如果 n == 3返回2两个可以合并成n小于4的时候返回n - 1
2. 如果 n == 4返回4
3. 如果 n > 4分成尽可能多的长度为3的小段每次循环长度n减去3乘积res乘以3最后返回时乘以小于等于4的最后一小段每次乘法操作后记得取余就行
4. 以上2和3可以合并
### 算法分析
* 时间复杂度O(logn)
* 空间复杂度O(1)
### 算法实现
```C++
int cuttingRope1(int n) {
//果然是数学问题
// 这东西由3和2的组成。3越多值越大。也就是存在三种情况。
// n能被3整除。则全为3
// n被3整除余数为1则减掉两个2后全为3
// n被3整除余2减掉1个2后全为3
if(n==2){
return 1;
}
if(n==3){
return 2;
}
if(n%3==0){
return multi(3,n/3);
}
else if(n%3==1){
return (4*multi(3,(n-4)/3))%1000000007;
}
else if(n%3==2){
return (2*multi(3,(n-2)/3))%1000000007;
}
return 0;
}
//数学类题目。使用快速幂
long long multi(long long x,int n){
if(n == 0) return 1;
if(n == 1) return x;
long long half = multi(x, n / 2);
long long mod = multi(x, n % 2);
return (half * half * mod)%1000000007;
}
int cuttingRope(int n) {
if(n < 4){
return n - 1;
}
long res = 1;
while(n > 4){
res = res * 3 % 1000000007;
n -= 3;
}
return (int) (res * n % 1000000007);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB