(高频) Rearrange String k Distance Apart


Rearrange String k Distance Apart

这道题考法非常多变,很好的题。普通写法

public class Solution {
    public String rearrangeString(String str, int k) {
        if(str==null || str.length()==0 || k<0) return "";
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        HashMap<Character, Integer> lastUsed = new HashMap<Character, Integer>();
        for(int i=0;i<str.length();i++){
            map.put(str.charAt(i), map.getOrDefault(str.charAt(i), 0) +1);
            lastUsed.put(str.charAt(i), -k);
        }

        StringBuilder sb = new StringBuilder();
        int time = 0;
        while(true){
            char selected = findAvailable(map, lastUsed, time, k);
            if(selected ==' ') return "";
            sb.append(selected);
            map.put(selected, map.get(selected)-1);
            lastUsed.put(selected, time);
            time++;
            if(sb.length()==str.length()){
                break;
            }
        }
        return sb.toString();
    }

    public char findAvailable(HashMap<Character, Integer> map, HashMap<Character, Integer> lastUsed, int time, int k){
        int max = Integer.MIN_VALUE; char selected = ' ';
        for(Character key : map.keySet()){
            if(map.get(key)!=0 && map.get(key)>max && time-lastUsed.get(key)>=k){
                selected = key;
                max = map.get(key);
            }
        }
        return selected; 
    }
}

以下的代码虽然慢,实际上在空间上保证了最优,当task有成千上万的时候,我们实际上不用位每个task maintain 一个table,用LinkedHashMap记录大小为K的就可以。

public class Solution {
    public String rearrangeString(String s, int k) {
        HashMap<Character, Integer> freq = new HashMap<Character, Integer>();
        LinkedHashMap<Character, Integer> time = new LinkedHashMap<Character, Integer>(k, 0.75f, true){
            protected boolean removeEldestEntry(Map.Entry eldest){
                return size()>k-1; //key point: k-1
            }
        };
        for(int i=0;i<s.length();i++){
            char c = s.charAt(i);
            freq.put(c, freq.getOrDefault(c,0)+1);
        }
        PriorityQueue<Character> pq = new PriorityQueue<Character>(new Comparator<Character>(){
            public int compare(Character c1, Character c2){
                if(freq.get(c1)==freq.get(c2))
                    return c1-c2;
                else
                    return freq.get(c2)-freq.get(c1); //key point: reverse order
            }
        });
        for(char key : freq.keySet()){
            pq.offer(key);
        }

        StringBuilder sb  = new StringBuilder(); int count = 0;
        while(!pq.isEmpty()){
            char selected = getNext(pq, time, count);
            if(selected==' ') return "";
            sb.append(selected);
            freq.put(selected, freq.get(selected)-1);
            if(freq.get(selected)==0) freq.remove(selected);
            else
                pq.add(selected);
            count++;
        }
        System.out.println(sb.toString());
        return sb.length()==s.length()? sb.toString():"";
    }

    public char getNext(PriorityQueue<Character> pq, LinkedHashMap<Character, Integer> time, int count){
        ArrayList<Character> invalid = new ArrayList<Character>();
        char selected = ' ';
        while(!pq.isEmpty()){
            char tmp = pq.poll();
            if(!time.containsKey(tmp)){
                selected = tmp;
                time.put(tmp, count);
                break;
            }
            invalid.add(tmp);
        }
        pq.addAll(invalid);
        return selected;
    }

}

换句话说,条件允许的时候我们要从堆顶拿元素,但是堆顶元素非法的时候,不一定代表此时无解。

因此我们需要在此基础上加一个 queue,来维护这次循环寻找元素时候的解;如果在任何时刻 maxHeap 里没东西了,但是 queue 里还有,都可以说明此时真的没有合法元素了,返回 "".

改良版的写法建立在下图的逻辑上:

  • 我们在一个 size = str.length 的 char[] 上维护 size = k 的 bin 若干个

  • 对于每个 bin ,里面都放置着最多 k 个 distinct character;

  • 每个 bin 里面元素的顺序存在一种 consistent ordering,即一定会严格按照我们定义的 Tuple 顺序由大到小填充。因此 bin 与 bin 之间一定不会违反 distance constraint;

  • 同时我们的遍历顺序是顺序依次遍历每个 bin,避免类似 AAAABBBBCCCCDDDD ,k = 3 的时候,实际 bin size 应该是 4 ,无脑从头填充会出 bug 的问题。

这种写法适合不适合写自己填空格的 follow-up,有待研究。。至少 AAABBBCCCDDD 这种填充时候需要额外判断一下 “这个 space 到底是不是必要的” 这类问题。

public class Solution {
    public String rearrangeString(String str, int k) {
        if(k==0) return str;
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        for(int i=0;i<str.length();i++){
            if(!map.containsKey(str.charAt(i))) map.put(str.charAt(i), 1);
            else map.put(str.charAt(i),map.get(str.charAt(i))+1);
        }

        PriorityQueue<Character> pq = new PriorityQueue<Character>(new Comparator<Character>(){
            public int compare(Character c1, Character c2){
                return map.get(c1).intValue()==map.get(c2).intValue()? c1.compareTo(c2) : map.get(c2)-map.get(c1); // >0 is ascending order
            }
        });

        for(char c : map.keySet())
            pq.offer(c);

        StringBuilder sb = new StringBuilder();
        int len = str.length();
        while(!pq.isEmpty()){
            int cnt = Math.min(k,len);
            ArrayList<Character> tmp = new ArrayList<Character>(); 
            for(int i=0;i<cnt;i++){
                if(pq.isEmpty()) return ""; //example 3
                Character ch = pq.poll();
                sb.append(ch);
                len--;
                map.put(ch, map.get(ch)-1);
                if(map.get(ch)>0)
                    tmp.add(ch);
            }
            for(char c : tmp)
                pq.offer(c);
        }
        return sb.toString();
    }
}

Task Scheduler

这个允许有idle。撸了最简单的写法,41/64 后TLE。

public class Solution {

    public int leastInterval(char[] tasks, int n) {
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        for(char c : tasks){
            map.put(c, map.getOrDefault(c,0)+1);
        }

        PriorityQueue<Character> pq = new PriorityQueue<Character>(new Comparator<Character>(){
            public int compare(Character c1, Character c2){
                return map.get(c2)-map.get(c1);
            }
        });
        for(Character t :map.keySet()){
            pq.offer(t);
        }

        HashMap<Character, Integer> time = new HashMap<Character, Integer>(); int ans = 0;
        while(!pq.isEmpty()){
            ArrayList<Character> tmp = new ArrayList<Character>();
            Character nextTask = null;
            while(!pq.isEmpty()){
                Character t = pq.poll();
                if(!time.containsKey(t)||ans-time.get(t)>n){
                    nextTask = t; break;
                }
                else{
                    tmp.add(t);
                }
            }
            pq.addAll(tmp);
            if(nextTask!=null){
                time.put(nextTask, ans);
                if(map.get(nextTask)==1) map.remove(nextTask);
                else{
                    map.put(nextTask, map.get(nextTask)-1);
                    pq.offer(nextTask);
                }
            }
            ans++;
        }
        return ans;
    }
}

惊世骇俗简单却没想到的解法,来看例子 AAAABBBEEFFGG 3

Frame: "AXXXAXXXAXXXA"
insert 'B': "ABXXABXXABXXA" <--- 'B' has higher frequency than the other characters, insert it first.
insert 'E': "ABEXABEXABXXA"
insert 'F': "ABEFABEXABFXA" <--- each time try to fill the k-1 gaps as full or evenly as possible.
insert 'G': "ABEFABEGABFGA"

实际这个range是由最频繁的字符控制的,一旦设置了最频繁的字符,这个frame就定下了最基本的框架,有多少个frequent的letter,就有多少个大小为k的bin。最后一个bin比较特殊,有多少个并列的frequent letter,最后就有多少。注意最后要和取原始字符串取max,一种情况“abcd”。

前面在思考这个问题的时候,更多的是站在局部考虑这个问题,下一步取什么;而这种approach站在一个大局在考虑问题,先设计好框架,再填数,甚至不填数字直接计算。

public class Solution {
    public int leastInterval(char[] tasks, int n) {

        int[] c = new int[26];
        for(char t : tasks){
            c[t - 'A']++;
        }
        Arrays.sort(c);
        int i = 25;
        while(i >= 0 && c[i] == c[25]) i--;

        return Math.max(tasks.length, (c[25] - 1) * (n + 1) + 25 - i);
    }
}

(FB) 不改顺序的 cooldown 输出

第二轮,老外面试官,给一个String, 如AABACCDCD, 插入''使同一个字母间隔为k: 如果k=3: A - - - A B - - A C - - - C D - - C D, 一开始理解有误,认为是要先shuffle字母顺序然后插入'',花了不少时间,然后面试官提示字母顺序不变。

刚刚面完,欧洲小哥,发上来攒人品 Tasks: 1, 1, 2, 1

cooldown: 2

Output: 7 (order is 1 - - 1 2 - 1)

Tasks: 1, 2, 3, 1, 2, 3

(cooldown): 3

Output: 7 (order is 1 2 3 - 1 2 3)

Tasks: 1, 2, 3 ,4, 5, 6, 2, 4, 6, 1, 2, 4

(cooldown): 6

Output: 18 (1 2 3 4 5 6 - - 2 - 4 - 6 1 - 2 - 4)

 public static String schedule(int[] tasks, int k){
        // Key : task number
        // Value : index of last appearance
        HashMap<Integer, Integer> map = new HashMap<>();
        StringBuilder sb = new StringBuilder();
        int timestamp = 0;

        for(int i = 0; i < tasks.length; i++){
            int task = tasks[i];
            if(!map.containsKey(task)){
                sb.append("" + task);
            } else {
                while(timestamp - map.get(task) <= k){
                    sb.append("_");
                    timestamp ++;
                }
                sb.append("" + task);
            }
            map.put(task, timestamp++);
        }

        return sb.toString();
    }

    public static void main(String[] args){
        int[] tasks1 = new int[]{1,2,3,1,2,3};
        int cooldown1 = 3;

        int[] tasks2 = new int[]{1, 2, 3 ,4, 5, 6, 2, 4, 6, 1, 2, 4};
        int cooldown2 = 6;

        int[] tasks3 = new int[]{1,2,3,1,2,3};
        int cooldown4 = 3;

        System.out.println(schedule(tasks2, cooldown2));

http://www.1point3acres.com/bbs/forum.php?mod=viewthread&tid=137479&extra=page%3D1%26filter%3Dsortid%26sortid%3D311&page=1

给定任务AABCB, 冷却时间k(相同任务之间的最短距离时间),任务顺序不能变,问完成任务的总时间。

例子:AABCB, k = 3, A _ _ A B C _ B, 时间为8.

解法:用hashtable保存上次的时间。

Followup1:如果k很小,怎么优化? 解法:之前的hashtable的大小是unique task的大小,如果k很小,可以只维护k那么大的hashtable

Followup2: 如果可以改变任务的顺序,最短的任务时间是多少? 例子:AABBC, K=2, AB*ABC, 时间为6.

解法:根据每个任务出现的频率排序,优先处理频率高的。但是具体细节没有时间讨论。

(FB) 变种:任务调度,生成“最优”的任务序列,可以用空格占位;

  • 问题一:返回最优长度

  • 问题二:返回带空格的最优序列

  • follow up:https://instant.1point3acres.com/thread/167190在这个基础上,已知cooldown会很小,可以视作constant,task的type会很多,让我减少空间复杂度 (这个是 task 顺序已经给好不需要动的情况)

results matching ""

    No results matching ""