LeetCode #316 Remove Duplicate Letters

解决LeetCode #316问题,通过贪心算法找到给定字符串中每个字母只出现一次且字典序最小的结果。分析了错误的从后往前检查方法,并详细解释了一个正确的递归贪心算法,该算法首先找到当前未出现的最小字母,然后移除其右侧重复的字母,保证字典序最小。

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

题目

Remove Duplicate Letters

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 1:

Input: "bcabc"
Output: "abc"

Example 2:

Input: "cbacdcbc"
Output: "acdb"

Difficulty: Hard

分析

题目的关键是,要给出词典顺序最小的结果字符串。

我一开始的想法是,从后往前检查,把搜到的第一个不唯一的字符 s[i] ,和它前面的一个副本 s[j] ,比较 s[j..i-1]s[j+1...i] ,从而决定要选择哪个位置,去除哪个字符。

比如:

bcabc -> (remove b) -> babc  -> (remove b) -> abc
cbacdcbc -> (remove c) -> cbacdbc -> (remove c) -> cbacdb -> (remove b) -> cacdb -> (remove c) -> acdb

但是有一个反例:

defcbeabdc -> (remove c) -> defbeabdc -> (remove d) -> defbeabc 
	-> (remove b) -> defbeac -> (remove e) -> defbac

正确答案应为 defabc

贪心算法

在网站上我看到了这个递归的贪心算法,它的思路是这样的。

思路

每一次的贪心选择即为答案最左端的字母。如何进行贪心选择呢?由题目要求可以知道,这个贪心选择得到的字母应该尽可能地小。姑且称呼这个选择为“最小字母”,它应当是在一定范围内最小的字母。

但是,这样的贪心选择必然有中止条件或限制范围。假想某个“最小字母”左边有唯一的字母,比如CBDDF,B左边有C,C在该字符串中是唯一的。这种情况下,以B作为贪心选择是不合理的。

而如果“最小字母”左边的字母,在“最小字母”右边也有它们的重复,那么左边的这些字母完全可以舍去,它们不会比“最小字母”小,由它们无法得到字典顺序最小的答案字符串。比如CBABC,A左边的CB可以舍去。

所以,字符串 s 中的 “最小字母” s[pos] 应该满足这样的条件:

  • s[0...pos-1] 中的字母,在字符串 s 中不是唯一的

  • s[pos...] 中的字母包括了所有在字符串 s 中唯一的字母

(在字符串 s 中没有唯一字母的情况下,会有多个“最小字母”,比如abcacb。这时选择最左边的“最小字母”。)

取出 s[pos] ,加入答案字符串中。把右边的子串 s[pos+1...] 中重复的字母 s[pos] 去掉后,对新的字符串重复上述操作。

例子

答案字符串待处理字符串第一个唯一字母最小字母
defcbeabdcfd
defcbeabcfe
defcbabcff
defcbabcaa
defabcbb
defabccc
defabc

C++代码

class Solution {
public:
    string removeDuplicateLetters(string s) {
		int count[26] = {0};
		for(int i = 0; i < s.length(); i++){
			count[s[i] - 'a']++;
		}
		int pos = 0;
		for(int i = 0; i < s.length(); i++){
			if(s[i] < s[pos]) pos = i;
			if(--count[s[i] - 'a'] == 0) break;
		}
		string suffix;
		if(pos + 1 > s.length()) suffix = "";
		else suffix = s.substr(pos + 1, s.length() - pos - 1);
		return s.length() == 0 ? "" : s[pos] + removeDuplicateLetters(removeOneLetter(suffix, s[pos]));
    }

private:
	string removeOneLetter(string s, char ch){
		while(1){
			int index = s.find(ch);
			if(index != -1) s = s.erase(index, 1);
			else break;
		}
		return s;
	}
};

因为 removeDuplicateLetters 的次数不会超过 26 26 26 ,在 removeDuplicateLetters 中遍历的长度不会超过 n n n ,所以运算的总次数不会超过 26 ∗ n 26*n 26n ,该算法的时间复杂度为 O ( n ) O(n) O(n) ,空间复杂度也为 O ( n ) O(n) O(n)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值