题目:
之前刷的题里面有相似的,比如说移掉K位数字(笔记7),下一个排列数(笔记2)。所以,今天这道题说要在两个数组中找出最大排列的时候,我第一个想到的方法就是利用栈来处理。但是思考了很久,也不知道怎么组织两个栈之间的关系,后来根据题解,才完全弄清楚。
为什么要这么做?
我觉得我们做题,不只是要弄懂它的方法,还要尽量去想,为什么能想到这个做法。
1.首先呢,根据以往的经验,用栈来处理这种最大排列,最大子序列的问题是最方便的,因为它可以比较栈顶数字和下一个数字的大小,来选择性的入栈和出栈。
比较栈顶数字和下一个数字的大小一般都是从数学的角度分析的,比如移除K位数字的那题,从数学角度上来说,越靠前的数字越大影响力越大,那么我们就找到第一个非降序的数字,然后不断弹出栈顶直到可以继续构成降序序列为止。
同样,这道题我们也有相同的思考方式。
首先这个数组是由两个数组的数字构成,那么我们就要先去找出这两个数组的最大子序列(可以用反证法证明)。
找最大子序列毫无疑问使用栈会方便很多,但是还有一个问题,就是这两个数组每个数组出多少个元素呢?
所以,这就需要对每一种情况都进行考虑。假如数组nums1出X个,nums2就出K-X个。
2.找到最大子序列之后,怎么拼接呢?
我们遵循两条原则:
第一,如果当前元素不同,理所当然应当选择大的那个入队。
第二,如果当前元素相同,则需要去找到它们后面第一个不相同的元素对,去判定大小,然后将较大的那个数组的当前元素入队。
操作完成以后,有元素入队的那个数组指针自加。
3.所以我们就有了三大步
第一步,对每一种数组个数的选择(x+y=k),用栈找出最大子序列
第二步,按规则拼接元素
第三步,找出最大的那一个拼接起来的元素
然后,就可以去写算法代码了。
这里我引用的是官方题解的代码,因为我自己写的太臃肿了,题解的比较精炼,所有注释我都加好了,一些代码细节还需要一定的理解。
比如怎么保证最大子序列返回的元素个数。
比如怎么让拼接操作较长的那个数组将尾部数字逐一拼接。
题解的代码十分简洁而且逻辑性很强,所以我在这里贴出题解的代码附带详细注释:
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
int m = nums1.size(), n = nums2.size();//m,n分别是nums1和nums2的长度
vector<int> maxSubsequence(k, 0);//返回的答案数组
int start = max(0, k - n), end = min(k, m);//需要得到 x+y=k所有可能对应的值 这种写法还顺带判断了数组大小的极限情况
for (int i = start; i <= end; i++) {
vector<int> subsequence1(MaxSubsequence(nums1, i));//得到最大子序列1
vector<int> subsequence2(MaxSubsequence(nums2, k - i));//得到最大子序列2 ,序列1,2,都需要满足当前指定的长度
vector<int> curMaxSubsequence(merge(subsequence1, subsequence2));
if (compare(curMaxSubsequence, 0, maxSubsequence, 0) > 0) {
maxSubsequence.swap(curMaxSubsequence);//选择较大的那个序列
}
}
return maxSubsequence;
}
vector<int> MaxSubsequence(vector<int>& nums, int k) {//其实是个最大子序列的问题,返回一个栈底到栈顶的递减栈
int length = nums.size();//获得长度
vector<int> stack(k, 0);//创建0栈,长度为k
int top = -1;//栈顶指针
int remain = length - k;//会剩下的数字个数,保证返回的栈满足要求
for (int i = 0; i < length; i++) {
int num = nums[i];
while (top >= 0 && stack[top] < num && remain > 0) {//当前元素大于栈顶元素时,且剩余元素充足
top--;
remain--;//弹出元素,直到栈顶元素大于等于num
}
if (top < k - 1) {//入栈
stack[++top] = num;
} else {
remain--;
}
}
return stack;//返回栈
}
vector<int> merge(vector<int>& subsequence1, vector<int>& subsequence2) {//合并两个单调栈
int x = subsequence1.size(), y = subsequence2.size();
if (x == 0) {
return subsequence2;
}
if (y == 0) {
return subsequence1;
}
int mergeLength = x + y;//融合后的长度
vector<int> merged(mergeLength);//创建融合序列
int index1 = 0, index2 = 0;
for (int i = 0; i < mergeLength; i++) {
if (compare(subsequence1, index1, subsequence2, index2) > 0) {
merged[i] = subsequence1[index1++];//subsequence1的当前元素比较大
} else {
merged[i] = subsequence2[index2++];//subsequence1的当前元素比较小
}
}
return merged;
}
int compare(vector<int>& subsequence1, int index1, vector<int>& subsequence2, int index2) {
int x = subsequence1.size(), y = subsequence2.size();
while (index1 < x && index2 < y) {//越界判断
int difference = subsequence1[index1] - subsequence2[index2];//判断当前两个元素是否相同
if (difference != 0) {//如果不同
return difference;//返回差值
}
index1++;//相同的话则往后找
index2++;//相同的话则往后找
}
return (x - index1) - (y - index2);//如果index1等于x,返回负数,如果index2等于y,返回正数,等于0则说明长度相同
}
};
int main()
{
vector<int> a={3, 4, 6, 5};
vector<int> b={9, 1, 2, 5, 8, 3};
int k = 5;
Solution s;
vector<int> ans = s.maxNumber(a,b,k);
for(int num:ans)
{
cout<<num<<endl;
}
}
最后,多提一句!
这种涉及到数组某种最大或者最小排序的题,一定要记得考虑栈的数据结构。因为它真的很好使!