字典序最大问题
问题描述
- 给定一个1到n的排列(无序状态的),依次插入到栈中,在每时每刻都可以多次从栈中弹出栈顶
- 问:应如何使得弹出栈顶的序列的字典序最大,并输出这个序列
问题分析
- 什么是字典序
- 比如两个序列:5,4,3,2,1和1,2,3,4,5
- 这两个中第一个的字典序最大
- 每个位置上的数字比其他序列上同一位置的数字要大
- 比如:第一位置中5比1大,第二位置中4比2大这样
- 即:字典序最大 = 从左向右看这个序列,每个数字都尽量大
- 题目给定一个1到n的排列,每个数字只出现一次
- 要使出栈的字典序最大,则第一个出栈的一定是n, 也就是序列中的最大值,同时n的入栈和出栈动作一定是连续的,不会被其他元素所干扰
- 第二个出栈的会是谁呢?
- 在n出栈之前,n前面的所有元素都一定在栈中,没有一个被弹出去,否则第一个弹出的位置就不会是n
- 由于每次只能弹出栈顶,我们只需考虑栈顶的数字和n这个数字后面的元素中选一个最大值作为第二个出栈的候选者
- 第三、第四等等以此类推
- 总结一下,这个算法实现需要三个功能
- 栈的基本操作
- 连续一段(至末尾)序列最大值的查找
- 判断谁是下一个出栈:当前栈顶与前一个出栈元素后面的元素中最大的那个
关键算法实现
1 ) 蛮力算法:算法复杂度为:O(n2)O(n^2)O(n2)
- 每次花费O(n)的时间查找最大值
- 一共进行了O(n)次
- 算法复杂度为:O(n2)O(n^2)O(n2)
// n: 序列长度
// ele:序列数组,下标从0开始
// 返回值:返回字典序最大的出栈序列
vector<int> getResult(int n, int *ele) {
vector<int> ans; // 动态数组,用于返回
stack<int> s; // 用于存放索引的栈
// 处理当前所有的元素
for (int i = 0; i < n;) {
int mx = 0; // 初始化最大值
// 找到当前最大值(并非全局最大值,第一次循环的时候是全局最大值)
for (int j = i; j < n; ++j) {
mx = max(mx, ele[j]);
}
// 如果栈不空且栈顶元素较大,则将较大的栈顶弹出(满足字典序最大)
if(!s.empty() && ele[s.top()] > mx) {
ans.push_back(ele[s.top()]); // 将这个较大栈顶存入结果
s.pop(); // 出栈
} else {
// 将最大值之外的元素全部压进去(因为最大值只有一个,所以用!=的方式处理除了最大值之外的所有元素)
while(mx!=ele[i]) {
s.push(i); // 入栈
++i; // i自增跳出while,重新执行外层循环
}
// 当前的i元素ele[i]就是最大值的时候,将这个最大值存入结果
ans.push_back(ele[i]);
++i;
}
}
// 当外面的元素全都没有了(该入栈的都入栈了),最后只能从栈中一个一个弹出来了,将剩余元素依次弹出,并存入最终结果
while(!s.empty()) {
ans.push_back(ele[s.top()]);
s.pop();
}
return ans;
}
2 ) 线性算法:算法复杂度为:O(n)O(n)O(n)
- 维护一个数组maxElement,表示位置i到位置n的最大值
- maxElementi=max{maxElementi+1,elementi}maxElement_i = max\{ maxElement_{i+1}, element_i \}maxElementi=max{maxElementi+1,elementi}
- 算法开始之前做数据的预处理,时间复杂度为O(n)
- 之后,算法查询O(n)次,每次花费O(1)的时间来查询最大值,结合之前预处理时间也是O(n), 所以总时间复杂度还是O(n)
- 以空间换时间,妥妥的线性算法, 非常高效
// n: 序列长度
// ele:序列数组,下标从0开始
// 返回值:返回字典序最大的出栈序列
vector<int> getResult(int n, int *ele) {
vector<int> ans; // 动态数组,用于返回
stack<int> s; //
// ----- 数据预处理 开始 ----- //
int *maxElement = new int[n](); // 定义一个存储所有元素的空间
maxElement[n-1] = ele[n-1]; // 初始化最后一个元素,作为切入点
// 批量处理其他元素,注意,此处是倒序,最大值的比较是当前元素和前一个最大值(序号较大的,因为是倒序循环)
for(int i=n-2; i>=0; --i) {
maxElement[i] = max(maxElement[i+1], ele[i]);
}
// ----- 数据预处理 结束 ----- //
// 遍历当前所有的元素 O(n)
for (int i = 0; i < n;) {
int mx = maxElement[i]; // 初始化最大值
// 如果栈不空且栈顶元素较大,则将较大的栈顶弹出(满足字典序最大)
if(!s.empty() && ele[s.top()] > mx) {
ans.push_back(ele[s.top()]); // 将这个较大栈顶存入结果
s.pop(); // 出栈
} else {
// 将最大值之外的元素全部压进去(因为最大值只有一个,所以用!=的方式处理除了最大值之外的所有元素)
while(mx!=ele[i]) {
s.push(i); // 入栈
++i; // i自增跳出while,重新执行外层循环
}
// 当前的i元素ele[i]就是最大值的时候,将这个最大值存入结果
ans.push_back(ele[i]);
++i;
}
}
// 当外面的元素全都没有了(该入栈的都入栈了),最后只能从栈中一个一个弹出来了,将剩余元素依次弹出,并存入最终结果
while(!s.empty()) {
ans.push_back(ele[s.top()]);
s.pop();
}
return ans;
}
3 ) 其他复杂度算法:O(nlogn)O(nlogn)O(nlogn)
- 既然解决方案可以从O(n2) O(n)O(n^2) ~ O(n)O(n2) O(n), 那么O(nlogn)的复杂度也可以实现
- 每一次查询只会花费O(logn)的时间,构建以及其他零散总时间也不会超过O(nlogn)
- 可以参考:线段树,堆,平衡树来考虑,此处不再赘述
- 具体替换的位置在下面,替换上上面的一些技术来做查询
// 找到当前最大值(并非全局最大值,第一次循环的时候是全局最大值) for (int j = i; j < n; ++j) { mx = max(mx, ele[j]); }
通用的输入程序
int main() {
int n;
cin >> n;
int *ele = new int[n](); // 输入的数组
// 初始化
for (int i = 0; i < n; ++i) {
cin >> ele[i];
}
auto answer = getResult(n, ele);
// 循环输出数组
for (auto i: answer) {
cout << i << " ";
}
cout << "\n";
return 0;
}
测试数据
- 给定输入序列
- 5
- 4 1 3 5 2
- 输出序列:5 3 2 1 4
扩展
- 当这个序列不是逐个排列给出,而是允许有重复元素,上述算法也可正常运行
本文探讨了如何在给定1到n的排列中,通过栈操作生成字典序最大的出栈序列。介绍了蛮力算法、线性算法及O(nlogn)算法,详细解析了算法实现与优化。
3264

被折叠的 条评论
为什么被折叠?



