962. 最大宽度坡
给定一个整数数组
A
,坡是元组(i, j)
,其中i < j
且A[i] <= A[j]
。这样的坡的宽度为j - i
。找出
A
中的坡的最大宽度,如果不存在,返回 0 。示例 1:
输入:[6,0,8,2,1,5] 输出:4 解释: 最大宽度的坡为 (i, j) = (1, 5): A[1] = 0 且 A[5] = 5.
示例 2:
输入:[9,8,1,0,1,9,4,0,4,1] 输出:7 解释: 最大宽度的坡为 (i, j) = (2, 9): A[2] = 1 且 A[9] = 1.
提示:
2 <= A.length <= 50000
0 <= A[i] <= 50000
1.暴力解法,时间超时
暴力的思路是,用一个po_len数组存储每一个元素对应的最大宽度的坡.
然后遍历po_len找到最大值即可.
class Solution {
public:
vector<int> nums; // 用于存储输入的整数数组A
int n; // 数组的长度
int ret; // 用于存储最终的最大宽度坡的宽度
vector<int> po_len; // 用于存储每个元素对应的最大宽度坡的长度信息
// 初始化函数
void init() {
n = nums.size(); // 获取数组长度
po_len.assign(n, 0); // 初始化po_len数组,大小为n,所有元素初始化为0
}
// 解决问题的函数
void solve() {
// 外层循环,遍历数组中的每个元素,作为坡的起始点
for (int i = 0; i < n; i++) {
// 内层循环,从数组的最后一个元素向前遍历,寻找可能的坡的终点
for (int j = n - 1; j > i; j--) {
// 判断当前的终点值是否大于等于起始点值
if (nums[j] >= nums[i]) {
po_len[i] = j - i; // 如果满足条件,记录当前坡的宽度
break; // 找到第一个满足条件的坡后,跳出内层循环
}
}
// 更新记录的最大宽度坡的宽度
ret = max(ret, po_len[i]);
}
}
// 主函数,处理输入并返回最大宽度坡的宽度
int maxWidthRamp(vector<int>& _nums) {
nums = _nums; // 将输入数组赋值给成员变量nums
init(); // 调用初始化函数
solve(); // 调用解决问题的函数
return ret; // 返回计算出的最大宽度坡的宽度
}
};
2.剪枝,单调栈启发思维
我们需要得到的最终的答案是某一个元素对应的最大坡的长度.
也就是其他元素都不是最终的答案.如果我们知道其他的元素不可能是最终的答案那么这些元素无论怎样我们都不关心.
所以我们可以考虑哪一些元素可能成为最终的答案.也就是考虑哪一些元素不可能是最终的答案.
很容易发现,从前开始遍历,只需要存储大到小的数,依次放到栈里面,保持栈的大到小的严格单调性.
栈里面的元素就是可能成为最终答案的元素,如果下一个元素想要入栈,如果这个想要入栈的元素大于等于栈顶的元素,那么他是不可能成为最终的答案的,因为如果相等的话,他的最大的坡一定小于此时栈顶元素的最大的坡.
如果是大于的话,此时他的最大的坡一定小于此时栈顶元素的最大的坡.
所以这个剪枝的操作可以将很多的不可能成为答案的元素去除掉.
此时我们只需要考虑栈里面可能成为最终答案的元素的最大坡即可.
剪枝操作我们可以明确知道有哪些元素是不可能成为最终答案的.对于可能成为最终答案的元素我们去计算他们的可能值.
class Solution {
public:
vector<int> nums; // 用于存储输入的整数数组
int n; // 数组的长度
int ret; // 用于存储最终的最大宽度坡的宽度
vector<int> st; // 单调栈,用来存储可能成为最终答案的元素的索引
// 初始化函数
void init() {
n = nums.size(); // 获取数组长度
}
// 解决问题的函数
void solve() {
// 遍历数组元素
for (int i = 0; i < n; i++) {
// 如果栈为空,直接将当前索引入栈
if (st.empty())
st.push_back(i);
else {
// 如果当前元素小于栈顶元素对应的值,则当前元素索引入栈
if (nums[i] < nums[st.back()])
st.push_back(i);
}
}
// 当栈不为空时,处理栈中的元素
while (!st.empty()) {
// 取出栈顶元素索引
int top = st.back();
// 从数组的末尾开始,向前找第一个不小于栈顶元素值的位置
for (int j = n - 1; j > top; j--) {
if (nums[j] >= nums[top]) {
ret = max(ret, j - top); // 更新最大宽度坡
break; // 一旦找到就停止,因为我们只需要最远的那个
}
}
st.pop_back(); // 处理完当前栈顶元素后出栈
}
}
// 主函数,处理输入并返回最大宽度坡的宽度
int maxWidthRamp(vector<int>& _nums) {
ios::sync_with_stdio(0); // 禁用同步,以提高 I/O 性能
cin.tie(0); // 解除 cin 和 cout 的绑定
cout.tie(0); // 解除 cout 的绑定
nums = _nums; // 将输入数组赋值给成员变量nums
init(); // 调用初始化函数
solve(); // 调用解决问题的函数
return ret; // 返回计算出的最大宽度坡的宽度
}
};
3.进一步的剪枝
在第二步的基础上我们可以进一步进行剪枝.
当我们找到了栈顶元素的最大坡的位置假设是j位置,那么j位置是第一个大于栈顶元素的位置,也就是j后面的元素全都是小于栈顶元素的,所以这些元素不可能成为栈里面所有元素的答案,不可能成为栈里面所有元素的最大坡的位置,所以j可以不回退.继续考虑下一个栈里面的元素的最大坡的位置.
class Solution {
public:
vector<int> nums; // 用于存储输入的整数数组
int n; // 数组的长度
int ret; // 用于存储最终的最大宽度坡的宽度
vector<int> st; // 单调栈,用来存储可能成为最终答案的元素的索引
// 初始化函数
void init() {
n = nums.size(); // 获取数组长度
}
// 解决问题的函数
void solve() {
// 遍历数组元素
for (int i = 0; i < n; i++) {
// 如果栈为空,则直接将当前索引入栈
if (st.empty())
st.push_back(i);
else {
// 如果当前元素小于栈顶元素对应的值,则当前元素索引入栈
if (nums[i] < nums[st.back()])
st.push_back(i);
}
}
int j = n - 1; // 初始化j为数组的最后一个元素的索引
// 当栈不为空时,处理栈中的元素
while (!st.empty()) {
int top = st.back(); // 获取栈顶元素,即当前考虑的起点
// 在数组后半部分寻找第一个不小于栈顶元素值的位置
while (j >= top) {
if (nums[j] >= nums[top]) {
ret = max(ret, j - top); // 更新最大宽度坡
break; // 找到后即跳出循环
}
j--; // 否则继续向前搜索
}
st.pop_back(); // 当前栈顶元素处理完成后出栈
}
}
// 主函数,处理输入并返回最大宽度坡的宽度
int maxWidthRamp(vector<int>& _nums) {
ios::sync_with_stdio(0); // 禁用同步,以提高 I/O 性能
cin.tie(0); // 解除 cin 和 cout 的绑定
cout.tie(0); // 解除 cout 的绑定
nums = _nums; // 将输入数组赋值给成员变量nums
init(); // 调用初始化函数
solve(); // 调用解决问题的函数
return ret; // 返回计算出的最大宽度坡的宽度
}
};
316. 去除重复字母
给你一个字符串
s
,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序
最小(要求不能打乱其他字符的相对位置)。
示例 1:
输入:s = "bcabc"输出:"abc"
示例 2:
输入:s = "cbacdcbc"输出:"acdb"
提示:
1 <= s.length <= 10(4)
s
由小写英文字母组成注意:该题与 1081 . - 力扣(LeetCode) 相同
1.
贪心+单调栈.
栈保持单调性从小到大的严格单调性.
用_count记录还没有考虑到的字母的个数.
用now_count记录在栈里面的字母的个数.
如果栈空直接入栈.
如果栈不空,当前想要入栈的元素入栈之后并不会违反单调性,那么就直接入栈.
如果当前想要入栈的元素入栈之后会违反单调性,那么看一下他是否可以成为答案的组成部分.
如果栈顶的元素未来还会出现,那么栈顶元素直接出栈,当前位置元素入栈完全没问题,如果栈顶的元素未来不会再出现,此时只能直接入栈.
2.
还有一个大前提,如果当前入栈的元素在栈里面已经出现过了,就不需要再考虑他入栈了.
class Solution {
public:
string s; // 用于存储输入的字符串
int n; // 字符串的长度
map<char, int> _count; // 记录每个字符的出现次数(初始化为还未考虑的情况)
map<char, int> now_count; // 记录当前栈内的每个字符的个数
vector<char> st; // 单调栈,记录可能成为最终答案的字符序列
string ret; // 存储最终结果的字符串
// 初始化函数
void init() {
n = s.size(); // 获取字符串长度
for (int i = 0; i < n; i++) {
_count[s[i]]++; // 统计每个字符的出现次数
}
}
// 解决问题的函数
void solve() {
for (int i = 0; i < n; i++) {
_count[s[i]]--; // 将当前字符标记为已考虑
if (now_count[s[i]] != 0)
continue; // 如果字符已在栈中,跳过处理
if (st.empty()) {
st.push_back(s[i]); // 如果栈为空,直接入栈
now_count[s[i]]++; // 记录当前字符入栈
} else {
if (s[i] == st.back())
break; // 当前字符已在栈顶,无需处理
else if (s[i] > st.back()) {
st.push_back(s[i]); // 当前字符大于栈顶,直接入栈
now_count[s[i]]++; // 记录当前字符入栈
} else {
while (1) {
if (st.empty())
break;
if (s[i] > st.back())
break;
if (_count[st.back()] == 0)
break;
now_count[st.back()]--; // 弹出栈顶字符
st.pop_back();
}
now_count[s[i]]++;
st.push_back(s[i]); // 当前字符入栈
}
}
}
ret = string(st.begin(), st.end()); // 构建最终结果字符串
}
// 移除重复字母并保证字典序最小的函数
string removeDuplicateLetters(string _s) {
ios::sync_with_stdio(0); // 优化I/O性能
cin.tie(0); // 解除cin和cout的绑定
cout.tie(0); // 解除cout的绑定
s = _s; // 将输入字符串赋值给成员变量s
init(); // 调用初始化函数
solve(); // 调用解决问题的函数
return ret; // 返回最终的结果字符串
}
};
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!