问题
小明陪小红去看钻石,他们从一堆钻石中随机抽取两颗并比较她们的重量。这些钻石的重量各不相同。在他们们比较了一段时间后,它们看中了两颗钻石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不连通
}