左神:暴力递归

1.机器人移动

2.最少硬币数

3.马到终点的方法数 

4.最少方法数 


1.机器人移动

问题:

int N:N个格子(1~N)

int s:开始位置

int E:结束位置

int k:机器人必须走k步

机器人移动方式:向左或向右一格,但不能越界

求:总共有几种方式,用k步从s到e

方法一:普通版
/*
N:一共是N个位置
E:终点
rest:还剩rest步要走,rest初始值其实就是k
cur:当前位置
反回方法数
*/
int f1(int N, int E, int rest, int cur) {
	if (rest == 0) {
		return cur == E ? 1 : 0;
	}
	//防止越界,强行干预机器人移动
	if (cur == 1) return f1(N, E, rest - 1, 2);
	if (cur == N)return f1(N, E, rest - 1, N - 1);
	//中间位置,机器人即可向左走也可向右走
	return f1(N, E, rest - 1, cur - 1) + f1(N, E, rest - 1, cur + 1);
}

int walkWays1(int N, int E, int S, int K) {
	return f1(N, E,K,S);
}
方法一的问题:由于f1函数只和rest、cur这两个变量有关,而每个格子(除边界格子)又可能从左边或右边来,因此子
    		过程可能会有重复的f1函数。
时间复杂度分析:
机器人既可往左也可往右,类似二叉树,总共有k步,所以是一棵高度为k的二叉树,那么时间复杂度就是 O(2^k)

方法二:优化版(利用可变参数做二维表)
int f2(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] = f2(N, E, rest - 1, 2, dp);
	}
	else if (cur == N) {
		dp[rest][cur] = f2(N, E, rest - 1, N-1, dp);
	}
	else {//中间位置,机器人即可向左走也可向右走
		dp[rest][cur]= f2(N, E, rest - 1, cur - 1, dp) + f2(N, E, rest - 1, cur + 1, dp);
	}
	return dp[rest][cur];
}

int walkWays2(int N, int E, int S, int K) {
	vector < vector<int>>dp(K + 1, vector<int>(N + 1,-1));
	return f2(N, E,K,S,dp);
}
时间复杂度分析:
dp的大小是k*N,而求每个dp的时间复杂度是 O(1),因此,总时间复杂度就是 O(k*N)

2.最少硬币数

给定不同面额的硬币 coins (硬币数各只有一个)和一个总金额 amount。编写一个函数来计算可以凑成

总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

方法一:普通版
//返回组成rest的最少硬币数
int process1(vector<int>& coins, int index, int rest) {
	if (rest < 0) {//该枚硬币不可取,删去
		return -1;
	}
	if (rest == 0) {//该枚硬币取完之后刚好组成aim,但因为已经算过该枚硬币了,所以这里返回0
		return 0;
	}
	if (index == coins.size()) {//越界,因此多算了一枚,要删去
		return -1;
	}
	int p1 = process1(coins, index + 1, rest);//不取
	int p2Next = process1(coins, index + 1, rest - coins[index]);//取
	if (p1 == -1 && p2Next == -1) {
		return -1;//无效解
	}
	else {
		if (p1 == -1)return 1 + p2Next;
		if (p2Next == -1)return p1;
	}
	return min(p1, 1 + p2Next);
}

int minCoins1(vector<int>& coins, int aim) {
	return process1(coins, 0, aim);
}

方法二:优化版
int process2(vector<int>& coins, 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) {//该枚硬币取完之后刚好组成aim,但因为已经算过该枚硬币了,所以这里返回0
		dp[index][rest] = 0;
	}
	if (index == coins.size()) {//越界,因此多算了一枚,要删去
		dp[index][rest] = -1;
	}
	else {
		int p1 = process2(coins, index + 1, rest, dp);//不取
		int p2Next = process2(coins, index + 1, rest - coins[index], dp);//取
		if (p1 == -1 && p2Next == -1) {
			dp[index][rest] = -1;//无效解
		}
		else {
			if (p1 == -1) {
				dp[index][rest] = 1 + p2Next;
			}
			else if (p2Next == -1) {
				dp[index][rest] = p1;
			}
			else {
				dp[index][rest] = min(p1, 1 + p2Next);
			}
		}
	}
	return dp[index][rest];
}

int minCoins2(vector<int>& coins, int aim) {
	vector<vector<int>>dp(coins.size() + 1, vector<int>(aim + 1, -2));
	return process2(coins, 0, aim, dp);
}

方法三:动态规划
int minCoins3(vector<int>coins, int aim) {
	int N = coins.size();
	vector<vector<int>>dp(N + 1, vector<int>(aim + 1));
	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 p2Next = -1;
			if (rest - coins[index] >= 0) {//防止越界
				p2Next = dp[index + 1][rest - coins[index]];
			}
			if (p1 == -1 && p2Next == -1) {
				dp[index][rest] = -1;
			}
			else {
				if (p1 == -1) {
					dp[index][rest] = p2Next + 1;
				}
				else if (p2Next == -1) {
					dp[index][rest] = p1;
				}
				else {
					dp[index][rest] = min(p1, p2Next + 1);
				}
			}
		}
	}
	return dp[0][aim];
}

3.马到终点的方法数 

问题:

给定一个中国象棋棋盘,马走日,从(0,0)到(a,b)必须走k步的方法数有多少?  

思路:
    可以反着来,从(a,b)到(0,0),step递减,最终看step=0时,是否到达(0,0)

//防止越界访问数组
int getValue(vector<vector<vector<int>>>& dp, int step, int row, int col) {
	if (row < 0 || row>9 || col < 0 || col>8)return 0;
	return dp[step][row][col];
}

int dpWays(int i, int j, int step) {
	if (j < 0 || j>8 || i < 0 || i>9 || step < 0) {
		return 0;
	}
	vector<vector<vector<int>>>dp(step + 1, vector<vector<int>>(10, vector<int>(9)));
	dp[0][0][0] = 1;
	for (int h = 1; h <= step; h++) {//层
		for (int r = 0; r < 10; r++) {
			for (int c = 0; c < 9; c++) {
				dp[h][r][c] = getValue(dp, h - 1, r - 1, c + 2);
				dp[h][r][c] = getValue(dp, h - 1, r - 1, c - 2);
				dp[h][r][c] = getValue(dp, h - 1, r + 1, c + 2);
				dp[h][r][c] = getValue(dp, h - 1, r + 1, c - 2);
				dp[h][r][c] = getValue(dp, h - 1, r - 2, c + 1);
				dp[h][r][c] = getValue(dp, h - 1, r - 2, c - 1);
				dp[h][r][c] = getValue(dp, h - 1, r + 2, c + 1);
				dp[h][r][c] = getValue(dp, h - 1, r + 2, c - 1);
			}
		}
	}
	return dp[step][i][j];
}

4.最少方法数 

给定不同面额的硬币 coins (硬币数各有任意个)和一个总金额 amount。编写一个函数来计算可以凑成

总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。  

方法一:递归版
int process(vector<int>& coins, int index, int rest) {
	if (index == coins.size()) {
		return rest == 0 ? 1 : 0;
	}
	int ways = 0;
	//张数过大就没意义了,因为已经超出rest了
	for (int zhang = 0; coins[index] * zhang < rest; zhang++) {
		ways += process(coins, index + 1, rest - coins[index] * zhang);
	}
	return ways;
}

方法二:动态规划
int way2(vector<int>coins, int aim) {
	if (coins.size() == 0)return 0;
	int N = coins.size();
	vector<vector<int>>dp(N + 1, vector<int>(aim + 1));
	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; coins[index] * zhang <= rest; zhang++) {
				ways += dp[index + 1][rest - coins[index] * zhang];
			}
			dp[index][rest] = ways;
		}
	}
	return dp[0][aim];
}
时间复杂度: O(N*aim^2)

优化版:用临近值代替枚举
int way3(vector<int>coins, int aim) {
	if (coins.size() == 0)return 0;
	int N = coins.size();
	vector<vector<int>>dp(N + 1, vector<int>(aim + 1));
	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-coins[index]){//防止越界
                dp[index][rest]+=dp[index][rest-coins[index]]
            }
		}
	}
	return dp[0][aim];
}
时间复杂度: O(N*aim)

 

总结:

递归(定)=> 记忆搜索 => 严格位置依赖

  1. 可变参数范围

  2. 目标值

  3. base case

  4. 中间范围的依赖关系

  5. 求值顺序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jomo.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值