算法整理

This commit is contained in:
estomm
2021-03-19 09:57:57 +08:00
parent 4fa84c33f1
commit d382bc86eb
12 changed files with 534 additions and 2 deletions

View File

@@ -44,7 +44,7 @@
1. 选择合适的**数据结构**
2. 选择合适的**算法思想**
3. **算法设计**
1. 确定**算法技术**,递归技术、循环技术、位运算技术
1. 确定**算法原理**,递归技术、循环技术、位运算技术
2. 设计**算法流程**,数学递推关系和伪代码
4. **正确性证明**
1. 查看伪代码的流程的正确性,手推算法过程。

View File

@@ -0,0 +1,71 @@
## 1 连续子数组最大和
### 问题描述
* 输入一个整型数组数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。
### 算法设计
* 状态定义: 设动态规划列表 dpdp dp[i]dp[i] 代表以元素 nums[i]nums[i] 为结尾的连续子数组最大和。
* 为何定义最大和 dp[i]dp[i] 中必须包含元素 nums[i]nums[i] :保证 dp[i]dp[i] 递推到 dp[i+1]dp[i+1] 的正确性;如果不包含 nums[i]nums[i] ,递推时则不满足题目的 连续子数组 要求。
* 转移方程: 若 dp[i-1] \leq 0dp[i1]≤0 ,说明 dp[i - 1]dp[i1] 对 dp[i]dp[i] 产生负贡献,即 dp[i-1] + nums[i]dp[i1]+nums[i] 还不如 nums[i]nums[i] 本身大。
* 当 dp[i - 1] > 0dp[i1]>0 时:执行 dp[i] = dp[i-1] + nums[i]dp[i]=dp[i1]+nums[i]
* 当 dp[i - 1] \leq 0dp[i1]≤0 时:执行 dp[i] = nums[i]dp[i]=nums[i]
* 初始状态: dp[0] = nums[0]dp[0]=nums[0],即以 nums[0]nums[0] 结尾的连续子数组最大和为 nums[0]nums[0] 。
* 返回值: 返回 dpdp 列表中的最大值,代表全局最大值。
![](image/2021-03-19-00-24-38.png)
### 算法分析
* 时间复杂度 O(N)O(N) 线性遍历数组 numsnums 即可获得结果,使用 O(N)O(N) 时间。
* 空间复杂度 O(1)O(1) 使用常数大小的额外空间。
### 算法实现
```C++
int maxSubArray(vector<int>& nums) {
//动态规划有很多不同的方向。例如,这里把动态变化定位不断增加的连续数组的长度。
// 当连续数组的长度为1,2,3,4,5时会利用之前计算的结果。求解。但是这很暴力。
// 相当于求出了当前的所有最长子数组的解。把问题的规模缩小。
// nums的长度。动态变化。
// 失败的动态规划,实际上是暴力求解。
// vector<vector<int>> vec;
// int max=-999999;
// vec.push_back(vector<int>());
// for(int i=0;i<nums.size();i++){
// vec[0].push_back(nums[i]);
// if(nums[i]>max){
// max=nums[i];
// }
// }
// for(int i=1;i<nums.size();i++){
// vec.push_back(vector<int>());
// for(int j=0;j<nums.size()-i;j++){
// vec[i].push_back(vec[i-1][j]+vec[0][j+i]);
// if(vec[i][j]>max){
// max=vec[i][j];
// }
// }
// }
// 正常的动态规划
int max=nums[0];
vector<int> vec;
vec.push_back(nums[0]);
for(int i=1;i<nums.size();i++){
int temp =nums[i]+vec[i-1];
if(temp > nums[i]){
vec.push_back(temp);
}
else{
vec.push_back(nums[i]);
}
if(vec[i]>max){
max=vec[i];
}
}
return max;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,18 @@
# 树的遍历
## 1 树的子结构
* 输入两棵二叉树A和B判断B是不是A的子结构。(约定空树不是任意一个树的子结构).B是A的子结构 即 A中有出现和B相同的结构和节点值。
* [链接](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)
## 2 树的镜像
* 请完成一个函数,输入一个二叉树,该函数输出它的镜像。
* [链接](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/)
## 3 对称的二叉树
* 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
* [链接](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)

View File

@@ -0,0 +1,138 @@
# 二叉搜索树与双向链表
## 1
### 问题分析
* 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
* [链接](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/)
### 算法设计
* 通过左旋右旋操作实现树的旋转。最终旋转成一个倒V树。
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(n)
### 算法实现
```C++
class Solution {
public:
Node* treeToDoublyList(Node* root) {
// 类似于二叉平衡树的左旋右旋。
if(root==nullptr){
return nullptr;
}
// 进行左旋和右旋
m[root]=nullptr;
m[root->left]=root;
m[root->right]=root;
rotate_left(root->left);
rotate_right(root->right);
// 旋转完成后,制作循环链表
Node* temp_left=root;
while(temp_left->left!=nullptr){
temp_left=temp_left->left;
}
Node* temp_right=root;
while(temp_right->right!=nullptr){
temp_right=temp_right->right;
}
temp_right->right=temp_left;
temp_left->left = temp_right;
// 返回链表的最左端
return temp_left;
}
//使用map来记录父节点
map<Node*,Node*> m;
//左树有右节点,则左旋
void rotate_left(Node* root){
if(root==nullptr){
return;
}
// 头递归,子树完成了旋转再旋转本树
m[root->left]=root;
m[root->right]=root;
rotate_left(root->left);
rotate_right(root->right);
// 右树为空,不需要左旋,只需要改变一个连接right指向父节点
if(root->right==nullptr){
root->right=m[root];
return;
}
// 右树不为空。则找到右子树。插入到本节点和父节点之间
Node* temp_right=root->right;
while(temp_right->right!=nullptr){
temp_right=temp_right->right;
}
// 改变3个连接。最后一个右节点与父节点相互指向。右节点的做节点指向这一个。
temp_right->right=m[root];
m[root]->left=temp_right;
root->right->left=root;
return ;
}
// 右树有左节点,则右旋
void rotate_right(Node* root){
if(root==nullptr){
return;
}
// 头递归,子树完成了旋转再旋转本树
m[root->left]=root;
m[root->right]=root;
rotate_left(root->left);
rotate_right(root->right);
// 左树为空,不需要旋转
if(root->left==nullptr){
root->left=m[root];
return;
}
// 左树不为空
Node* temp_left=root->left;
while(temp_left->left!=nullptr){
temp_left=temp_left->left;
}
// 对称
temp_left->left=m[root];
m[root]->right=temp_left;
root->left->right=root;
return ;
}
```
### 算法设计
* 按中序遍历输出链表。
* 那么就用中序遍历的前一个节点指向本节点。
### 算法分析
* 时间复杂度O(n)
* 空间复杂度O(1)
### 算法实现
```
public:
Node* treeToDoublyList(Node* root) {
if(root == nullptr) return nullptr;
dfs(root);
head->left = pre;
pre->right = head;
return head;
}
private:
Node *pre, *head;
void dfs(Node* cur) {
if(cur == nullptr) return;
dfs(cur->left);
if(pre != nullptr) pre->right = cur;
else head = cur;
cur->left = pre;
pre = cur;
dfs(cur->right);
}
```

View File

@@ -0,0 +1,57 @@
# 数组与滑动窗口
## 1.2 最长不含重复字符的子字符串——滑动窗口
### 问题描述
* 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
### 问题分析
### 问题分类
### 算法设计
* 哈希表 dicdic 统计: 指针 jj 遍历字符 ss ,哈希表统计字符 s[j]s[j] 最后一次出现的索引 。
* 更新左指针 ii 根据上轮左指针 ii 和 dic[s[j]]dic[s[j]] ,每轮更新左边界 ii ,保证区间 [i + 1, j][i+1,j] 内无重复字符且最大。
$$
i=max(dic[s[j]],i)
$$
* 更新结果 res 取上轮 res 和本轮双指针区间 [i + 1,j][i+1,j] 的宽度(即 j - iji )中的最大值
$$
res=max(res,ji)
$$
![](image/2021-03-19-01-02-12.png)
### 算法分析
* 时间复杂度 O(N) 其中 N为字符串长度动态规划需遍历计算 dpdp 列表。
* 空间复杂度 O(1) 字符的 ASCII 码范围为 00 ~ 127哈希表 dic最多使用 O(128) = O(1) 大小的额外空间。
### 算法分析
* 时间复杂度 O(N)O(N) 其中 NN 为字符串长度,动态规划需遍历计算 dpdp 列表。
* 空间复杂度 O(1)O(1) 字符的 ASCII 码范围为 00 ~ 127127 ,哈希表 dicdic 最多使用 O(128) = O(1)O(128)=O(1) 大小的额外空间。
### 算法实现
```C++
int lengthOfLongestSubstring(string s) {
vector<int> a(200,-1);
int repeat=-1;
int max_val=0;
for(int i=0;i<s.size();i++){
repeat=max(a[s[i]],repeat);
max_val=max(i-repeat,max_val);
a[s[i]]=i;
}
return max_val;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -109,4 +109,38 @@
}
return 0;
}
```
```
## 2 数组中超过一半的数
### 问题描述
* 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
* [链接](https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/)
### 问题分析
### 问题分类
### 策略选择
* 数论知识补充
* 推论一: 若记 众数 的票数为 +1+,非众数 的票数为 -1 ,则一定有所有数字的 票数和 > 0 。
* 推论二: 若数组的前 a 个数字的 票数和 =0 ,则 数组剩余 (na) 个数字的 票数和一定仍 >0 ,即后 (na) 个数字的 众数仍为 x
![](image/2021-03-19-00-19-33.png)
### 算法设计
* 摩尔投票法
1. 初始化: 票数统计 votes = 0 众数 x
2. 循环: 遍历数组 nums 中的每个数字 num
1. 当 票数 votes 等于 0 ,则假设当前数字 num 是众数;
2. 当 num = x 时,票数 votes 自增 1 ;当 num != x 时,票数 votes 自减 1
3. 返回值: 返回 x 即可;
### 算法分析
* 时间复杂度 O(N)O(N) NN 为数组 nums 长度。
* 空间复杂度 O(1)O(1) votes 变量使用常数大小的额外空间。
### 算法实现

View File

@@ -0,0 +1,124 @@
## 1 把数组排成最小数
### 问题描述
* 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
* 示例 1:
* 输入: [10,2]
* 输出: "102"
* 示例 2:
* 输入: [3,30,34,5,9]
* 输出: "3033459"
[链接](https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof)
### 问题分析
### 问题分类
* 排序
### 算法设计
* 算法原理此题求拼接起来的最小数字本质上是一个排序问题。设数组nums 中任意两数字的字符串为 x 和 y ,则规定 排序判断规则 为:
* 若拼接字符串 x+y>y+x ,则 x “大于” y
* 反之,若 x+y<y+x ,则 x “小于” y
* 算法流程
* 初始化: 字符串列表 strsstrs ,保存各数字的字符串格式;
* 列表排序: 应用以上 “排序判断规则” ,对 strsstrs 执行排序;
* 返回值: 拼接 strsstrs 中的所有字符串,并返回。
### 算法分析
* 时间复杂度O(NlogN) N 为最终返回值的字符数量( strs列表的长度 N≤N );使用快排或内置函数的平均时间复杂度为 O(NlogN) ,最差为 O(N^2)
* 空间复杂度 O(N)O(N) 字符串列表 strsstrs 占用线性大小的额外空间。
### 算法实现
* 以下是自己的方法,循环比较
```C++
string minNumber(vector<int>& nums) {
vector<string> vec;
for(auto a:nums){
vec.push_back(to_string(a));
}
// 太他妈的秀了。这个循环比较。我是服了。
// 对compare函数有了更深的理解。return true表示第一个参数需要移到第二个参数前。
// return false。表示第一个参数不需要移到第二个参数前。
// 也就是说默认情况下第一个参数在第二个参数后边。true表示需要移动。false表示不需要移动。
// 还是官方英文的参考文档说的比较明白
sort(vec.begin(),vec.end(),[](const string&a,const string&b){
int i=0,j=0,k=0;
// return false;
// 完全相等的情况
if(a==b)return false;
// cout<<a<<" "<<b<<endl;
while(true){
k++;
if(k>2*a.size()&&k>2*b.size())return false;
// cout<<i<<" "<<j<<endl;
if(a[i]<b[j])return true;
else if(a[i]>b[j])return false;
else if(a[i]==b[j]){
i=(i+1)%a.size();
j=(j+1)%b.size();
}
}
});
string result;
for(auto s:vec){
result+=s;
}
return result;
}
```
* 一下是官方的方法。手写快排
```C++
class Solution {
public:
string minNumber(vector<int>& nums) {
vector<string> strs;
for(int i = 0; i < nums.size(); i++)
strs.push_back(to_string(nums[i]));
quickSort(strs, 0, strs.size() - 1);
string res;
for(string s : strs)
res.append(s);
return res;
}
private:
void quickSort(vector<string>& strs, int l, int r) {
if(l >= r) return;
int i = l, j = r;
while(i < j) {
while(strs[j] + strs[l] >= strs[l] + strs[j] && i < j) j--;
while(strs[i] + strs[l] <= strs[l] + strs[i] && i < j) i++;
swap(strs[i], strs[j]);
}
swap(strs[i], strs[l]);
quickSort(strs, l, i - 1);
quickSort(strs, i + 1, r);
}
};
```
* 一下是官方的方法,内置函数
```
class Solution {
public:
string minNumber(vector<int>& nums) {
vector<string> strs;
string res;
for(int i = 0; i < nums.size(); i++)
strs.push_back(to_string(nums[i]));
sort(strs.begin(), strs.end(), [](string& x, string& y){ return x + y < y + x; });
for(int i = 0; i < strs.size(); i++)
res.append(strs[i]);
return res;
}
};
```

View File

@@ -0,0 +1,40 @@
## 字符串排列
* 递归、回溯。
* 判断相同字符的选择
* 时间复杂度O(n!)
* 空间复杂度O(n2)
```C++
vector<string> permutation(string s) {
vector<string> vec;
string pre;
perm(pre,s,vec);
return vec;
}
void perm(string pre,string s,vector<string> &vec){
if(s.size()==0){
// cout<<pre<<endl;
vec.push_back(pre);
return;
}
set<char> se;
for(int i=0;i<s.size();i++){
string temp = s;
if(se.count(s[i])){
continue;
}
else{
se.insert(s[i]);
}
perm(pre+s[i],temp.erase(i,1),vec);
}
return ;
}
```

View File

@@ -0,0 +1,50 @@
## 1 1n 整数中 1 出现的次数
### 问题描述
* 输入一个整数 n 求1n这n个整数的十进制表示中1出现的次数。例如输入12112这些整数中包含1 的数字有1、10、11和121一共出现了5次。
[链接](https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof)
### 问题分析
### 问题分类
* 数值问题
### 策略选择
### 算法设计
### 算法分析
* 时间复杂度 O(logn) 循环内的计算操作使用 O(1)时间;循环次数为数字 n 的位数即logn 因此循环使用O(logn)时间。
* 空间复杂度 O(1)O(1) 几个变量使用常数大小的额外空间。
### 算法实现
```C++
int countDigitOne(int n) {
int i=0;
int wei=0;
int result=0;
int end=0;
while(n!=0){
wei=n%10;
i++;
if(wei>1){
result+=(i-1)*wei*pow(10,i-2)+pow(10,i-1);
}
else if(wei==1){
result+=(i-1)*wei*pow(10,i-2)+end+1;
}
end+=wei*pow(10,i-1);
// cout<<end<<endl;
n/=10;
}
return result;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB