问:
- 给定一个每一项都是正整数的数组,现在有两个玩家A,B。玩家只可以拿数组的开头或者结尾的数字,A先B后。请问他们能拿到的最高分是多少。
- 一个M * N 的棋盘上,有一个棋子马(马走日),从(0,0)位置出发,走K步到达(x,y)位置,有多少种走法
- 一个正整数数组,数组中每一项代表一个面值,该面值可以选任意张。给一个target,有多少种方法可以累加到target。譬如:arr=[1,2],target=3。可以选3个1,或者1个1+1个2。即一共两种方法
解:
- 递归改DP
// 暴力递归
function smartPeople(arr) {
const left = 0
const right = arr.length - 1
// 先手函数
function firstHand(left, right) {
if (left === right) {
return arr[left]
}
const p1 = arr[left] + secondHand(left + 1, right)
const p2 = arr[right] + secondHand(left, right - 1)
return Math.max(p1, p2)
}
// 后手函数
function secondHand(left, right) {
if (left === right) {
return 0
}
const p1 = firstHand(left + 1, right)
const p2 = firstHand(left, right - 1)
return Math.min(p1, p2)
}
return Math.max(firstHand(left, right), secondHand(left, right))
}
// 递归改dp
function smartPeople (arr) {
const dp1 = []
const dp2 = []
for (let i = 0; i < nums.length; i++) {
dp1[i] = []
dp2[i] = []
dp1[i][i] = nums[i]
dp2[i][i] = 0
}
for (let i = nums.length - 2; i >= 0; i--) {
for (let j = i + 1; j < nums.length; j++) {
dp1[i][j] = Math.max(dp2[i + 1][j] + nums[i], dp2[i][j - 1] + nums[j])
dp2[i][j] = Math.min(dp1[i + 1][j], dp1[i][j - 1])
}
}
return dp1[0][nums.length - 1] >= dp2[0][nums.length - 1]
}
// 暴力递归
function moveMethods(m, n, x, y, k) {
// 当前坐标和剩余步数
function getRes(curX, curY, surplusStep) {
if (curX < 0 || curX > m || curY < 0 || curY > n){
return 0
}
// 从(0,0)到(X,Y)等同于从(X,Y)到(0,0),所以base case也可设置为走到0,0时返回一条结果
if (surplusStep === 0) {
return (curX === 0 && curY === 0) ? 1 : 0
}
const res = getRes(curX + 1, curY + 2, surplusStep-1) +
getRes(curX + 2, curY +1, surplusStep-1)+
getRes(curX + 2, curY - 1, surplusStep-1)+
getRes(curX + 1, curY - 2, surplusStep-1)+
getRes(curX - 1, curY - 2, surplusStep-1)+
getRes(curX - 2, curY - 1, surplusStep-1)+
getRes(curX - 2, curY + 1, surplusStep-1)+
getRes(curX - 1, curY + 2, surplusStep-1)
return res
}
return getRes(x, y, k)
}
// 递归改DP
function moveMethods2(m, n, x, y, k) {
if (x < 0 || x > m || y < 0 || y > n){
return 0
}
const dp = []
// 生成三维表
for (let step = 0; step <= k; step++){
dp[step]=[]
for (let row = 0; row <= m; row++) {
dp[step][row]=[]
}
}
// 根据base case初始化dp表
dp[0][0][0] = 1
// 如下图所示,根据base case可知,在K维度为0的时候,只有(0,0,0)处坐标值是1,其余全部为0
// 根据递归依赖关系,K维度上同一层的数据,只依赖于它的下一层数据,所以K的维度从下往上遍历,每一层都可以计算
// 得到每一个坐标的位置,最后到第K层时,返回(k,x,y)位置的数据即可
for (let step = 1; step <= k; step++) {
for (let row = 0;row <= m; row++) {
for (let col = 0; col <= n; col++) {
dp[step][row][col] = getValue(row + 1, col + 2, step-1)+
getValue(row + 2, col + 1, step-1)+
getValue(row + 2, col - 1, step-1)+
getValue(row + 1, col - 2, step-1)+
getValue(row - 1, col - 2, step-1)+
getValue(row - 2, col - 1, step-1)+
getValue(row - 2, col + 1, step-1)+
getValue(row - 1, col + 2, step-1)
}
}
}
return dp[k][x][y]
function getValue(row, col, step) {
if (row < 0 || row > m || col < 0 || col > n){
return 0
}
return dp[step][row][col] ?? 0
}
}
3.
// 暴力递归
function getAllPlans(arr, target) {
// cur: 当前遍历到的位置 need: 还需要多少钱
function getRes(cur, need) {
if (cur === arr.length) {
return need === 0 ? 1 : 0
}
let res = 0
// 遍历选择取多少张(即当前面值选0张,1张,2张....x张,各自递归下去累加起来)
// 选择的张数*面值不要超过所需要的钱
for (let i = 0; arr[cur] * i <= need; i++) {
res += getRes(cur + 1, need - arr[cur] * i)
}
return res
}
return getRes(0, target)
}
// 递归改DP
function getAllPlans2(arr, target) {
const dp = []
// 创建二维表
for (let i = 0; i <= arr.length; i++) {
dp[i] = []
for (let j = 0 ;j <= target ;j++) {
if (i === arr.length) {
dp[i][j] = 0
}
if (j === 0) {
dp[i][j] = 1
}
}
}
// 由依赖关系可知,每一个数据依赖它下一层数据,所以从下往上,从左往右遍历
for (let i = arr.length -1; i >=0;i--) {
for (let j =0;j<=target;j++) {
// 根据递归写法改造的第一版写法
//dp[i][j]=0
//for (let k = 0; arr[i] * k <= j; k++) {
// dp[i][j] += dp[i + 1][j - arr[i] * k] ?? 0
//}
// 第二版 DP斜率优化
// 右下图关系可以看出,本层依赖的数据虽然是下一层的数据。
//但是本层更靠左的数据,其实已经计算过一次了。所以当前数据就是
//当前位置减去偏移量(面值)对应的项 + 当前位置正下方的项
dp[i][j] = (dp[i][j-arr[i]] ?? 0 ) + dp[i+1][j]
}
}
return dp[0][target]
}