Leetcode - Remove Duplicate Letters

My code:

public class Solution {
    public String removeDuplicateLetters(String s) {
        if (s == null || s.length() == 0)
            return s;
        int len = s.length();
        Stack<Character> tracker = new Stack<Character>();
        HashMap<Character, Integer> dic = new HashMap<Character, Integer>(); // character -> numbers of this character
        /** form this dictionary to record the numbers of characters */
        for (int i = 0; i < len; i++) {
            char curr = s.charAt(i);
            if (!dic.containsKey(curr))
                dic.put(curr, 1);
            else {
                int times = dic.get(curr);
                dic.put(curr, times + 1);
            }
        }
        /** remove elements from string */
        for (int i = 0; i < len; i++) {
            char curr = s.charAt(i);
            if (tracker.isEmpty())
                tracker.push(curr);
            else if (tracker.contains(curr)) {
                int times = dic.get(curr);
                dic.put(curr, times - 1);
                continue;
            }
            else {
                while (!tracker.isEmpty()) {
                    int times = dic.get(tracker.peek());
                    if (curr < tracker.peek() && times > 1) {
                        dic.put(tracker.peek(), times - 1);
                        tracker.pop();
                    }
                    else {
                        tracker.push(curr);
                        break;
                    }
                }
                if (tracker.isEmpty())
                    tracker.push(curr);
            }
        }
        StringBuilder ret = new StringBuilder();
        while (!tracker.isEmpty()) {
            ret.append(tracker.pop());
        }
        return ret.reverse().toString(); // string does not have reverse() method
    }
}

这道题木我没有做出来。看了答案才想出来。
一开始我写了我的解法,就是看右边的值是否比自己小。小的化就删除自己。后来发现,右边第一个可能比自己大,但是右边第二个比自己小的时候,依然得把自己删除。然后我就不知道怎么继续写了。看了答案。拿Stack来做。很巧妙。
首先,为什么拿Stack来做呢?他的什么特性复合这道题木呢?
我们需要拿后面的字母和前面的作比较,进行操作,而不需要拿前面的字母和后面的字母作比较。所以,栈很合适。
做法是,先用HashMap做一个字典。统计每个字母出现的个数。
然后遍历整个字符串。
当栈为空的时候,直接插入。
非空时,
当插入的元素,栈中已经存在了,那么就舍弃,直接continue;
当插入的元素,比栈顶元素值要小时,判断该栈顶元素的对应counter是否大于1,如果大于1,说明以后还会出现。那么,就把该元素pop出来。然后拿插入元素和新的栈顶元素比较,进行相同操作。直到最后栈空或者栈顶元素小于该插入元素时,就向栈插入该元素。
这一步很关键,他保证了,栈中的元素,从底部到顶部,值是从小到大的。除非某些元素一共只出现了一次,那没办法。
然后新的插入元素出现时,如果栈顶元素,即他左侧的字母,是可以删除的(counter > 1), 那么我们就该选择删除这个元素而不是后面的,因为这样的话,值小的元素就能移动到前端,满足字符顺序。
如果插入元素已经存在在了栈中,那么丢弃该元素,也就是删除该字母而不是前面的相同字母。为什么。因为栈是从大到小排列的,此刻的字符串,字母是从小到大的。删除了栈中的元素,会使得字典顺序变大。

还有一种解法具体我就不研究了。
参考网页:
https://www.hrwhisper.me/leetcode-remove-duplicate-letters/

Anyway, Good luck, Richardo!

My code:

import java.util.HashMap;

public class Solution {
    public String removeDuplicateLetters(String s) {
        if (s == null || s.length() == 0) {
            return s;
        }
        
        HashMap<Character, Integer> helper = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); i++) {
            char curr = s.charAt(i);
            helper.put(curr, i);
        }
        
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        StringBuilder ret = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char curr = s.charAt(i);
            if (!map.containsKey(curr)) {
                ret.append(curr);
                map.put(curr, ret.length() - 1);
            }
            else {
                int pre = map.get(curr);
                int start = -1;
                int end = pre + 1;
                while (end < ret.length()) {
                    if (ret.charAt(end) > ret.charAt(pre)) {
                        if (find(s, i + 1, ret.charAt(end), helper)) {
                            end++;
                        }
                        else {
                            break;
                        }
                    }
                    else {
                        if (start == -1 || ret.charAt(start) > ret.charAt(end)) {
                            start = end;
                        }
                        if (find(s, i + 1, ret.charAt(end), helper)) {
                            end++;
                        }
                        else {
                            break;
                        }
                    }
                }
                if (start != -1) {
                    StringBuilder temp = new StringBuilder(ret.substring(0, pre));
                    map.clear();
                    for (int j = 0; j < pre; j++) {
                        map.put(ret.charAt(j), j);
                    }
                    for (int j = start; j < ret.length(); j++) {
                        temp.append(ret.charAt(j));
                        map.put(ret.charAt(j), temp.length() - 1);
                    }
                    temp.append(curr);
                    map.put(curr, temp.length() - 1);
                    ret = temp;
                }
            }
        }
        
        return ret.toString();
    }
    
    private boolean find(String s, int start, char target, HashMap<Character, Integer> helper) {
        return helper.get(target) >= start;
    }
    
    public static void main(String[] args) {
        Solution test = new Solution();
        String ret = test.removeDuplicateLetters("mitnlruhznjfyzmtmfnstsxwktxlboxutbic");
        System.out.println(ret);
    }
}

没想到之前做过这道题目,可我完全没印象了。。。
然后自己写了出来,提交了好多次。。。许多细节问题没能考虑到。

先说下我的思想。其实也是栈的思想,思路起源于这道题目:
http://www.jianshu.com/p/d6115154c548

他其中的一个子问题,给定 arr[], k, 取出k个数字组成最大的数字,保留原数组顺序。

然后这道题目很像,就是当碰到某些条件需要删除时,什么情况下,可以删除。什么情况下,不能删除。

首先,条件是什么?
条件是,当当前这个字符,在之前已经出现过了。
ok,然后我们就需要考虑,删不删?

我们取出原字符的index,从他后面开始遍历。
这里有个细节,遍历的是之前扫出来的独一无二的string, ret

int pre = map.get(curr);
so end = pre + 1;
while (end < ret.length()) {
....
}

然后对于遍历的每个字符

if (ret.charAt(curr) > ret.charAt(pre)) {
   ...
}
else {
...
}

如果大的话,这个字符肯定不能删,但不代表后面不会出现更小的。
那么我们就需要判断,这个较大的字符,如果删了,后面有没有补充。
即,在 s[i + 1, s.length() - 1] 的范围内,该较大的字符是否仍然存在。
如果是,那么,那么就往后继续走,看看有没有小的。
如果不是,直接跳出循环。

如果小的话,我们需要考虑 start 的赋值。
如果start == -1, 那么直接赋值。
如果start != -1, 那么,如果当前的字符,同样小于原start指向的字符,那么就更新start,否则,就略过。
然后接着判断,是否可以继续扫下去。原理差不多。

然后就差不多了。

然后看了下以前的解法,用Stack的。真的很简洁!
自己重写了下:
My code:

public class Solution {
    public String removeDuplicateLetters(String s) {
        if (s == null || s.length() == 0) {
            return s;
        }
        
        HashMap<Character, Integer> dic = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); i++) {
            char curr = s.charAt(i);
            if (!dic.containsKey(curr)) {
                dic.put(curr, 1);
            }
            else {
                dic.put(curr, dic.get(curr) + 1);
            }
        }
        
        Stack<Character> st = new Stack<Character>();
        for (int i = 0; i < s.length(); i++) {
            char curr = s.charAt(i);
            dic.put(curr, dic.get(curr) - 1);
            if (st.isEmpty()) {
                st.push(curr);
            }
            else if (st.contains(curr)) {
                continue;
            }
            else {
                if (curr > st.peek()) {
                    st.push(curr);
                }
                else {
                    while (!st.isEmpty() && st.peek() > curr && dic.get(st.peek()) > 0) {
                        st.pop();
                    }
                    st.push(curr);
                }
            }
        }
        
        StringBuilder ret = new StringBuilder();
        while (!st.isEmpty()) {
            ret = ret.append(st.pop());
        }
        
        return ret.reverse().toString();
    }
}

Anyway, Good luck, Richardo! -- 08/25/20

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容