删除重复项后,选择字典最小字符串

时间:2015-12-27 01:32:35

标签: c++ string algorithm lexicographic

从字符串中删除所有重复项,并选择可能的字典最小字符串。例如,字符串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;
    }

4 个答案:

答案 0 :(得分:3)

<强>算法

  1. 检查输入字符串中是否存在字母:a,b,c,d
  2. 找到其后全部为a的第一个b,c,d 或者,如果无法做到这一点,请找到其后全部为b的第一个a,c,d 或者,如果无法做到这一点,请找到其后全部为c的第一个a,b,d 或者,如果不可能,请找到第一个d
  3. 将输入字符串的开头放弃到所选字符。
  4. 从步骤2开始重复,找到剩余的字符。
  5. 代码示例

    (在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"));

答案 1 :(得分:0)

算法草图。

  1. 传递字符串,构建每个字符出现次数的映射,以及每个字符最右边(也可能是唯一)出现的位置。

  2. 找到可以出现在第一个位置的最小字符。要做到这一点,从左到右,注意遇到的最小字符;当你碰到任何角色的最右边时,就停止。删除最小字符之前的所有字符,以及最小字符的所有其他副本;相应地更新地图。

  3. 从第2步中最小的一个字符开始重复。

  4. 一旦地图中的所有计数器达到1,就可以提前终止。其他副本的删除可以与正常迭代结合使用(只需在计数器映射中标记要删除0的字符,在正常搜索中跳过它们,删除前缀时删除它们。

    这种算法在最坏的情况下是二次的,至少在字母表的大小(最坏的情况是abc...zabc...;算法检查每个字符的一半字符串,只决定保留它)。我认为这可以通过在一种优先级队列结构中保持跟踪不仅是最小的,也可以是第二小和第三小的等等来解决(详细信息留给读者阅读)。

答案 2 :(得分:0)

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;
}

答案 3 :(得分:0)

我发现这种方法很简单。

首先找到每个字符的数量。

输入: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)