【机器人运动问题】
参数n:表示有n个位置 1 ~ n
参数s:机器人初始位置
参数e:机器人目标位置
参数k:机器人必须走k步
机器人每次可往左或往右走,但每次只能走一步
递归调用:
// 一共1~N个位置,目标位置是E,当前位置是cur,还要走rest步
int f(int N, int E, int rest, int cur){
if(rest == 0){
return cur == E ? 1 : 0;
}
if(cur == 1){
return f(N, E, rest - 1, 2);
}
if(cur == N){
return f(N, E, rest - 1, N - 1);
}
return f(N, E, rest - 1, cur - 1) + f(N, E, rest - 1, cur + 1);
}
记忆化搜索:
// 一共1~N个位置,目标位置是E,当前位置是cur,还要走rest步
int f(int N, int E, int rest, int cur, vector<vector<int>>& dp){
if(dp[rest][cur] != -1){
return dp[rest][cur];
}
if(rest == 0){
dp[rest][cur] = cur == E ? 1 : 0;
return dp[rest][cur];
}
if(cur == 1){
dp[rest][cur] = f(N, E, rest - 1, 2, dp);
}
else if(cur == N){
dp[rest][cur] = f(N, E, rest - 1, N - 1, dp);
}
else{
dp[rest][cur] = f(N, E, rest - 1, cur - 1, dp) + f(N, E, rest - 1, cur + 1, dp);
}
return dp[rest][cur];
}
int walkWays(int N, int E, int rest, int cur){
vector<vector<int>> dp(rest + 1, vector<int> (N + 1, -1));
return f(N, E, rest, cur, dp);
}
动态规划:
int dpWay(int N, int E, int rest, int cur){
vector<vector<int>> dp(rest + 1, vector<int>(N + 1, 0));
dp[0][E] = 1;
for(int i = 1; i <= rest; i++){
for(int j = 1; j <= N; j++){
if(j == 1){
dp[i][j] = dp[i - 1][j + 1];
}
else if(j == N){
dp[i][j] = dp[i - 1][j - 1];
}
else{
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
}
}
}
return dp[rest][cur];
}
【题目】
一个正整数数组,每个元素表示一枚硬币的面值,找到总面值为aim的最少个数的硬币。
如 arr = [2, 7, 3, 5, 3] aim = 10 最少个数硬币:2(7 + 3 = 10)
int process(vector<int>& arr, int index, int rest){
if(rest < 0){
return -1;
}
if(rest == 0){
return 0;
}
if(index == arr.size()){
return -1;
}
int p1 = process(arr, index + 1, rest);
int p2 = process(arr, index + 1, rest - arr[index]);
if(p1 == -1 && p2 == -1){
return -1;
}
else if(p1 == -1){
return p2 + 1;
}
else if(p2 == -1){
return p1;
}
else{
return min<int>(p1, p2 + 1);
}
}
int minCoins(vector<int>& arr, int aim){
return process(arr, 0, aim);
}
记忆化搜索:
int process(vector<int>& arr, int index, int rest, vector<vector<int>>& dp){
if(rest < 0){
return -1;
}
if(dp[index][rest] != -2){
return dp[index][rest];
}
if(rest == 0){
dp[index][rest] = 0;
return dp[index][rest];
}
if(index == arr.size()){
dp[index][rest] = -1;
return dp[index][rest];
}
int p1 = process(arr, index + 1, rest, dp);
int p2 = process(arr, index + 1, rest - arr[index], dp);
if(p1 == -1 && p2 == -1){
dp[index][rest] = -1;
}
else if(p1 == -1){
dp[index][rest] = p2 + 1;
}
else if(p2 == -1){
dp[index][rest] = p1;
}
else{
dp[index][rest] = min<int>(p1, p2 + 1);
}
return dp[index][rest];
}
int minCoins(vector<int>& arr, int aim){
vector<vector<int>> dp(arr.size() + 1, vector<int>(aim + 1, -2));
return process(arr, 0, aim, dp);
}
动态规划:
int minCoins(vector<int>& arr, int aim){
int n = arr.size();
vector<vector<int>> dp(n + 1, vector<int>(aim + 1, 0));
for(int row = 0; row <= n; row++){
dp[row][0] = 0;
}
for(int col = 1; col <= aim; col ++){
dp[n][col] = -1;
}
for(int index = n - 1; index >= 0; index--){
for(int rest = 1; rest <= aim; rest++){
int p1 = dp[index + 1][rest];
int p2 = -1;
if(rest - arr[index] >= 0){
p2 = dp[index + 1][rest - arr[index]];
}
if(p1 == -1 && p2 == -1){
dp[index][rest] = -1;
}
else if(p1 == -1){
dp[index][rest] = p2 + 1;
}
else if(p2 == -1){
dp[index][rest] = p1;
}
else{
dp[index][rest] = min<int>(p1, p2 + 1);
}
}
}
return dp[0][aim];
}
递归 -> 记忆化搜索 -> 严格表依赖
1.分析可变参数变化范围
2.标出要计算的终止位置
3.标出直接出答案的位置(根据basecase)
4.推普遍位置是如何依赖其他位置的
5.定出严格表依次计算的顺序
【题目】
给定一个整形数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。返回最后获胜者的分数。
【举例】
arr = [1, 2, 100, 4]
开始时,玩家A只能拿走1或4.如果开始时玩家A拿走1,则排列变为[2, 100, 4],接下来玩家B可以拿走2或4,然后继续轮到玩家A
如果开始时玩家A拿走4,则排列变为[1, 2, 100],接下来玩家B可以拿走1或100,然后继续轮到玩家A
玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100.所以玩家A会先拿1,让排列变为[2, 100, 4],接下来玩家B不管怎么选,100都会被玩家A拿走,玩家A会获胜,分数为101,所以返回101。
int max(int a, int b){
return a > b ? a : b;
}
int s(vector<int>& arr, int l, int j);
int f(vector<int>& arr, int l, int r){
if(l == r){
return arr[l];
}
else{
return max(arr[l] + s(arr, l + 1, r), arr[r] + s(arr, l, r - 1));
}
}
int s(vector<int>& arr, int l, int r){
if(l == r){
return 0;
}
else{
return min(f(arr, l + 1, r), f(arr, l, r - 1));
}
}
int cardsInLine(vector<int> arr){
if(arr.empty()){
return 0;
}
return max(f(arr, 0, arr.size() - 1), s(arr, 0, arr.size() - 1));
}
动态规划:
int dp(vector<int>& arr){
if(arr.empty()){
return 0;
}
vector<vector<int>> f (arr.size(), vector<int>(arr.size(), 0));
vector<vector<int>> s (arr.size(), vector<int>(arr.size(), 0));
for(int i = 0; i < arr.size(); i++){
f[i][i] = arr[i];
}
for(int col = 1; col < arr.size(); col++){
int i = 0;
int j = col;
while(i < arr.size() && j < arr.size()){
f[i][j] = max<int>(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
s[i][j] = min<int>(f[i][j - 1], f[i + 1][j]);
i++;
j++;
}
}
return max<int>(f[0][arr.size() - 1], s[0][arr.size() - 1]);
}
【题目】
象棋棋盘十行九列,假设左下角为(0, 0),右上角为(8, 9),初始时刻棋子“马”在(0, 0),问:该棋子走step步到达(x, y)的方法有多少种?
int process(int x, int y, int step){
if(x < 0 || x > 8 || y < 0 || y > 9){
return 0;
}
if(step == 0){
return (x == 0 && y == 0) ? 1 : 0;
}
return process(x - 1, y - 2, step - 1)
+ process(x - 2, y - 1, step - 1)
+ process(x + 2, y + 1, step - 1)
+ process(x + 1, y + 2, step - 1)
+ process(x + 1, y - 2, step - 1)
+ process(x + 2, y - 1, step - 1)
+ process(x - 1, y + 2, step - 1)
+ process(x - 2, y + 1, step - 1);
}
int getWays(int x, int y, int step){
return process(x, y, step);
}
动态规划:
int getValue(vector<vector<vector<int>>>& dp, int x, int y, int h){
if(x < 0 || x > 8 || y < 0 || y > 9){
return 0;
}
return dp[x][y][h];
}
int dpWays(int x, int y, int step){
if(x < 0 || x > 8 || y < 0 || y > 9 || step < 0){
return 0;
}
vector<vector<vector<int>>> dp (9, vector<vector<int>> (10, vector<int>(step + 1, 0)));
dp[0][0][0] = 1;
for(int h = 1; h <= step; h++){
for(int row = 0; row <= 8; row++){
for(int col = 0; col <= 9; col++){
dp[row][col][h] += getValue(dp, row - 1, col - 2, h - 1);
dp[row][col][h] += getValue(dp, row - 2, col - 1, h - 1);
dp[row][col][h] += getValue(dp, row + 1, col + 2, h - 1);
dp[row][col][h] += getValue(dp, row + 2, col + 1, h - 1);
dp[row][col][h] += getValue(dp, row - 1, col + 2, h - 1);
dp[row][col][h] += getValue(dp, row - 2, col + 1, h - 1);
dp[row][col][h] += getValue(dp, row + 1, col - 2, h - 1);
dp[row][col][h] += getValue(dp, row + 2, col - 1, h - 1);
}
}
}
return dp[x][y][step];
}
【题目】
有N行M列的棋盘,左下角坐标为(0, 0),右上角坐标为(M - 1, N - 1),棋子的初始坐标为(a, b),棋子每次随机从“上下左右”中一个方向走一步,棋子需要走k步,如果棋子某一刻走出棋盘外,棋子死掉。求棋子生存的概率。
long process(int N, int M, int row, int col, int k){
if(row < 0 || row >= N || col < 0 || col >= M){
return 0;
}
if(k == 0){
return 1;
}
long live = process(N, M, row - 1, col, k - 1);
live += process(N, M, row + 1, col, k - 1);
live += process(N, M, row, col - 1, k - 1);
live += process(N, M, row, col + 1, k - 1);
return live;
}
double live(int N, int M, int row, int col, int k){
long all = pow(4, k);
long live = process(N, M, row, col, k);
return (double)(live)/all;
}
动态规划:
double dplive(int N, int M, int a, int b, int k){
vector<vector<vector<long>>> dp (N + 2, vector<vector<long>> (M + 2, vector<long> (k + 1, 0)));
for(int row = 1; row <= N; row++){
for(int col = 1; col <= M; col++){
dp[row][col][0] = 1;
}
}
for(int rest = 1; rest <= k; rest++){
for(int row = 1; row <= N; row++){
for(int col = 1; col <= M; col++){
dp[row][col][rest] = dp[row - 1][col][rest - 1];
dp[row][col][rest] += dp[row + 1][col][rest - 1];
dp[row][col][rest] += dp[row][col - 1][rest - 1];
dp[row][col][rest] += dp[row][col + 1][rest - 1];
}
}
}
long all = pow(4, k);
long live = dp[a + 1][b + 1][k];
return (double)(live) / all;
}
【题目】
一个整形数组arr,元素表示一种货币的面值,货币数量不限。现在要凑出的总面值为aim,求出货币组合的方法数。
int process(vector<int>& arr, int index, int rest){
if(index == arr.size()){
return rest == 0 ? 1 : 0;
}
int ways = 0;
for(int zhang = 0; arr[index] * zhang <= rest; zhang++){
ways += process(arr, index + 1, rest - arr[index] * zhang);
}
return ways;
}
int coinWays(vector<int>& arr, int aim){
return process(arr, 0, aim);
}
动态规划:
int dpCoins(vector<int>& arr, int aim){
if(arr.empty()){
return 0;
}
int n = arr.size();
vector<vector<int>> dp (n + 1, vector<int>(aim + 1, 0));
dp[n][0] = 1;
for(int index = n - 1; index >= 0; index--){
for(int rest = 0; rest <= aim; rest++){
int ways = 0;
for(int zhang = 0; arr[index] * zhang <= rest; zhang++){
ways += dp[index + 1][rest - arr[index] * zhang];
}
dp[index][rest] = ways;
}
}
return dp[0][aim];
}
斜率优化:
int dpCoins(vector<int>& arr, int aim){
if(arr.empty()){
return 0;
}
int n = arr.size();
vector<vector<int>> dp (n + 1, vector<int>(aim + 1, 0));
dp[n][0] = 1;
for(int index = n - 1; index >= 0; index--){
for(int rest = 0; rest <= aim; rest++){
dp[index][rest] = dp[index + 1][rest];
if(rest - arr[index] >= 0){
dp[index][rest] += dp[index][rest - arr[index]];
}
}
}
return dp[0][aim];
}
总结:
1.尝试版本(从左往右试,范围尝试),70%题目
2.记忆化搜索
3.严格表结构动态规划
4.更精致版本dp
可变参数最好是零维,比如整型,不要是数组。