动态规划 n到m走k步的方法数

114 阅读2分钟

题目

给定一个数组,规定从位置 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

image.png

又根据 base case 里的边界情况和非边界情况,得出表中每个位置的依赖关系

image.png

通过依赖关系就能补全整张表的内容,进而求得给定 c、k 的值