У меня есть задача удалить дубликаты из заданной строки (классический вопрос интервью), но этот немного отличается, конечный результат должен быть в наименьшем лексикографическом порядке среди прочих. Например, cbacdcbc => acdb, bcabc => abc. Я видел несколько связанных проблем в SO, но я не мог найти ответ.

Изменить . Вот мой код (пока не работает):

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

EDIT2 . Я сожалею, что не поставил ссылку на вопрос ранее. вот ссылка:

3
Humoyun Ahmad 4 Сен 2017 в 12:39

3 ответа

Лучший ответ

Сначала давайте создадим набор всех различных букв строки s. Размер этого набора - это длина ответа и количество шагов в нашем алгоритме. Мы добавим одну букву к ответу на каждом шаге следующим жадным подходом:

На каждом шаге повторяйте оставшиеся буквы в алфавитном порядке и для каждой буквы l:

  1. Найдите первое вхождение l в s. Давайте назовем это lpos.
  2. Если подстрока s[lpos, end] содержит все оставшиеся буквы, то добавьте l к результату, замените s на s[lpos+1, end] и перейдите к следующему шагу с набором сокращенных оставшихся букв.

Реализация с некоторыми оптимизациями для достижения большей сложности времени:

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

Выполняемая версия: https://ideone.com/wIKi3x

2
DAle 4 Сен 2017 в 20:41

Замечание 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))
1
David Eisenstat 4 Сен 2017 в 14:35

Создайте результат, перейдя назад от конца ввода к началу. На каждом шаге:

  1. Если встречается новая буква, добавьте ее к результату.
  2. Если обнаружен дубликат, сравните его с заголовком результата. Если голова больше, удалите дубликат из результата и добавьте вместо него.

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();
}
2
Vasily Liaskovsky 4 Сен 2017 в 16:14