網易16年研發實習生筆試題 - 比較重量

問題

小明陪小紅去看鑽石,他們從一堆鑽石中隨機抽取兩顆並比較她們的重量。這些鑽石的重量各不相同。在他們們比較了一段時間後,它們看中了兩顆鑽石g1和g2。現在請你根據之前比較的信息判斷這兩顆鑽石的哪顆更重。

給定兩顆鑽石的編號g1,g2,編號從1開始,同時給定關係數組vector,其中元素爲一些二元組,第一個元素爲一次比較中較重的鑽石的編號,第二個元素爲較輕的鑽石的編號。最後給定之前的比較次數n。請返回這兩顆鑽石的關係,若g1更重返回1,g2更重返回-1,無法判斷返回0。輸入數據保證合法,不會有矛盾情況出現。


輸入樣例

2,3,[[1,2],[2,4],[1,3],[4,3]],4


輸出樣例

1


JavaCode

import java.util.*;
import org.junit.Test;

public class CompareWeight {

    @Test
    public void test() {
        int g1 = 5;
        int g2 = 1;
        int n = 6;
        int[][] records = new int[n][];
        records[0] = new int[]{1,2};
        records[1] = new int[]{2,4};
        records[2] = new int[]{1,3};
        records[3] = new int[]{4,5};
        records[4] = new int[]{6,5};
        records[5] = new int[]{4,6};

        System.out.println(cmp(g1, g2, records, n));
    }

    /**
     * 算法思想:
     * 
     * 已知輸入二元組數據包含了若干對鑽石之間的重量關係,同一顆鑽石可能與不同的多顆鑽石已經確定了重量關係,
     * 根據重量大小關係的傳遞性,我們有可能知道任意兩顆鑽石之間的重量關係。因此可以爲每個二元組中第一個編號
     * 的鑽石分別建立一棵多叉樹,其孩子節點就是其在二元組中對應的第二個編號的鑽石,即比它輕的鑽石。爲了判斷
     * g1和g2之間的重量關係,那麼可以從這些樹中逐層搜索,即從以g1爲根節點的樹開始逐層遍歷其所有子孫節點,
     * 如果g2出現在子孫節點中,那麼g1比g2重,類似地,從以g2爲根節點的樹開始逐層遍歷其所有子孫節點,如果g1
     * 出現在子孫節點中,那麼g2比g1重,否則無法判斷。
     * 
     */

    public int cmp(int g1, int g2, int[][] records, int n) {

        Map<Integer,ArrayList<Integer>> map = new HashMap<>();

        //將n組輸入存入map中,合併相同的key,即將爲每顆鑽石構造一棵多叉樹描述重量關係
        for(int i=0; i < n; ++i) {
            int key = records[i][0];
            if(map.containsKey(key)) {
                ArrayList<Integer> value = map.get(key);
                value.add(records[i][1]);
                map.put(key,value);
            }else {
                ArrayList<Integer> value = new ArrayList<Integer>();
                value.add(records[i][1]);
                map.put(key,value);
            }
        }

        LinkedList<Integer> list = new LinkedList<>();//節點隊列,存儲當前遍歷的層的所有節點
        ArrayList<Integer> nodes = new ArrayList<>();//暫存當前被遍歷節點的所有子節點
        Set<Integer> set = new HashSet<>();//記錄已經遍歷過的節點
        Integer keyNode;//當前正被遍歷的節點

        //將根節點g1和層標識位null入隊
        list.add(g1);
        list.add(null);

        //層次遍歷g1的所有子孫節點,直到隊列中只剩下層標識位null
        while(list.peek() != null) {
            //遍歷當前層的所有節點,直到遇到層標誌位null
            while((keyNode = list.poll()) != null) {
                //若當前節點存在子節點,則將其所有子節點入隊,否則繼續遍歷該層其他節點
                if((nodes = map.get(keyNode)) == null) 
                    continue;
                for(Integer ele : nodes) {
                    //將當前節點未被遍歷過,則需要遍歷其子節點
                    if(!set.contains(ele)) {
                        if(ele == g2) return 1;
                        list.add(ele);
                        set.add(ele);
                    }
                }
            }
            //已遍歷完一層,在隊尾設置新的層標誌位
            list.add(null);
        }

        //清空
        list.clear();
        set.clear();


        //將根節點g2和層標識位null入隊
        list.add(g2);
        list.add(null);

        //層次遍歷g2的所有子孫節點
        while(list.peek() != null) {
            while((keyNode = list.poll()) != null) {
                if((nodes = map.get(keyNode)) == null) 
                    continue;
                for(Integer ele : nodes) {
                    if(!set.contains(ele)) {
                        if(ele == g1) return -1;                
                        list.add(ele);
                        set.add(ele);
                    }
                }
            }
            list.add(null);
        }

        //如果沒找到,即無法判斷孰輕孰重
        return 0;
    }
}

說明

  • 本題初看起來不太好處理,首要問題就是如何用合適的數據結構描述各個鑽石之間的重量關係,以及如何通過儘可能少的比較儘快地找到兩顆目標鑽石g1和g2之間的輕重關係。本題的解法參考牛客網@shizheng的解題思路並用java實現;

  • 遍歷樹的每個節點採用層次遍歷的方式,詳細的算法分析可以參考相關問題Leetcode - Binary Tree Level Order Traversal

  • 在遍歷節點的過程中必須要做的一個優化是,避免遍歷重複節點,由於我們遍歷的兩顆“大樹”是由許多“小樹”組合出來的(map中一組key-value就對應一顆小樹),而且“小樹”非常有可能在“大樹”中反覆出現多次。比如,輸入的二元組爲[1,2],[1,3],[2,4],[3,4],[4,5],[4,6],[4,7]…

          1
         / \
        2   3
       /     \
      4       4
     /|\     /|\
    5 6 7   5 6 7
    

    那麼當我們在[2,4]中遍歷過節點4之後,就沒必要在[3,4]中再次遍歷節點4了,因爲在第四層中重複遍歷4的子節點是沒有必要的,如果不做這種去重複處理,算法運行會超時!

  • 另外需要注意一點,代碼中的(nodes = map.get(keyNode)) == null判斷是必不可少的,因爲如果某個編號的鑽石只出現在二元組的第二個位置而從未出現在第一個位置,那麼在map中他就沒有生成對應的“小樹”,那麼取出來就會是null。


更新(2016.08.04)

本題也可以使用Floyd算法(弗洛伊德算法)來解決。Floyd算法一般用於求解加權有向圖中任意兩個節點之間的最短路徑問題,本題中的有向圖沒有權重,而且我們也無需求最短路徑,只需要找到兩個節點之間的通路即可。可以看到,Floyd算法的代碼非常簡潔,而且處理思路容易理解,但是其缺點是三層for循環的計算複雜度較高,對於路徑比較稀疏的多節點有向圖,實際需要的計算量可能並不大,但是卻無法提前結束循環,而上述DFS解法則可以提前結束遍歷。關於Floyd算法的具體介紹可以參考博文【坐在馬桶上看算法】算法6:只有五行的Floyd最短路算法


JavaCode

//使用有向圖的Floyd算法求節點之間的通路
public int cmpByFloyd(int g1, int g2, int[][] records, int n) {
    int maxNum = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
        maxNum = maxNum > records[i][0] ? maxNum : records[i][0];
        maxNum = maxNum > records[i][1] ? maxNum : records[i][1];
    }

    // 初始化有向圖,0表示不連通,1表示連通,注意下標從1開始
    int[][] map = new int[maxNum + 1][maxNum + 1];
    for (int i = 1; i <= maxNum; i++) {
        for (int j = 1; j <= maxNum; j++) {
            if (i == j)
                map[i][j] = 1;// 同一個節點本身就是相通的
        }
    }

    // 建立有向圖,
    for (int i = 0; i < n; i++)
        map[records[i][0]][records[i][1]] = 1;

    //Floyd算法
    for (int k = 1; k <= maxNum; k++) {
        for (int j = 1; j <= maxNum; j++) {
            for (int i = 1; i <= maxNum; i++) {
                //如果節點i可到達k,k可到達j,那麼i可到達j
                if (map[i][k] == 1 && map[k][j] == 1)
                    map[i][j] = 1;
            }
        }
    }


    if (map[g1][g2] == 1)//g1可達g2
        return 1;
    else if (map[g2][g1] == 1)//g2可達g1
        return -1;
    else
        return 0;//g1和g2不連通
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章