316. Remove Duplicate Letters

本文介绍了一种去除字符串中重复字母并保持最小字典序的方法,通过递归、栈和下标规律三种策略实现,确保结果唯一且有序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Given a string which contains only lowercase letters, remove duplicate letters so that every letter appear once and only once. You must make sure your result is the smallest in lexicographical order among all possible results.

Example:

Given "bcabc"
Return "abc"

Given "cbacdcbc"
Return "acdb"

摘自

https://discuss.leetcode.com/topic/31404/a-short-o-n-recursive-greedy-solution

http://www.cnblogs.com/lasclocker/p/5037254.html

Given the string s, the greedy choice (i.e., the leftmost letter in the answer) is the smallest s[i], s.t.
the suffix s[i .. ] contains all the unique letters. (Note that, when there are more than one smallest s[i]'s, we choose the leftmost one. Why? Simply consider the example: "abcacb".)

After determining the greedy choice s[i], we get a new string s' from s by

  1. removing all letters to the left of s[i],
  2. removing all s[i]'s from s.

We then recursively solve the problem w.r.t. s'.

The runtime is O(26 * n) = O(n).

public class Solution {
    public String removeDuplicateLetters(String s) {
        int[] cnt = new int[26];
        int pos = 0; // the position for the smallest s[i]
        for (int i = 0; i < s.length(); i++) cnt[s.charAt(i) - 'a']++;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) < s.charAt(pos)) pos = i;
            if (--cnt[s.charAt(i) - 'a'] == 0) break; //保证字符是全的,例如"bcabc",到a位置就终止了,也保证了最小字符前面的可以删,因为包括当前操作的位置,往后至少所有所有字符还会有一份
        }
        return s.length() == 0 ? "" : s.charAt(pos) + removeDuplicateLetters(s.substring(pos + 1).replaceAll("" + s.charAt(pos), ""));//当前位置的字符+迭代删除以后的字符中去掉当前字符
    }
}


------------------------------------------------------------------------------------------------------

给所有单词计数,每次检查到一个位置,就给这个位置上的字母计数值-1,维护一个栈,当前元素如果比栈顶的字典序小,在计数值不为0的情况下(表明如果现在把这个字母从栈顶弹出,后面还存在当前被出栈的字母)就出栈直到栈空或者当前元素比栈顶字母字典序大,这样处理一遍就得到所求的。栈可以用StringBuilder代替


public String removeDuplicateLetters3(String s) {
	        
	        int[] res = new int[26]; // will contain number of occurences of character (i+'a')
	        boolean[] visited = new boolean[26]; // will contain if character ('a' + i) is present in current result Stack
	        char[] ch = s.toCharArray();
	        for(char c : ch){  // count number of occurences of character 
	            res[c-'a']++;
	        }
	        StringBuilder sb = new StringBuilder();; // answer stack
	        int index;
	        for(char c : ch){ 
	            index = c - 'a';
	            res[index]--;   // decrement number of characters remaining in the string to be analysed
	            if(visited[index]) // if character is already present in stack, dont bother
	                continue;
	            // if current character is smaller than last character in stack which occurs later in the string again
	            // it can be removed and  added later e.g stack = bc remaining string abc then a can pop b and then c
	            while( (sb.length() > 0) && c < sb.charAt(sb.length()-1) && res[sb.charAt(sb.length()-1)-'a']!=0){ 
	                visited[sb.charAt(sb.length()-1) - 'a'] = false;
	                sb.deleteCharAt(sb.length()-1);
	            }
	            sb.append(c); // add current character and mark it as visited
	            visited[index] = true;
	        }
	        
	        return sb.toString();
	    
	    }




------------------------------------------------------------------------------------------------------

分析:通过观察每个字母下标的规律,以"cbacdcbc"为例,

第一步,计算下标(countIndex):

复制代码
a  => 2,  

b  => 1, 6

c  => 0, 3, 5, 7

d  => 4
复制代码

第二步,寻找符合条件的字母(findLetter):

如果一个字母的第一个下标小于其后每个字母最后一个下标,则该字母符合条件。比如 a 中的 2 小于 b 的 6,c 的 7,d 的 4,则 a 中的2符合条件。

第三步,删除前面的下标(removeIndex):

不防设第二步中符合条件的字母为X,则删除X下标之前的所有下标,并删除X。比如X为a,X下标为2,则删除 b 中的 1,c 中的 0,并把 a 删除。

重复第二步,第三步,直到集合为空。

以 "cbacdcbc" 为例:

复制代码
b  => 6

c  => 3, 5, 7

d  => 4
复制代码
复制代码
b  => 6



d  => 4
复制代码

上述变换解释:虽然 6 < 7,但 6 却 > 4, 故 b 不符合,而 c 中的 3 小于其后的 4,故 c 符合。 

b  => 6

故最后生成 a -> c -> d -> b,即 acdb.

代码如下:

复制代码
    public String removeDuplicateLetters(String s) {
        StringBuilder res = new StringBuilder();
        HashMap<Character, ArrayList<Integer>> hm = new HashMap<Character, ArrayList<Integer>>();
        ArrayList<Character> reference = new ArrayList<Character>();
        countIndex(s, hm, reference);
        Collections.sort(reference);
        while (!reference.isEmpty()) {
            int lettIndex = findLetter(s, res, hm, reference);
            removeIndex(lettIndex, hm, reference);
        }
        return res.toString();
    }
    
    private void countIndex(String s, HashMap<Character, ArrayList<Integer>> hm, ArrayList<Character> reference) {
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (! hm.containsKey(ch)) {
                ArrayList<Integer> tmp = new ArrayList<Integer>();
                tmp.add(i);
                hm.put(ch, tmp);
                reference.add(ch);
            } else {
                hm.get(ch).add(i);
            }
        }
    }
    
    private int findLetter(String s, StringBuilder res, HashMap<Character, ArrayList<Integer>> hm, ArrayList<Character> reference) {
        int m = 0;
        for (int i = 0; i < reference.size(); i++) {
            m = hm.get(reference.get(i)).get(0);
            int j = i+1;
            for (; j < reference.size(); j++) {
                ArrayList<Integer> tmp = hm.get(reference.get(j));
                if (m > tmp.get(tmp.size()-1)) {
                    break;
                }
            }
            if (j == reference.size()) {
                res.append(reference.get(i));
                reference.remove(i);
                break;
            }
        }
        return m;
    }
    
    private void removeIndex(int lett, HashMap<Character, ArrayList<Integer>> hm, ArrayList<Character> reference) {
        for (int i = 0; i < reference.size(); i++) {
            ArrayList<Integer> tmp = hm.get(reference.get(i));
            while (!tmp.isEmpty() && tmp.get(0) < lett) {
                tmp.remove(0);
            }
        }
    }

### LeetCode 316 Remove Duplicate Letters C++ 实现 为了构建字典序最小且无重复字符的子序列,可以采用贪心算法配合栈结构来解决这个问题。对于给定字符串中的每一个字符,如果这个字符已经在结果中,则跳过;否则,在保持当前部分结果仍为字典序最小的前提下尽可能移除已有的较大字符。 #### 贪心策略解释 当处理一个新的字符时,应该考虑两个条件: - 如果新字符小于栈顶元素,并且栈顶元素在未来还会再次遇到,则应弹出栈顶元素以尝试获得更小的字典顺序[^4]。 - 使用布尔数组记录哪些字符已经被加入到了最终的结果里,这样能确保每个字符只被选一次[^5]。 #### 时间与空间复杂度分析 由于 `removeDuplicateLetters` 函数调用次数不超过26次(即字母表大小),每次遍历整个输入串长度 \(n\) ,因此整体时间复杂度为 \(O(n)\),同样地,额外使用的辅助数据结构也使得空间复杂度达到 \(O(n)\)。 下面是一个具体的 C++ 实现: ```cpp class Solution { public: string removeDuplicateLetters(string s) { vector<int> last_seen(256, -1); // 记录每种字符最后出现的位置 vector<bool> visited(256, false); // 标记某字符是否已在栈中 stack<char> st; int n = s.size(); for(int i=0; i<n; ++i){ last_seen[s[i]] = i; // 更新最后一次见到某个字符的位置 } for(int i=0;i<s.length();i++){ char c=s[i]; if(!visited[c]){ while (!st.empty() && c<st.top() && i<last_seen[st.top()]) { visited[st.top()] = false; st.pop(); } st.push(c); visited[c]=true; } } string res=""; while (!st.empty()) { res+=st.top(); st.pop(); } reverse(res.begin(),res.end()); return res; } }; ``` 此代码片段实现了上述逻辑,通过维护一个栈以及相应的标记位图来保证所得到的结果满足题目要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值