算法 2.6 栈 + 贪心:去除重复字母【leetcode 31

2021-02-08  本文已影响0人  珺王不早朝

题目描述

给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。
需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)

示例:
输入: "bcabc"
输出: "abc"

示例:
输入: "cbacdcbc"
输出: "acdb"

数据结构

算法思维

解题步骤


一. Comprehend 理解题意
题目主干
附加限制
  1. 不能打乱字符的相对位置:“cba”, “acb” 不满足条件(无法通过删除字符得到)
  2. 返回字典序最小的字符串: “abc”< “bac”< “bca”< “cab”,故应返回 “abc”
宽松限制
细节问题
二. Choose 选择数据结构与算法
数据结构选择
算法思维选择:贪心算法与栈的结合
三. Code 编码实现基本解法
解题思路剖析
代码实现
class Solution {
    public String removeDuplicateLetters(String s) {
        int length = s.length();
        //创建计数器 lastOccurrence 记录每个字母在字符串中最后一次出现的位置
        int[] lastOccurrence = new int[26];
        for (int i = 0; i < length; i++)
            lastOccurrence[s.charAt(i) - 'a'] = i;
        Stack<Character> stack = new Stack<>();
        boolean[] seen = new boolean[26]; //记录当前栈中已经存在的字符
        //再浏览一遍字符串,对当前字符:
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            if (!seen[c - 'a']) {//若栈不空,且当前字符在栈中出现过,跳过当前字符
                while (!stack.isEmpty() && c < stack.peek() && lastOccurrence[stack.peek() - 'a'] > i) {
                    //栈为空、或当前字符大于栈顶字符、或栈顶字符在当前字符后不再出现,当前字符压栈
                    seen[stack.pop() - 'a'] = false;
                }
                stack.push(c);
                seen[c - 'a'] = true;
            }
        }
        String result = "";
        while (!stack.isEmpty()) result = stack.pop() + result; //栈拼接成字符串
        return result;
    }
}

时间复杂度:O(n)
  • 遍历字符串,统计字母在字符串中最后一次出现的位置 O(n)
  • 遍历字符串,计算结果 O(n)

空间复杂度:O(1)
  • 使用栈存放结果字符串(最大长度26)O(1)
  • 其他变量的常数级空间占用 O(1)

执行耗时:4 ms,击败了 91.26% 的Java用户
内存消耗:39.4 MB,击败了 9.72% 的Java用户

四. Consider 思考更优解
剔除无效代码 优化空间消耗
五. Code 编码实现最优解
class Solution {
    public String removeDuplicateLetters(String s) {
        //此处使用字符数组来模拟栈,top记录栈顶元素的下标(top=-1时栈为空)
        char[] stack = new char[26];
        int top = -1;
        //数组seen记录当前栈中已经存在的字符,如果后续再遇到可以跳过
        boolean[] seen = new boolean[26];
        //last_occurrence 记录字符串中出现过的字符在字符串最后一次出现的位置
        int[] last_occurrence = new int[26];
        char[] cs = s.toCharArray();
        for (int i = 0; i < s.length(); i++)
            last_occurrence[cs[i] - 'a'] = i;
        //从左到右扫描字符串
        for (int i = 0; i < s.length(); i++) {
            char c = cs[i];
            if (!seen[c - 'a']) {
                // 若当前字符已经在栈中,无需处理
                // 如果栈中有元素,且栈顶元素比当前字符小,并且栈顶字符在后续字符串还会出现,
                // 那么我们可以用当前字符替换栈顶字符得到一个字典序更小的字符串
                //(注意此处将一直与栈顶元素相比,直到栈为空或栈顶字符比当前字符小,或栈顶字符在当前位置之后不会再出现)
                while (top != -1 && c < stack[top] && last_occurrence[stack[top] - 'a'] > i)
                    seen[stack[top--] - 'a'] = false;
                seen[c - 'a'] = true;
                stack[++top] = c;
            }
        }
        //将栈中的字母连接起来
        StringBuilder ss = new StringBuilder();
        for (int i = 0; i <= top; i++) ss.append(stack[i]);
        return ss.toString();
    }
}

时间复杂度:O(n)
  • 数组的遍历 O(n)

空间复杂度:O(1)
  • 常数级内存空间 O(1)

执行耗时:1 ms,击败了 100.00% 的Java用户
内存消耗:37.1 MB,击败了 100.00% 的Java用户

六. Change 变形与延伸
题目变形
上一篇 下一篇

猜你喜欢

热点阅读