leetcode_935. Knight Dialer_动态规划_矩阵快速幂
https://leetcode.com/problems/knight-dialer/
在如下图的拨号键盘上,初始在键盘中任意位置,按照国际象棋中骑士(中国象棋中马)的走法走N-1步,能拨出多少种不同的号码。
解法一:动态规划,逆向搜索
class Solution { public: vector<vector<int> > gra{{4,6},{6,8},{7,9},{4,8},{0,3,9}, {},{0,1,7},{2,6},{1,3},{2,4}}; const int mod=1e9+7; int knightDialer(int N) { int res=0; for(int i=0; i<=9; i++) { vector<vector<int>> dp(N+1,vector<int>(10,-1)); dp[0][i]=1; for(int j=0;j<=9;j++) res = (res+dfs(N-1,j,dp))%mod; } return res; } int dfs(int step,int num,vector<vector<int>>& dp) { if(dp[step][num]>=0) return dp[step][num]; if(step==0) return dp[step][num]=0; int ret=0; for(int i=0;i<gra[num].size();i++) ret = (ret + dfs(step-1, gra[num][i], dp))%mod; return dp[step][num]=ret; } };
解法二:动态规划,正向递推
class Solution { public: vector<vector<int> > gra{{4,6},{6,8},{7,9},{4,8},{0,3,9}, {},{0,1,7},{2,6},{1,3},{2,4}}; const int mod=1e9+7; int knightDialer(int N) { int res=0; for(int i=0; i<=9; i++) { vector<vector<int>> dp(N+1,vector<int>(10,0)); dp[0][i]=1; for(int j=1; j<=N-1; j++) for(int k=0; k<=9; k++) for(int l=0; l<gra[k].size(); l++) dp[j][k] = (dp[j][k]+dp[j-1][gra[k][l]])%mod; for(int j=0; j<=9; j++) res = (res+dp[N-1][j])%mod; } return res; } };
问题一:要构造10次二维的vector,很耗时,dp[N][10]空间也有很大浪费。
改进:
将dp[j][k] = (dp[j][k]+dp[j-1][gra[k][l]])%mod;(当前状态由前一时刻状态推得)
改为dp[j+1][gra[k][l]] = (dp[j+1][gra[k][l]]+dp[j][k])%mod;(由当前时刻状态推下一时刻状态)
改进过后可以省去9次构造二维vector的开销,除此之外,递推更加高效(相比之下少了一层for)。
class Solution { public: vector<vector<int> > gra{{4,6},{6,8},{7,9},{4,8},{0,3,9}, {},{0,1,7},{2,6},{1,3},{2,4}}; const int mod=1e9+7; int knightDialer(int N) { int res=0; int dp[5000][10]; //vector<vector<int>> dp(N,vector<int>(10,0)); memset(dp,0,sizeof(dp)); for(int i=0; i<=9; i++) dp[0][i]=1; for(int j=0; j<=N-2; j++) for(int k=0; k<=9; k++) for(int l=0; l<gra[k].size(); l++) dp[j+1][gra[k][l]] = (dp[j+1][gra[k][l]]+dp[j][k])%mod; for(int j=0; j<=9; j++) res = (res+dp[N-1][j])%mod; return res; } };
空间复杂度还没有还没优化,但是可以发现,递推关系只需要两个状态(当前状态和下一步状态),而不需要N个状态。
解法三:动态规划,矩阵快速幂
进一步使用矩阵运算来优化状态的递推关系,同时还可以使用快速幂,使最终时间复杂度优化到O(logN),空间复杂度优化到常数量级。但是C++自己实现矩阵稍微有点麻烦。使用python的numpy非常方便。
class Matrix { public: Matrix(int row, int col); Matrix(vector<vector<int>>& v); Matrix operator * (const Matrix& rh)const; Matrix& operator = (const Matrix& rh); int GetRow(){return row_;} int GetCol(){return col_;}int SumOfAllElements(); ~Matrix(); private: int row_,col_; long long **matrix_; }; Matrix::Matrix(int row, int col) { row_ = row; col_ = col; matrix_ = new long long* [row_]; for(int i=0; i<row_; i++) matrix_[i] = new long long[col_]; for(int i=0; i<row_; i++) for(int j=0; j<col_; j++) matrix_[i][j] = (i==j?1:0); } Matrix::Matrix(vector<vector<int>>& v) { row_ = v.size(); col_ = v[0].size(); matrix_ = new long long* [row_]; for(int i=0; i<row_; i++) matrix_[i] = new long long[col_]; for(int i=0; i<row_; i++) for(int j=0; j<col_; j++) matrix_[i][j] = v[i][j]; } Matrix Matrix::operator * (const Matrix& rh)const { Matrix result(row_,col_); for(int i=0; i<row_; i++) for(int j=0; j<col_; j++) { long long temp=0; for(int k=0; k<col_; k++) { temp += matrix_[i][k]*rh.matrix_[k][j]; temp %= (int)1e9+7; } result.matrix_[i][j] = temp; } return result; } Matrix& Matrix::operator = (const Matrix& rh) { if(this==&rh) return (*this); for(int i=0; i<col_; i++) delete [] matrix_[i]; delete [] matrix_; row_ = rh.row_; col_ = rh.col_; matrix_ = new long long* [row_]; for(int i=0; i<row_; i++) matrix_[i] = new long long[col_]; for(int i=0; i<row_; i++) for(int j=0; j<col_; j++) matrix_[i][j] = rh.matrix_[i][j]; return (*this); } int Matrix::SumOfAllElements() { long long result=0; for(int i=0; i<row_; i++) for(int j=0; j<col_; j++) { result += matrix_[i][j]; result %= (int)1e9+7; } return result; }
Matrix::~Matrix() { for(int i=0; i<col_; i++) delete [] matrix_[i]; delete [] matrix_; } //以上为矩阵类的实现,仅能满足此题方阵乘法,其他的功能性质没有考虑
class Solution { public: const int mod=1e9+7; int knightDialer(int N) { vector<vector<int> > matrix { {0,0,0,0,1,0,1,0,0,0}, {0,0,0,0,0,0,1,0,1,0}, {0,0,0,0,0,0,0,1,0,1}, {0,0,0,0,1,0,0,0,1,0}, {1,0,0,1,0,0,0,0,0,1}, {0,0,0,0,0,0,0,0,0,0}, {1,1,0,0,0,0,0,1,0,0}, {0,0,1,0,0,0,1,0,0,0}, {0,1,0,1,0,0,0,0,0,0}, {0,0,1,0,1,0,0,0,0,0}, }; Matrix matrix1(matrix); Matrix result(matrix1.GetRow(), matrix1.GetCol()); int step = N-1; while(step>0) { if(step&1) result = result * matrix1; step >>= 1; matrix1 = matrix1 * matrix1; } return result.SumOfAllElements(); } };