删除重复项后选择字典最小的字符串
Posted
技术标签:
【中文标题】删除重复项后选择字典最小的字符串【英文标题】:Select lexicographical smallest string after duplicates removed 【发布时间】:2015-12-27 01:32:34 【问题描述】:从字符串中删除所有重复项并选择可能的字典最小字符串。例如,字符串 cbacdcbc 将返回 acdb,而不是 adcb。
因此,如果我们不必选择字典最小的字符串,这有一个相对简单的解决方案,但考虑到这一事实,我不确定如何找到一个有效的解决方案。这是我目前所拥有的:
string removeDuplicateLetters(string s)
vector<bool> v(26,0);
for(int i = 0; i < s.size(); i++)
v[s[i]-'a'] = 1;
string ss = "";
for(int i = 0; i < s.size(); i++)
if(v[s[i]-'a'])
ss += s[i];
v[s[i]-'a'] = 0;
return ss;
【问题讨论】:
这个问题听起来像家庭作业。 @HannoBinder 其实是个面试题。 为什么abcd
不能是字符串?
【参考方案1】:
算法
-
检查输入字符串中存在哪些字母:
a,b,c,d
。
找到第一个 a
后面有所有 b,c,d
。
或者,如果这不可能,请找到第一个 b
,其后包含所有 a,c,d
。
或者,如果这不可能,请找到第一个 c
,其后包含所有 a,b,d
。
或者,如果这不可能,请找到第一个 d
。
丢弃输入字符串的开头直到所选字符。
从第 2 步开始重复,找到剩余的字符。
代码示例
(在 javascript 中;我的 C++ 生锈了)。它创建一个位模式chars
来存储哪些字符仍然可以找到,并创建一个位模式数组after
来存储每个位置之后哪些字符仍然可用。
function smallestString(input)
var chars = 0, after = [];
for (var i = input.length - 1; i >= 0; i--)
chars |= 1 << (input.charCodeAt(i) - 97);
after[i] = chars;
var result = "", start = 0, pos;
while (chars)
for (var i = 0; i < 26; i++)
if (chars & (1 << i))
pos = input.indexOf(String.fromCharCode(97 + i), start);
if (chars == (chars & after[pos]))
result += String.fromCharCode(97 + i);
chars -= 1 << i;
break;
start = pos + 1;
return result;
document.write(smallestString("cbacdcbc") + "<BR>");
document.write(smallestString("thequickbrownfoxjumpsoverthelazydog"));
【讨论】:
【参考方案2】:m69 在 c++ 中的 javascript:
string smallestString(string input)
int chars = 0;
int after[sizeof(input)];
for (int i = input.length() - 1; i >= 0; i--)
chars |= 1 << (input[i] - 97);
after[i] = chars;
string result = "";
int start = 0, pos;
while (chars)
for (int i = 0; i < 26; i++)
if (chars & (1 << i))
pos = input.find('a' + i, start);
if (chars == (chars & after[pos]))
result += 'a' + i;
chars -= 1 << i;
break;
start = pos + 1;
return result;
【讨论】:
谢谢;我最初是通过有关 C 和 C++ 的书籍学习编程的,但我已经很多年没有使用它了,所以我犹豫在其中发布代码..【参考方案3】:算法草图。
传递字符串,构建每个字符出现次数的映射,以及每个字符最右边(也可能是唯一)出现的位置。
找到可以出现在第一个位置的最小字符。为此,请从左到右,注意遇到的最小字符;当您击中任何字符的最右侧出现时停止。删除最小字符之前的所有字符,以及最小字符的所有其他副本;相应地更新地图。
从紧跟第 2 步中最小字符的字符开始重复。
一旦映射中的所有计数器达到 1,可以提前终止。删除其他副本可以与正常迭代相结合(只需在计数器映射中用 0 标记要删除的字符,在正常搜索中跳过它们,删除前缀时删除它们)。
这个算法在最坏的情况下是二次的,至少在字母表的大小上(最坏的情况是abc...zabc...
;算法检查每个字符的一半字符串,然后决定保留它)。我认为这可以通过在一种优先级队列结构中不仅跟踪最小的,还跟踪第二小的和第三小的等等来解决(细节留给读者练习)。
【讨论】:
【参考方案4】:我觉得这种方法很简单。
先求每个字符的个数。
输入:s
vector<int> cnt(26);
int n=s.size();
for(int i=0;i<n;i++) cnt[s[i]-'a']++;
有一个访问过的向量,vector<bool> visit(26);
string ans="";
for(int i=0;i<n;i++)
int t=s[i]-'a';
cnt[t]--;
if(visit[t]) continue;
while(ans.size()>0 && s[i]<ans.back() && cnt[ans.back()-'a']>0)
visit[ans.back()-'a']=false;
ans.pop_back();
ans.push_back(s[i]);
visit[t]=true;
return ans;
时间复杂度为O(n)
【讨论】:
以上是关于删除重复项后选择字典最小的字符串的主要内容,如果未能解决你的问题,请参考以下文章