题目
给定一个数组,规定从位置 n 到位置 m 一共要走 k 步,求一共有多少种方式,每次只能选一个方向走 1 步,如果到数组边界,只能往反方向走
- 构造递归结构 + 处理 base case,从 n 开始可以往两个方向走,每到一个位置都可以往两个方向走,当走了 k 步且在 m 时满足条件 res + 1;
function getK(arrLen, target, current, reset) {
// 以步数走完为终止条件
if (reset === 0) {
return current === target ? 1 : 0;
}
if (current == arrLen - 1) {
// 到了右边界
return getK(arrLen, target, arrLen - 1, reset - 1);
}
if (current === 0) {
// 到了左边界
return getK(arrLen, target, 1, reset - 1);
}
return (
getK(arrLen, target, current - 1, reset - 1) + // 往左走
getK(arrLen, target, current + 1, reset - 1) // 往右走
);
}
进阶2:记忆化搜索,时间复杂度 O(m x k)
因为 current 位置,剩 k 步,是由 current-1 和 current + 1的结果得来的,所以会有很多重复的子问题求解过程,因为 current 位置存在剩余 k 步的 k 并不固定,所以 dp 是一个二维数组
const dp = [];
const k = 4;
const arr = [1, 3, 2, 5, 4];
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < k; j++) {
dp[i][j] = -1;
}
}
function getK(arrLen, target, current, reset) {
if (dp[current][reset] !== -1) {
return dp[current][reset];
}
// 以步数走完为终止条件
if (reset === 0) {
return (dp[current][reset] = current === target ? 1 : 0);
}
if (current == arrLen - 1) {
// 到了右边界
dp[current][reset] = getK(arrLen, target, arrLen - 1, reset - 1);
} else if (current === 0) {
// 到了左边界
dp[current][reset] = getK(arrLen, target, 1, reset - 1);
} else {
dp[current][reset] =
getK(arrLen, target, current - 1, reset - 1) + // 往左走
getK(arrLen, target, current + 1, reset - 1); // 往右走
}
return dp[current][reset];
}
进阶3:严格表结构,时间复杂度 O(m x k)
相对于记忆化搜索(无位置依赖关系),通过将第一种方式构造出来的递归结构和 base case,以及第二种方式的dp结构,画出二维表,通过每个位置的依赖关系,推算出整个表的值,优先考虑矩阵两边的值,再考虑中间范围的值
基于 base case 进行分析,只有当到达 c 位置,剩余 k 步,且k 为 0 时,取得答案 1 ,所以表的第一个已知内容出来了(0,3)=1,且 0 行其它位置都为 0,因为 c 不想等,假设 c 为 3,k为 3
又根据 base case 里的边界情况和非边界情况,得出表中每个位置的依赖关系
通过依赖关系就能补全整张表的内容,进而求得给定 c、k 的值