原题地址:
https://leetcode-cn.com/problems/chuan-di-xin-xi/
原题如下:
小朋友 A 在和 ta 的小伙伴们玩传信息游戏,游戏规则如下:
有 n 名玩家,所有玩家编号分别为 0 ~ n-1,其中小朋友 A 的编号为 0
每个玩家都有固定的若干个可传信息的其他玩家(也可能没有)。传信息的关系是单向的(比如 A 可以向 B 传信息,但 B 不能向 A 传信息)。
每轮信息必须需要传递给另一个人,且信息可重复经过同一个人
给定总玩家数 n,以及按 [玩家编号,对应可传递玩家编号] 关系组成的二维数组 relation。返回信息从小 A (编号 0 ) 经过 k 轮传递到编号为 n-1 的小伙伴处的方案数;若不能到达,返回 0。
示例 1:
输入:n = 5, relation = [[0,2],[2,1],[3,4],[2,3],[1,4],[2,0],[0,4]], k = 3
输出:3
解释:信息从小 A 编号 0 处开始,经 3 轮传递,到达编号 4。共有 3 种方案,分别是 0->2->0->4, 0->2->1->4, 0->2->3->4。
示例 2:
输入:n = 3, relation = [[0,2],[2,1]], k = 2
输出:0
解释:信息不能从小 A 处经过 2 轮传递到编号 2
限制:
2 <= n <= 10
1 <= k <= 5
1 <= relation.length <= 90, 且 relation[i].length == 2
0 <= relation[i][0],relation[i][1] < n 且 relation[i][0] != relation[i][1]
循环、遍历、递归,这是这题给我的第一想法,当着手去做的时候才发现,脑中虽然能生成几阶的树结构,比如0->2,0>4等,再从这一阶往下进行,会发现这一阶的每一个尾数,作为头数的时候,也会对应一个列表,这导致之后的思路密密麻麻的乱。
着手去做,刚开始尝试用递归去做,做到一半放弃了,因为我发现递归的结束条件没有办法写,也或者是我自己掉入了一个心里死循环。
我发现条件中k是有限制的,心里很开心,还有一个默认条件,就是开头和结尾的两个数字,是确定的,比如上面示例1中,开头0结尾4是确定的,并且总的步数也是确定的,我心想这应该就是一个循环查找并对比的题吧,结果我做了循环查找并对比,针对5种K值做了switch,结果代码如下:
class Solution {
private ArrayList<Integer> findEndList(int[][] relation,int startNum){
ArrayList<Integer> integers = new ArrayList<>();
for (int i = 0; i < relation.length; i++) {
if (relation[i][0]==startNum) {
integers.add(relation[i][1]);
}
}
return integers;
}
private ArrayList<Integer> findStartList(int[][] relation,int endNum){
ArrayList<Integer> integers = new ArrayList<>();
for (int i = 0; i < relation.length; i++) {
if (relation[i][1]==endNum) {
integers.add(relation[i][0]);
}
}
return integers;
}
private boolean isContainNums(int[][] relation,int[] array){
return isContainNums(relation,array[0],array[1]);
}
private boolean isContainNums(int[][] relation,int start,int end){
for (int i = 0; i < relation.length; i++) {
if (relation[i][0]==start&&relation[i][1]==end) {
return true;
}
}
return false;
}
//输入:n = 5, relation = [[0,2],[2,1],[3,4],[2,3],[1,4],[2,0],[0,4]], k = 3
public int numWays(int n, int[][] relation, int k) {
int startIndex=0;
int endIndex=n-1;
int lunci=k;
int[] resultLine=new int[lunci+1];
resultLine[0]=startIndex;
resultLine[resultLine.length-1]=endIndex;
switch (k) {
case 1:
for (int i = 0; i < relation.length; i++) {
if (relation[i][0]==resultLine[0]&&relation[i][1]==resultLine[resultLine.length-1]) {
return 1;
}
}
return 0;
case 2:
ArrayList<Integer> tempList_2_1 = new ArrayList<>();
ArrayList<Integer> tempList_2_2 = new ArrayList<>();
for (int i = 0; i < relation.length; i++) {
if (relation[i][0]==resultLine[0]) {
tempList_2_1.add(relation[i][1]);
}
if (relation[i][1]==resultLine[resultLine.length-1]) {
tempList_2_2.add(relation[i][0]);
}
}
int resultNum_2=0;
for (Integer temp1 :
tempList_2_1) {
for (Integer temp2 :
tempList_2_2) {
if (temp1==temp2) {
resultNum_2++;
break;
}
}
}
return resultNum_2;
case 3://[0,X1,X2,4]
ArrayList<Integer> tempList_3_1 = new ArrayList<>();
ArrayList<Integer> tempList_3_2 = new ArrayList<>();
for (int i = 0; i < relation.length; i++) {
if (relation[i][0]==resultLine[0]) {
tempList_3_1.add(relation[i][1]);
}
if (relation[i][1]==resultLine[resultLine.length-1]) {
tempList_3_2.add(relation[i][0]);
}
}
int resultNum_3=0;
for (int i = 0; i < relation.length; i++) {
int i1 = relation[i][0];
boolean finded=false;
for (Integer temp1 :
tempList_3_1) {
if (i1==temp1) {
for (Integer temp2 :
tempList_3_2) {
int i2 = relation[i][1];
if (temp2==i2) {
resultNum_3++;
finded=true;
break;
}
}
if (finded) {
break;
}
}
}
}
return resultNum_3;
case 4://[0,X1,X2,X3,4]
ArrayList<Integer> tempList_4_1 = new ArrayList<>();
ArrayList<Integer> tempList_4_2 = new ArrayList<>();
for (int i = 0; i < relation.length; i++) {
if (relation[i][0]==resultLine[0]) {
tempList_4_1.add(relation[i][1]);
}
if (relation[i][1]==resultLine[resultLine.length-1]) {
tempList_4_2.add(relation[i][0]);
}
}
int resultNum_4=0;
for (int temp1 :
tempList_4_1) {
ArrayList<Integer> endList = findEndList(relation, temp1);
for (int temp2 :
tempList_4_2) {
ArrayList<Integer> startList = findStartList(relation, temp2);
for (int end :
endList) {
for (int start :
startList) {
if (end==start) {
resultNum_4++;
}
}
}
}
}
return resultNum_4;
case 5://[0,X1,X2,X3,X4,4]
ArrayList<Integer> tempList_5_1 = new ArrayList<>();
for (int i = 0; i < relation.length; i++) {
if (relation[i][0]==resultLine[0]) {
tempList_5_1.add(relation[i][1]);
}
}
int resultNum_5=0;
for (int temp1 :
tempList_5_1) {
ArrayList<Integer> endListX2 = findEndList(relation, temp1);
for (int tempX2 :
endListX2) {
ArrayList<Integer> endListX3 = findEndList(relation, tempX2);
for (int tempX3 :
endListX3) {
ArrayList<Integer> endListX4 = findEndList(relation, tempX3);
for (int tempX4 :
endListX4) {
ArrayList<Integer> endListX5 = findEndList(relation, tempX4);
for (int tempX5 :
endListX5) {
if (tempX5==resultLine[resultLine.length-1]) {
resultNum_5++;
}
}
}
}
}
}
return resultNum_5;
}
return 0;
}
}
结果是对了,但N层嵌套循环实在太折磨人,消耗的内存和耗时也是异常的高,以至于我虽然知道这个多层嵌套循环改写一下,可以用递归来总结,可以让代码看起来少一些,但我也并不想这么做了,因为我觉得我的思路是麻烦的,我还是应该抱着学习的态度,而不是把他做出来的态度来做题。
我看了一下大神的代码:
class Solution {
public int numWays(int n, int[][] relation, int k) {
int [][] dp=new int[k+1][n];
dp[0][0]=1;
for(int i=0;i<k;i++){
for(int []edge:relation){
int src=edge[0],dst=edge[1];
dp[i+1][dst]+=dp[i][src];
}
}
return dp[k][n-1];
}
}
简直是天壤之别,我在验证他的算法时,在思考他为什么会这么写,他为什么能想到,而我为什么想不到这个思路。
经过思考,我将他的思路捋顺了一下,
使用这组数据:
n = 5, relation = [[0,2],[2,1],[3,4],[2,3],[1,4],[2,0],[0,4]], k = 3
用一个图示表示了出来:
用图表示出来感觉清晰了很多,问题转变为:
在确定的步数(纵坐标格数)内,有几种方式从startNum走向endNum,这样理解是没错的,但是如果考虑到这个图了,也未必写得出代码,是个很大的问题。
而大神用了一个巧妙的方式,将确定的步数作为最外循环,每循环一次相当于走一步,每走一步,通过二维数组的遍历,都能读到“当前的这一步作为起始数字,下一步为终点数字,的信息”,然后为下一步的数字格增加当前步有几条路能到达,
比如图中的[k=3,n=4]的格子之所以填3,是因为加了三次1,
假如[k=2,n=3]的格子数字是2(意味着到达这个格子的路有两条),那么[k=3,n=4]的格子就会填写4了。
然后反观原题,解题思路重点有2条对于我来说,
第一条就是,能不能构造出这样易于理解题意的模型;
第二条就是,构建出这样的模型后,能不能根据模型写出高效的代码。