leetcode
leetcode 1001 ~ 1050
不同字符的最小子序列

不同字符的最小子序列

难度:

标签:

题目描述

代码结果

运行时间: 24 ms, 内存: 16.0 MB


/**
 * 思路:
 * 1. 依旧是利用栈来维护字典序最小的子序列。
 * 2. 使用Java Stream API处理字符串并操作栈。
 */

import java.util.*;
import java.util.stream.Collectors;

public class SolutionStream {
    public String smallestSubsequence(String s) {
        int[] lastIndex = new int[26]; // 记录每个字符最后出现的位置
        Arrays.stream(s.split(""))
              .forEach(c -> lastIndex[c.charAt(0) - 'a'] = s.lastIndexOf(c.charAt(0)));

        boolean[] inStack = new boolean[26]; // 记录字符是否在栈中
        Deque<Character> stack = new ArrayDeque<>();

        s.chars()
         .mapToObj(c -> (char) c)
         .forEach(c -> {
             if (inStack[c - 'a']) return;
             while (!stack.isEmpty() && stack.peek() > c && lastIndex[stack.peek() - 'a'] > s.indexOf(c)) {
                 inStack[stack.pop() - 'a'] = false;
             }
             stack.push(c);
             inStack[c - 'a'] = true;
         });

        return stack.stream()
                     .map(String::valueOf)
                     .collect(Collectors.joining());
    }
}

解释

方法:

该解法使用贪心算法和栈来解决问题。首先,通过遍历字符串s建立每个字符的最后出现位置的哈希表,这有助于确定字符是否可以从栈中弹出。算法遍历字符串s中的每个字符,使用栈来构建最终的结果序列。如果当前字符未在结果序列中,且当前字符字典序小于栈顶字符,并且栈顶字符在后面还会出现,那么栈顶字符会被弹出。这保证了序列尽可能地小在字典序中。使用集合seen来跟踪哪些字符已经被加入到栈中,防止重复添加字符。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
如何确保通过遍历字符串来建立每个字符的最后出现位置的哈希表时,该表确实记录了每个字符的最后位置而不是第一个位置?
在构建哈希表时,我们遍历整个字符串,并用字典来记录每个字符的索引位置。每次遇到一个字符,都将其在字典中的值更新为当前的索引。这意味着,对于每个字符,最终存储的索引将是该字符在字符串中最后一次出现的位置。因为每次字符再次出现时,其索引值会被新的(较大的)索引值覆盖,从而确保了记录的是最后的位置。
🦆
在提到栈顶字符在后面还会出现时,是如何通过算法中的数据结构确定这一点的?
算法中使用了一个哈希表(last_occurrence)来记录每个字符最后一次出现的位置。当考虑是否应该从栈中弹出栈顶字符时,我们可以查看当前字符的索引(i)和栈顶字符的最后出现索引(从哈希表中获取)。如果栈顶字符的最后出现位置大于当前字符的索引,这表明栈顶字符在字符串中后面的位置还会再次出现,因此可以安全地将其从栈中移除,以便在后续适当的位置重新加入。
🦆
为什么在字符比栈顶元素字典序小的情况下就必须弹出栈顶元素?这样的操作是否可能导致最终结果不是最小字典序?
如果当前字符的字典序小于栈顶元素,并且栈顶元素后面还会出现,则弹出栈顶元素可以帮助我们获得更小的字典序结果。这是因为移除栈顶元素后,我们有机会在结果中以更小的字符替代它,从而达到字典序更小的目的。此操作不会导致最终结果不是最小字典序,因为每次操作都是在确认栈顶元素将来还会出现的情况下进行,这保证了不会丢失任何字符,同时可以尽可能地构造出字典序最小的结果。
🦆
在解法中,seen集合被用于跟踪哪些字符已加入栈中。如果字符已经在seen集合中了,为什么就不需要再次将其添加到栈中?
seen集合确保每个字符在最终结果子序列中只出现一次。如果一个字符已经在seen集合中,这意味着它已经在栈(也即是最终结果子序列)中。再次将其添加到栈中将会违反结果子序列中字符的唯一性要求。因此,一旦字符加入seen集合,我们就不再将它添加到栈中,除非它之前从栈中被移除(这种情况下该字符也会从seen集合中移除)。

相关问题