欧拉回路,Hierholzer算法


Reconstruct Itinerary

这题其实和我之前用 DFS 处理 topological sort 的代码非常像,主要区别在于存 graph 的方式不同,这里是一个 String 直接连着对应的 next nodes,而且形式是 min heap:

  • 原题给的是 edges,所以图是自己用 hashmap 建的。

    • min heap 可以自动保证先访问 lexicographical order 较小的;

    • 同时 poll 出来的 node 自动删除,免去了用 List 的话要先 collections.sort 再 remove 的麻烦。

    • 这种以 “edge” 为重心的算法多靠 heap,比如 dijkstra.

一次AC, 想清楚了代码很简单

public class Solution {
    public List<String> findItinerary(String[][] tickets) {
        HashMap<String, PriorityQueue<String>> map = new HashMap<String, PriorityQueue<String>>();
        for(String[] t: tickets){
            if(!map.containsKey(t[0])) map.put(t[0], new PriorityQueue<String>());
            map.get(t[0]).offer(t[1]);
        }

        Stack<String> stack = new Stack<String>();
        List<String> ans = new ArrayList<String>();
        stack.push("JFK");
        while(!stack.isEmpty()){
            PriorityQueue<String> pq = map.get(stack.peek());
            if(pq==null || pq.isEmpty()){
                ans.add(0, stack.pop());
            }
            else
                stack.push(pq.poll());
        }
        return ans;
    }
}
public class Solution {
    public List<String> findItinerary(String[][] tickets) {
        LinkedList<String> list = new LinkedList<>();

        HashMap<String, PriorityQueue<String>> map = new HashMap<>();
        for(String[] ticket : tickets){
            if(!map.containsKey(ticket[0])) map.put(ticket[0], new PriorityQueue<String>());

            map.get(ticket[0]).add(ticket[1]);
        }

        dfs(list, "JFK", map);

        return new ArrayList<String>(list);
    }

    private void dfs(LinkedList<String> list, String airport, HashMap<String, PriorityQueue<String>> map){
        while(map.containsKey(airport) && !map.get(airport).isEmpty()){
            dfs(list, map.get(airport).poll(), map);
        }
        list.offerFirst(airport);
    }
}

密码锁破解序列

首先你要弄明白你面对的是一个神马密码锁,它的特性是这样的: 一个长度为n=4的密码框,一个键盘有k=10个按键,分别是0~9。 你输入0000,系统会验证0000是否是正确密码。 这时候你再输入一个1,不同的密码锁有不同的处理方式 一种会reset,也就是说前面的4个0消失了,你需要继续输入4个数供系统判断。 另一种不会reset,它会验证最近键入的4歌数字,即0001。 我们面对的是后一种。

如果是前一种的话,没啥好想的,破解序列就是4*10000 但是后一种,可以做到长度为10003的破解序列覆盖所有可能的10000个4位数。 题目就是让你找到这个序列。

我觉得这道题在题意明确的情况下,把所有状态看成点,状态之间的转移看成边是比较自然的。 这样就有两种看法,一种就是把4位看成状态,一种就是把3位看成状态。 把4位看成状态的图上面找Hamilton回路,很显然是本题的答案,因为访问了每一个节点一次且只有一次。 把3位看成状态的图上面找欧拉回路可能需要给面试官解释一下。但我觉得还是比较好解释的。 因为每一条边其实代表了一种4位的状态,于是就很好解释了。 那么上面的DFS找欧拉回路的算法就是相当简单有效的解法了。在删除边和查找下一个没有访问的边的复杂度是O(1)的情况下这个算法的复杂度是O(E)的,也就是O(k^n)的,de Buijin构造算法不会比这个复杂度更好。 我这个实现没有用LinkedList或者Hash来保存边的信息,所以每次都是循环所有可能的边,也就是O(k)查找边,所以总的复杂度是O(k^(n+1))。 考虑到 k = 10 n = 4 我觉得没啥问题。+

参考资料

http://poj.org/problem?id=1780

http://xwk.iteye.com/blog/2129621

https://en.wikipedia.org/wiki/Lyndon_word

https://en.wikipedia.org/wiki/De_Bruijn_graph

public class EulerSolution {
    int n, k, v;
    int[] edge;
    StringBuilder sequence;

    public String crackSequence(int k, int n) {
        this.n = n;
        this.k = k;
        v = 1; for (int i = 0; i < n - 1; ++i) v *= k; // vertices are n-1 bit radix k number.
        edge = new int[v]; // edge list
        sequence = new StringBuilder();
        dfs(0);
//        for (int i = 0; i < n - 1; ++i) sequence.append(0); 
        return sequence.reverse().toString();
    }

    // Hierholzer's algorithm
    private void dfs(int u) {
        while (edge[u] != k) {
            int i = edge[u]++;
            dfs((u * k + i) % v);
            sequence.append(i);
        }
    }

    public static void main(String[] args) {
        System.out.println(new EulerSolution().crackSequence(9, 4).length());
    }
}

results matching ""

    No results matching ""