Leetcode - Remove Duplicate Letters

Leetcode - Remove Duplicate Letters_第1张图片

My code:

public class Solution {
    public String removeDuplicateLetters(String s) {
        if (s == null || s.length() == 0)
            return s;
        int len = s.length();
        Stack tracker = new Stack();
        HashMap dic = new HashMap(); // 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 helper = new HashMap();
        for (int i = 0; i < s.length(); i++) {
            char curr = s.charAt(i);
            helper.put(curr, i);
        }
        
        HashMap map = new HashMap();
        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 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 dic = new HashMap();
        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 st = new Stack();
        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

你可能感兴趣的:(Leetcode - Remove Duplicate Letters)