diff --git a/算法/A类:基本算法/1 算法概述.md b/算法/A类:基本算法/1 算法概述.md index ad8cc33a..dd2426d3 100644 --- a/算法/A类:基本算法/1 算法概述.md +++ b/算法/A类:基本算法/1 算法概述.md @@ -44,7 +44,7 @@ 1. 选择合适的**数据结构** 2. 选择合适的**算法思想** 3. **算法设计** - 1. 确定**算法技术**,递归技术、循环技术、位运算技术 + 1. 确定**算法原理**,递归技术、循环技术、位运算技术 2. 设计**算法流程**,数学递推关系和伪代码 4. **正确性证明** 1. 查看伪代码的流程的正确性,手推算法过程。 diff --git a/算法/A类:基本算法/5.6 最大子段和问题.md b/算法/A类:基本算法/5.6 最大子段和问题.md index e69de29b..f0344a22 100644 --- a/算法/A类:基本算法/5.6 最大子段和问题.md +++ b/算法/A类:基本算法/5.6 最大子段和问题.md @@ -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[i−1]≤0 ,说明 dp[i - 1]dp[i−1] 对 dp[i]dp[i] 产生负贡献,即 dp[i-1] + nums[i]dp[i−1]+nums[i] 还不如 nums[i]nums[i] 本身大。 + * 当 dp[i - 1] > 0dp[i−1]>0 时:执行 dp[i] = dp[i-1] + nums[i]dp[i]=dp[i−1]+nums[i] + * 当 dp[i - 1] \leq 0dp[i−1]≤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& nums) { + //动态规划有很多不同的方向。例如,这里把动态变化定位不断增加的连续数组的长度。 + // 当连续数组的长度为1,2,3,4,5时,会利用之前计算的结果。求解。但是这很暴力。 + // 相当于求出了当前的所有最长子数组的解。把问题的规模缩小。 + // nums的长度。动态变化。 + + // 失败的动态规划,实际上是暴力求解。 + // vector> vec; + // int max=-999999; + // vec.push_back(vector()); + // for(int i=0;imax){ + // max=nums[i]; + // } + // } + // for(int i=1;i()); + // for(int j=0;jmax){ + // max=vec[i][j]; + // } + // } + // } + + // 正常的动态规划 + int max=nums[0]; + vector vec; + vec.push_back(nums[0]); + for(int i=1;i nums[i]){ + vec.push_back(temp); + } + else{ + vec.push_back(nums[i]); + } + if(vec[i]>max){ + max=vec[i]; + } + } + return max; + } +``` \ No newline at end of file diff --git a/算法/A类:基本算法/image/2021-03-19-00-24-38.png b/算法/A类:基本算法/image/2021-03-19-00-24-38.png new file mode 100644 index 00000000..915a38e4 Binary files /dev/null and b/算法/A类:基本算法/image/2021-03-19-00-24-38.png differ diff --git a/算法/B类:数据结构算法/2.1 树的遍历.md b/算法/B类:数据结构算法/2.1 树的遍历.md new file mode 100644 index 00000000..12c6ad88 --- /dev/null +++ b/算法/B类:数据结构算法/2.1 树的遍历.md @@ -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/) + + diff --git a/算法/B类:数据结构算法/2.2 二叉搜索树与双向链表.md b/算法/B类:数据结构算法/2.2 二叉搜索树与双向链表.md new file mode 100644 index 00000000..41bdbc4f --- /dev/null +++ b/算法/B类:数据结构算法/2.2 二叉搜索树与双向链表.md @@ -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 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); + } +``` + diff --git a/算法/B类:数据结构算法/4.3 数组与滑动窗口.md b/算法/B类:数据结构算法/4.3 数组与滑动窗口.md new file mode 100644 index 00000000..da184c8b --- /dev/null +++ b/算法/B类:数据结构算法/4.3 数组与滑动窗口.md @@ -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 - ij−i )中的最大值 + +$$ +res=max(res,j−i) +$$ +![](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 a(200,-1); + int repeat=-1; + int max_val=0; + for(int i=0;i 0 。 +* 推论二: 若数组的前 a 个数字的 票数和 =0 ,则 数组剩余 (n−a) 个数字的 票数和一定仍 >0 ,即后 (n−a) 个数字的 众数仍为 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 变量使用常数大小的额外空间。 + +### 算法实现 \ No newline at end of file diff --git a/算法/C类:问题类型算法/2.1 数组排成最小数.md b/算法/C类:问题类型算法/2.1 数组排成最小数.md new file mode 100644 index 00000000..573f532e --- /dev/null +++ b/算法/C类:问题类型算法/2.1 数组排成最小数.md @@ -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& nums) { + vector 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<2*a.size()&&k>2*b.size())return false; + // cout<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& nums) { + vector 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& 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& nums) { + vector 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; + } +}; + +``` \ No newline at end of file diff --git a/算法/C类:问题类型算法/5.1 字符串排列.md b/算法/C类:问题类型算法/5.1 字符串排列.md new file mode 100644 index 00000000..b93f39e7 --- /dev/null +++ b/算法/C类:问题类型算法/5.1 字符串排列.md @@ -0,0 +1,40 @@ + + +## 字符串排列 + +* 递归、回溯。 +* 判断相同字符的选择 + +* 时间复杂度O(n!) +* 空间复杂度O(n2) + + +```C++ + vector permutation(string s) { + vector vec; + string pre; + perm(pre,s,vec); + return vec; + } + + void perm(string pre,string s,vector &vec){ + if(s.size()==0){ + // cout< se; + for(int i=0;i1){ + 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<