diff --git a/C++/面试/18 数组离散化的方法.md b/C++/面试/18 数组离散化的方法.md new file mode 100644 index 00000000..f2ae0b72 --- /dev/null +++ b/C++/面试/18 数组离散化的方法.md @@ -0,0 +1,52 @@ +# 数组离散化 + +## 1 问题描述 + + +* 离散化一个序列的前提是我们只关心这个序列里面元素的相对大小,而不关心绝对大小(即只关心元素在序列中的排名);离散化的目的是让原来分布零散的值聚集到一起,减少空间浪费。那么如何获得元素排名呢,我们可以对原序列排序后去重,对于每一个$a_i$通过二分查找的方式计算排名作为离散化之后的值。当然这里也可以不去重,不影响排名。 +* 数组本质上是一种有序的映射。i----A---->x 即(A[i]=x)的映射。有时候为了建立值的数量与坐标i的反向映射,但此时值x的范围是离散化的。需要建立离散数组。一个的额外的映射数组,完成反向映射x----B---->i。 + +## 2 离散数组——数组实现方法 + +* 使用数组B作为离散数组的映射。B的下标i能够反映原来数组中各个元素的大小顺序。但不是原来数组中各个元素的值。 +* 建立离散数组的时间复杂度为O(nlog n)需要对原数组进行快速排序。 + +![](image/2021-03-27-04-40-18.png) + +代码实现 +```C++ +vector nums;//表示原来的数组 +vector tmp = nums; + +//对离散数组进行排序 +sort(tmp.begin(), tmp.end()); + +//建立原数组到离散数组的映射 +for (int& num: nums) { + num = lower_bound(tmp.begin(), tmp.end(), num) - tmp.begin() + 1; +} +``` + +## 3 离散数组——有序map实现方法 + +* 有序map的创建,底层使用红黑树实现。本质上就是一种离散数组。根据键的大小进行了排序。 +* 可以使用这种有序的映射直接完成原数组的值到下表i的映射。并且保持一定的顺序。 +``` +#include +#include +using namespace std; + + +int main(){ + map m; + m[10]=10; + m[4]=4; + m[5]=5; + m[1000]=1000; + m[1]=1; + for(auto k:m){ + cout< +#include +using namespace std; + + +int main(){ + map m; + m[10]=10; + m[4]=4; + m[5]=5; + m[1000]=1000; + m[1]=1; + for(auto k:m){ + cout< T[i]){ + ​stk.pop(); + } + stk.push(A[i]); +} +``` + +* 单调递减站 + +``` +for(int i = T.size() - 1; i >= 0; i--){ + while(! stk.empty() && T[i] >= stk.top()){ + stk.pop(); + } + stk.push(i); +} +``` diff --git a/数据结构/6.11 树状数组.md b/数据结构/6.11 树状数组.md index 4be79de6..1aa498ab 100644 --- a/数据结构/6.11 树状数组.md +++ b/数据结构/6.11 树状数组.md @@ -54,12 +54,13 @@ $$ ## 3 应用 -### 把位置x的元素都加k +### 把位置k的元素加x -* 我们每次执行操作A(把位置x的值+k),只需要把"能管理到x的所有位置"都+k就行 - 1. A[i]+k - 2. i=i+lowbit(i) - 3. 重复以上步骤,直到i>max截止 +* 我们每次执行操作A(把位置k的值+x),只需要把"能管理到k的所有位置"都+x就行 + 1. i=k + 2. A[i]+x + 3. i=i+lowbit(i) + 4. 重复2-3步骤,直到i>max截止 ### 求前缀区间的值 @@ -79,13 +80,26 @@ $$ sum(L,R)=sum(1,R)-sum(1,L) $$ +### 创建树状数组 + +* 将树状数组初始化为0.依次添加位置k的元素添加x。创建数组的时间复杂度为O(nlog n) + + ## 实例1 基本运算 ```C++ //求最小幂2^k: int Lowbit(int t) { - return t & ( t ^ ( t - 1 ) ); + return t & ( t ^ ( t - 1 ) );//把最后一个1变为0,然后再按位与。 + //return t&(-t) + //return t&(~t+1) +} + +// 创建树状数组 + +int tree_array(){ + for(int i=1;i<) } //求前n项和: diff --git a/算法/A类:基本算法/3.9 位运算算法.md b/算法/A类:基本算法/3.9 位运算算法.md index 74d059f8..fb84acbb 100644 --- a/算法/A类:基本算法/3.9 位运算算法.md +++ b/算法/A类:基本算法/3.9 位运算算法.md @@ -18,6 +18,7 @@ |-----| -----| n & (n - 1) | n中的最后一个1变成0。 n & (\~n + 1) | lowbit()运算,n中最后一个1保留。 +(\~n) + 1== -n | 计算机中补码表示的-n。位运算取反 n/2 | 等价于 右移一位 n >> 1 n*2 | 等价于 左移一位 n << 1 n % 2 |等价于 判断二进制最右一位值 n \& 1 diff --git a/算法/B类:数据结构算法/3.1 单调栈-局部顺序.md b/算法/B类:数据结构算法/3.1 单调栈-局部顺序.md new file mode 100644 index 00000000..6ef41eed --- /dev/null +++ b/算法/B类:数据结构算法/3.1 单调栈-局部顺序.md @@ -0,0 +1,253 @@ +# 单调栈 + +* 用来解决**局部顺序问题** +## 1 视野总和 + +### 问题描述 + +* 有n个人站队,所有的人全部向右看,个子高的可以看到个子低的发型,给出每个人的身高,问所有人能看到其他人发现总和是多少。 +``` +输入:4 3 7 1 +输出:2 +解释:个子为4的可以看到个子为3的发型,个子为7可以看到个子为1的身高,所以1+1=2 +``` +* []() +### 问题分析 + +* 问题分类: +* 思路:观察题之后,我们发现实际上题目转化为找当前数字向右查找的第一个大于他的数字之间有多少个数字,然后将每个          结果累计就是答案,但是这里时间复杂度为O(N^2),所以我们使用单调栈来解决这个问题。 + +### 策略选择 +* 数据结构:单调栈 +* 算法思想:局部降序统计 + +### 算法设计 + +1. 设置一个单调递减栈 +2. 当入栈的的时候,栈内的人肯定能看到该元素。 + +### 算法分析 + +* 时间复杂度为O(N) +* 空间复杂度O(N) + + +### 算法实现 + +```C++ +int FieldSum(vector& v) +{ + stack s; + int sum = 0; + for (int i = 0; i < (int)v.size(); i++) + { + //除去违反规则的人 + while (!st.empty() && v[st.top()] <= v[i])s.pop(); + // 增加能看到该元素的人 + sum+=s.size(); + st.push(i); + } + return sum; +} +``` + + +## 2 柱状图中的最大矩形 + +### 问题描述 + +![](image/2021-03-27-05-15-13.png) + +### 问题分析 + + +### 策略选择 + +* 思路:当前的数字可以向两边拓展,遇到比自己大的就接着拓展,小的就停止,然后用自己的高度乘以拓展的宽度,每次更新最大面积,时间复杂度同样为O(N^2),所以我们接着借助单调栈 + +### 算法设计 + +1. 设置一个单调递增栈 +2. 当遇到小于栈顶元素的值,我们开始更新数据,因为有可能最大面积就会出现在栈中的序列里 +3. 牢记栈中数据永远是有序的,这个问题比较复杂,所以读者不妨对照着代码来理解问题 +### 算法分析 + +* 时间复杂度为O(N) +* 空间复杂度O(N) + +### 算法实现 + +``` +int largestRectangleArea(vector& heights) { + heights.push_back(-1);/同理,我们希望栈中所有数据出栈,所以给数组最后添加一个负数 + stack st; + int ret = 0, top; + for (int i = 0; i < heights.size(); i++) + { + if (st.empty() || heights[st.top()] <= heights[i]) + { + st.push(i); + } + else + { + while (!st.empty() && heights[st.top()] > heights[i]) + { + top = st.top(); + st.pop(); + //i-top指的是当前矩形的宽度,heights[top]就是当前的高度 + //再次强调栈中现在为单调递增 + int tmp = (i - top)*heights[top]; + if (tmp > ret) + ret = tmp; + } + st.push(top); + heights[top] = heights[i]; + } + } + return ret; +} +``` +## 3 求最大区间 + +### 问题描述 + +* 描述:给出一组数字,求一区间,使得区间元素和乘以区间最小值最大,结果要求给出这个最大值和区间的左右端点 +``` +输入:3 1 6 4 5 2 +输出:60 +       3 5 +``` +* 解释:将3到5(6+4+5)这段区间相加,将和与区间内最小元素相乘获得最大数字60 + +### 问题分析 + +### 策略选择 + +* 思路:使用暴力解法求出所有区间,再求出区间的最小值相乘跟新数据,并不是一种很好的算法,所以经过上面俩题的磨         炼,此时我们应该使用一个单调递减栈 + +### 算法设计 + +1. 设置一个单调递增栈 +2. 当遇到小于栈顶元素的值,我们开始更新数据,因为当前遇到的值一定是当前序列最小的 + + +### 算法分析 + +* 时间复杂度O(n) +* 空间复杂度O(n) +### 算法实现 + +```C++ +int GetMaxSequence(vector& v) +{ + stack st; + vector vs(v.size()+1); + vs[0] = 0; + for (int i = 1; i < vs.size(); i++) + { + vs[i] = vs[i - 1] + v[i-1]; + } + v.push_back(-1); + int top, start, end, ret = 0; + for (int i = 0; i < v.size(); i++) + { + if (st.empty() || v[st.top()] <= v[i]) + { + st.push(i); + } + else + { + while (!st.empty() && v[st.top()] > v[i]) + { + top = st.top(); + st.pop(); + int tmp = vs[i] - vs[top]; + tmp = tmp * v[top]; + if (tmp > ret) + { + ret = tmp; + start = top+1; + end = i; + } + } + st.push(top); + v[top] = v[i];//与第二题相同的道理,将当前数据的更改最左的top下标,防止出现比当前数据更小的数据 + //这句在这道题里真的超级难理解,但是只要你有耐心相信你可以理解的 + } + } + return ret +} +``` +## 4 132 模式 + +### 问题描述 + +* 给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。 + +* 如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。 + +* 进阶:很容易想到时间复杂度为 O(n^2) 的解决方案,你可以设计一个时间复杂度为 O(n logn) 或 O(n) 的解决方案吗? + + + +* 示例 1: +``` +输入:nums = [1,2,3,4] +输出:false +解释:序列中不存在 132 模式的子序列。 +``` +* 示例 2: +``` +输入:nums = [3,1,4,2] +输出:true +解释:序列中有 1 个 132 模式的子序列: [1, 4, 2] 。 +``` +### 问题分析 + +### 策略选择 + + +### 算法设计 + +* 因此,我们可以使用单调栈作为维护 22 的数据结构,并给出下面的算法: + +* 我们用单调栈维护所有可以作为 22 的候选元素。初始时,单调栈中只有唯一的元素 \textit{a}[n-1]a[n−1]。我们还需要使用一个变量 \textit{max\_k}max_k 记录所有可以真正作为 22 的元素的最大值; + +* 随后我们从 n-2n−2 开始从右到左枚举元素 a[i]a[i]: + + * 首先我们判断 a[i]a[i] 是否可以作为 11。如果 a[i] < \textit{max\_k}a[i]& nums) { + stack s; + int mask2=INT_MIN; + // 找比它大的第一个数。单调递减栈。大的pop出去 + for(int i=nums.size()-1;i>=0;i--){ + if(nums[i]s.top()){ + if(mask2