316. 去除重复字母
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
思路:
使用栈,遍历字符串s,
1)若当前字符大于栈顶字符或栈为空,则当前字符直接入栈;
2)若栈中已存在当前字符则直接跳过当前元素;
3)若当前字符小于栈顶字符,则循环判断当前字符之后的字符串是否包含栈顶字符,若包含则栈顶元素出栈,直到栈为空或当前字符大于栈顶字符为止,并将当前字符入栈。
遍历字符串结束后,栈中即为满足要求的子串。
class Solution {
public String removeDuplicateLetters(String s) {
Stack<Character> stack = new Stack<>();
for(int i=0;i<s.length();i++) {
if(stack.search(s.charAt(i))>=1)
//栈中已存在当前元素,当前元素不入栈,舍弃
continue;
//当前元素大于栈顶元素,则判断当前元素之后是否还包含栈顶元素,若包含则栈顶元素出栈
//直到栈空或栈顶元素小于当前元素或当前元素后不再包含栈顶元素为止
while(!stack.isEmpty() && s.charAt(i)<stack.peek() && s.substring(i).contains(String.valueOf(stack.peek()))) {
stack.pop();
}
stack.push(s.charAt(i));
}
//将栈中元素转换成字符数组
char[] res=new char[stack.size()];
for(int i=0;i<res.length;i++)
res[i]=stack.get(i);
return new String(res);
}
}
官方:贪心算法+栈
1.题目要求字典排序, 则结果中第一个字母的字典序靠前的优先级最高—贪心
azzzzzz 比 baaaaaa 的字典序列更靠前
2.因为不能打乱相对位置—即输入中排在输出第一个字母位置前的字母都不能出现,所以要在保证每个字母至少出现一次的前提下再考虑字典序排列
若输入为 bacc 则输出 ac 不合法 不能以 a 作为第一位因为这样会导致 bacc 中 a 之前只出现一次的 b 失效
3.根据第1点可以考虑使用单调栈来保证字典序排列,根据第2点我们给元素出栈时加上限制条件,只有在栈顶元素字典序靠后,且在之后还有出现次数才弹出栈.同时压栈时应该注意栈中没有出现过该元素才能压栈.
若输入为bcacc
- b 入栈
- c 入栈时因为字典序靠后,且栈中没出现过c,直接压栈
- a 入栈,因为 a 的字典序列靠前且a没有出现过,此时要考虑弹出栈顶元素. 元素 c 因为在之后还有 至少一次 出现次数,所以这里可以弹出. 元素 b 之后不再出现,为了保证至少出现一次这里不能再舍弃了.
- c 入栈时依然因为字典序靠后且栈中没出现过直接压栈
- c 入栈时栈中已经出现过c,跳过该元素 输出结果为 bac
4.需要的存储结构
根据思路首先需要一个栈结构,由于最终输出结果是先进先出,我们使用双向队列来模拟栈的操作.
弹出栈顶元素时需要知道该字母剩余出现次数,可以用一个哈希表统计字母出现次数,这里元素都是小写字母,可以使用长度26的int数组代替
压栈时还需要一个哈希表统计栈中是否出现过该字母,同上可以用长度26的boolean型数组代替
5.伪代码
// 声明变量
for{ // 遍历字符串s
// 初始化字母出现次数哈希表 }for{ // 遍历字符串s
if(该位置字母没有在栈中出现过){
while(栈不为空 && 栈顶元素字典序列靠后 && 栈顶元素还有剩余出现次数){
// 修改出现状态
// 栈顶元素弹出
}
// 该位置的字母压栈
// 修改出现状态
}
// 遍历过的字母出现次数减一 }// 返回栈中元素
6.Java实现
public String removeDuplicateLetters(String s) {
LinkedList<Character> deque = new LinkedList<>();
int[] count = new int[26];
boolean[]exists = new boolean[26];
// 初始化,记录每个字符出现次数
for(char ch : s.toCharArray()){
count[ch - 'a']++;
}
// 遍历s并入栈
for (int i = 0; i < s.length(); i++) {
char temp = s.charAt(i);
if (!exists[temp - 'a']){
while (!deque.isEmpty() && deque.getLast() > temp && count[deque.getLast() - 'a'] > 0){
exists[deque.getLast() - 'a'] = false;
deque.removeLast();
}
deque.add(temp);
exists[temp - 'a'] = true;
}
count[temp - 'a']--;
}
//返回
StringBuilder res = new StringBuilder();
for(char ch : deque){
res.append(ch);
}
return res.toString();
}
7.思路总结
1.从字典序入手,第一个字母靠前可以使整个字符串靠前 —>贪心
2.每次贪心要找到一个当前最靠前的字母 —>单调栈
3.从限制条件切入,改造贪心条件 —>有条件限制的单调栈 确定数据结构