骑士拨号器
1.想法:
根据题意:可得1下一步可以到达的地方为6,8
2可到的地方是7,9
依次类推,可以得到其他的到达序列,
因为第一次到达的是所有点,接下来的过程只是多次重复这个过程。那么我们记录下分别前一次到达的位置的次数,那么下一次所有的可能都是可知的
1.存储位置法:
也就是我们维护一个List,List里面存储的是所有可能到达的节点位置号,初始化为0,1,2,3,4,5,6,7,8,9.然后根据前一次跳到的位置,寻找下一次跳到的位置,例如前一次的0可以跳到4,6,那么下一个List就加上4和6,依次类推,直到坐上N-1次
class Solution {
List nextlist=new ArrayList<>();
int count=0;
static final int INDEX=(int) (Math.pow(10, 9)+7);
public int knightDialer(int N) {
if(N==1) {
return 10;
}
for(int i=0;i<10;i++) {
nextlist.add(i);
}
while(N-->1) {
getMoveCount(nextlist,N);
}
return count%INDEX;
}
private void getMoveCount(List movelist, int n) {
int[] data=new int[] {2,2,2,2,3,0,3,2,2,2};
//最后一步跳了
if(n==1) {
for(int item:movelist) {
count+=data[item];
}
}else {//不是最后一步,寻找下一步的位置
nextlist=new ArrayList<>();
for(int item:movelist) {
for(int next:getNextNumber(item)) {
nextlist.add(next);
}
}
}
}
public List getNextNumber(int number){
List list=new ArrayList<>();
switch (number) {
case 1:
list.add(6);
list.add(8);
break;
case 2:
list.add(7);
list.add(9);
break;
case 3:
list.add(4);
list.add(8);
break;
case 4:
list.add(3);
list.add(9);
list.add(0);
break;
case 5:
break;
case 6:
list.add(1);
list.add(7);
list.add(0);
break;
case 7:
list.add(2);
list.add(6);
break;
case 8:
list.add(1);
list.add(3);
break;
case 9:
list.add(4);
list.add(2);
break;
case 0:
list.add(4);
list.add(6);
break;
}
return list;
}
}
这个方法的复杂度很高,当N=17就超时了
方法二:利用递归
我们第N次的结果就是第N-1次的结果的再处理
例如我们f(0,n-1) = f(4,n-1)+f(6,n-1)
那么可得
/*
* 用来返回n次的,k的次数
*/
private int getSum(int i, int n) {
if(n==1)return 1;
switch (i){
case 0:
return getSum(4,n-1)%mod+getSum(6,n-1)%mod;
case 1:
return getSum(6,n-1)%mod+getSum(8,n-1)%mod;
case 2:
return getSum(7,n-1)%mod+getSum(9,n-1)%mod;
case 3:
return getSum(4,n-1)%mod+getSum(8,n-1)%mod;
case 4:
return getSum(3,n-1)%mod+getSum(9,n-1)%mod+getSum(0,n-1)%mod;
case 5:
return 0;
case 6:
return getSum(1,n-1)%mod+getSum(7,n-1)%mod+getSum(0,n-1)%mod;
case 7:
return getSum(2,n-1)%mod+getSum(6,n-1)%mod;
case 8:
return getSum(1,n-1)%mod+getSum(3,n-1)%mod;
case 9:
return getSum(4,n-1)%mod+getSum(2,n-1)%mod;
}
return -1;
}
最后加起来:
public int knightDialer(int N) {
int sum =0;
for(int i = 0;i<10;i++){
sum=(sum+getSum(i,N))%mod;
}
return sum;
}
结果在161超时
3.数组存储法
就是int[] res = new int[10];代表了现在每个位置的个数
例如res[0]代表了现在在0位置的个数
我们可以预测在下一步的res[0]个数
res[0] = res[4] + res[6];但是不能这样写,因为这样就污染了数据
所以new一个新的数组temp用来存储下一次的数据
所以下一次的数据为:
int[] temp = new int[res.length];
temp[0] = (res[4] + res[6])%mod;
temp[1] = (res[6] + res[8])%mod;
temp[2] = (res[7] + res[9])%mod;
temp[3] = (res[4] + res[8])%mod;
temp[4] = (res[0] + res[3] + res[9])%mod;
temp[5] = 0;
temp[6] = (res[0] + res[1] + res[7])%mod;
temp[7] = (res[2] + res[6])%mod;
temp[8] = (res[1] + res[3])%mod;
temp[9] = (res[2] + res[4])%mod;
for(int j=0;j<10;j++){
res[j] = temp[j]%mod;
}
既然一次做成功了,只需坐上N-1次
public int knightDialer(int N) {
int[] res = new int[10];
for (int i = 0; i < 10; i++) {
res[i] = 1;
}
for (int i = 2; i <= N; i++) {
int[] temp = new int[res.length];
temp[0] = (res[4] + res[6])%mod;
temp[1] = (res[6] + res[8])%mod;
temp[2] = (res[7] + res[9])%mod;
temp[3] = (res[4] + res[8])%mod;
temp[4] = (res[0] + res[3] + res[9])%mod;
temp[5] = 0;
temp[6] = (res[0] + res[1] + res[7])%mod;
temp[7] = (res[2] + res[6])%mod;
temp[8] = (res[1] + res[3])%mod;
temp[9] = (res[2] + res[4])%mod;
for(int j=0;j<10;j++){
res[j] = temp[j]%mod;
}
}
int sum=0;
for(int i=0;i<10;i++){
System.out.println(res[i]);
}
return sum;
但是这个是存在问题的,因为两个数x相加不会超过Integer.MAX_VALUE
三个数会。(因为每个数都取10^9+7的余数,保证2个数相加不会越界)
所以修改所有三个数相加的部分
public int knightDialer(int N) {
int[] res = new int[10];
for (int i = 0; i < 10; i++) {
res[i] = 1;
}
for (int i = 2; i <= N; i++) {
int[] temp = new int[res.length];
temp[0] = (res[4] + res[6])%mod;
temp[1] = (res[6] + res[8])%mod;
temp[2] = (res[7] + res[9])%mod;
temp[3] = (res[4] + res[8])%mod;
temp[4] = ((res[0] + res[3])%mod + res[9])%mod;
temp[5] = 0;
temp[6] = ((res[0] + res[1])%mod + res[7])%mod;
temp[7] = (res[2] + res[6])%mod;
temp[8] = (res[1] + res[3])%mod;
temp[9] = (res[2] + res[4])%mod;
for(int j=0;j<10;j++){
res[j] = temp[j]%mod;
}
}
int sum=0;
for(int i=0;i<10;i++){
System.out.println(res[i]);
}
return sum;
}
OK,编译通过
其实我们每次都不必要进行new一个数组,因为这样的控件开销为O(N),且省去了复制的过程
我们利用二维数组
dp[k][i] 表示k位置的第i次移动所得到的次数
例如dp[0][n] = dp[4][n-1]+dp[6][n-1]
2.代码:
1.利用二维数组,(动态规划)
public int knightDialer(int N) {
int[][] dp = new int[10][N+1];
for (int i = 0; i < 10; i++) {
dp[i][1]=1;
}
for (int i = 2; i <= N; i++) {
dp[0][i] = (dp[4][i-1]+dp[6][i-1])%mod;
dp[1][i] = (dp[6][i-1]+dp[8][i-1])%mod;
dp[2][i] = (dp[7][i-1]+dp[9][i-1])%mod;
dp[3][i] = (dp[4][i-1]+dp[8][i-1])%mod;
dp[4][i] = ((dp[0][i-1]+dp[3][i-1])%mod+dp[9][i-1])%mod;
dp[5][i] = 0;
dp[6][i] = ((dp[0][i-1]+dp[1][i-1])%mod+dp[7][i-1])%mod;
dp[7][i] = (dp[2][i-1]+dp[6][i-1])%mod;
dp[8][i] = (dp[1][i-1]+dp[3][i-1])%mod;
dp[9][i] = (dp[2][i-1]+dp[4][i-1])%mod;
}
int sum=0;
for(int i=0;i<10;i++){
sum=sum+dp[i][N];
sum=sum%mod;
}
return sum;
}
2.利用数组记录
public int knightDialer(int N) {
int[] res = new int[10];
for (int i = 0; i < 10; i++) {
res[i] = 1;
}
for (int i = 2; i <= N; i++) {
int[] temp = new int[res.length];
temp[0] = (res[4] + res[6])%mod;
temp[1] = (res[6] + res[8])%mod;
temp[2] = (res[7] + res[9])%mod;
temp[3] = (res[4] + res[8])%mod;
temp[4] = ((res[0] + res[3])%mod + res[9])%mod;
temp[5] = 0;
temp[6] = ((res[0] + res[1])%mod + res[7])%mod;
temp[7] = (res[2] + res[6])%mod;
temp[8] = (res[1] + res[3])%mod;
temp[9] = (res[2] + res[4])%mod;
for(int j=0;j<10;j++){
res[j] = temp[j]%mod;
}
}
int sum=0;
for(int i=0;i<10;i++){
sum=sum+res[i];
if(sum >= mod)sum= sum%mod;
}
return sum;
}