如何在保留最小的词典顺序的同时删除重复的字母

时间:2017-09-04 09:39:02

标签: java string algorithm

我有一项任务是从给定的字符串中删除重复项(经典访谈问题),但这个有点不同,最终结果应该是最小的字典顺序。例如,public static String removeDuplicateCharsAlphbetically(String str) { int len = str.length(); if (len<2) return str; char[] letters = str.toCharArray(); int[] counts = new int[26]; for (char c : letters) { counts[c-97]++; } StringBuilder sb = new StringBuilder(); for (int i=0;i<len-1;i++) { if (letters[i]==letters[i+1]) continue; if (counts[letters[i]-97]==1) { sb.append(letters[i]); } else if (counts[letters[i]-97] != 0) { if (letters[i]<letters[i+1] && counts[letters[i]-97] == 1) { sb.append(letters[i]); counts[letters[i]-97]=0; } else { counts[letters[i]-97]--; } } } return sb.toString(); } 。我在SO中看到了几个相关问题,但我找不到答案。

修改:到目前为止,这是我的代码(工作不正常):

{{1}}

EDIT2 :我很抱歉没有提前提问。这是link

4 个答案:

答案 0 :(得分:3)

这可以在O(StringLenght)时间完成。 字符串长度= N; 时间复杂度O(N),字符串的单次扫描。 空间复杂度O(26)

解决方案:

  1. 创建一个字母数组,该数组将存储指向双链表Node的指针。 ListNode *数组[26]; //用NULL值初始化。

  2. 创建一个空的链表,该链表将在任何时间点以相反的顺序表示解决方案字符串。

  3. 扫描字符串,然后为每个字母检查字母,ltr,检查数组[ltr-'a']    一种。如果为NULL,则表示它是第一次出现,并将其添加到链接列表中。 b。如果数组指向任何节点listNodeltr,则表示字母已经在结果中        一世。在linklist

    中检查上一个listNode到listNodeltr的值

    如果listNodeltr-> prev-> val val的值,则意味着从该位置删除当前节点将使结果在字典上变小。 因此,从linkedList中的当前位置删除listNodeLtr并将其添加到末尾。

    否则,找到并继续使用ltr的当前位置。

cba cdcbc

[a]-> [b]-> [c]

cbac dcbc [c]-> [a]-> [b]

cbacdc bc [d]-> [c]-> [a]-> [b]

cbacdcb c [b]-> [d]-> [c]-> [a]

cbacdcbc [b]-> [d]-> [c]-> [a]

以相反的顺序打印链接列表:acdb。

答案 1 :(得分:2)

首先,让我们创建一组字符串s的所有不同字母。该集合的大小是答案的长度和算法中的步骤数。 我们将使用以下贪婪的方法在每个步骤中为答案添加一个字母:

在每一步中按字母顺序和每个字母l迭代剩余的字母:

  1. l中查找s的第一个匹配项。我们将其命名为lpos
  2. 如果子字符串s[lpos, end]包含所有剩余字母,则将l添加到结果中,将s替换为s[lpos+1, end],然后使用缩小的剩余字母集进行下一步。
  3. 通过一些优化实现以实现更好的时间复杂性:

    public String removeDuplicateLetters(String s) {
        StringBuilder result = new StringBuilder();
        int[] subsets = new int[s.length()];
    
        int subset = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            char ch = s.charAt(i);
            subset = addToSet(subset, ch);
            subsets[i] = subset;
        }
    
        int curPos = 0;
        while (subset != 0) {
            for (char ch = 'a'; ch <= 'z'; ++ch) {
                if (inSet(subset, ch)) {
                    int chPos = s.indexOf(ch, curPos);
                    if (includes(subsets[chPos], subset)) {
                        result.append(ch);
                        subset = removeFromSet(subset, ch);
                        curPos = chPos + 1;
                        break;
                    }
                }
            }
        }
    
        return result.toString(); 
    }   
    
    private boolean inSet(int set, char ch) {
        return (set & (1 << (ch - 'a'))) != 0;    
    }
    
    private boolean includes(int set, int subset) {
        return (set | subset) == set;
    }
    
    private int addToSet(int set, char ch) {
        return set | (1 << (ch - 'a'));
    }
    
    private int removeFromSet(int set, char ch) {
        return set & ~(1 << (ch - 'a')); 
    }
    

    Runnable版本:https://ideone.com/wIKi3x

答案 2 :(得分:2)

通过从输入结尾向后开始构建结果。在每一步:

  1. 如果遇到新信,请将其添加到结果中。
  2. 如果遇到重复,则将其与结果头部进行比较。如果head更大,则从结果中删除duplicate并改为添加前缀。
  3. LinkedHashSet对于存储结果集及其内部顺序都很好。

    public static String unduplicate(String input) {
        Character head = null;
        Set<Character> set = new LinkedHashSet<>();
        for (int i = input.length() - 1; i >= 0; --i) {
            Character c = input.charAt(i);
            if (set.add(c))
                head = c;
            else if (c.compareTo(head) < 0) {
                set.remove(c);
                set.add(head = c);
            }
        }
        StringBuilder result = new StringBuilder(set.size());
        for (Character c: set)
            result.append(c);
        return result.reverse().toString();
    }
    

答案 3 :(得分:1)

观察1:输出的第一个字母是最小字母,使得所有其他字母出现在字符串中最左边的外观的右侧。

观察2:输出的剩余字母是第一个字母最左边外观右边字母的子序列。

这表明了一种递归算法。

def rem_dups_lex_least(s):
    if not s:
        return ''
    n = len(set(s))  # number of distinct letters in s
    seen = set()     # number of distinct letters seen while scanning right to left
    for j in range(len(s) - 1, -1, -1):  # len(s)-1 down to 0
        seen.add(s[j])
        if len(seen) == n:  # all letters seen
            first = min(s[:j+1])
            i = s.index(first)  # leftmost appearance
            return first + rem_dups_lex_least(''.join(c for c in s[i+1:] if c != first))