动态规划从入门到入坟

本文介绍了动态规划的基本概念和解题步骤,通过数的求和、斐波那契数列等简单示例阐述了动态规划的递推公式和状态转移。接着讨论了打家却舍问题和最长回文子串问题的解决方案,展示了动态规划在解决实际问题中的应用。最后,文章探讨了一个复杂的房屋修建规划问题,展示了解决无法用公式表达的动态规划问题的方法。
摘要由CSDN通过智能技术生成

核心:动态规划(Dynamic Programming)中每一个状态一定是由上一个状态推导出来的

动态规划的解题思路

动态规划步骤

  1. 举例并定义子问题
  2. 递推的规律:即通过子问题确定递推公式或推导逻辑
  3. 初始化
  4. 确定遍历顺序

通过简单数学公式推导的动态规划

有明确的推导公式,可直接运用公式

数的求和

/**
 * 数的求和:求(1+...+n)的和
 * 公式:sum(n) = sum(n-1) + n;
 * 跳出条件:n =1;
 */
// 递归写法
function sum(n) {
  if (n === 1) return 1;
  return sum(n - 1) + n;
}

// Dp
function sumDp(n) {
  let ret = [];
  ret[0] = 0;
  for (let i = 1; i < n + 1; i++) {
    ret[i] = ret[i - 1] + i;
  }
  return ret[n];
}


斐波那契数

/**
 * 斐波那契数列 :求数列的第n项 (1,1,2,3,5....n)
 * 公式:f(n)=f(n-1)+f(n-2)
 * 跳出条件:n=0或者n=1
 */
 // 递归写法
function fibonacci(n) {
  if (n === 0 || n === 1) return 1;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// Dp
function fibonacciDp(n) {
  let ret = [];
  ret[0] = 1;
  ret[1] = 1;
  for (let i = 2; i < n + 1; i++) {
    ret[i] = ret[i - 1] + ret[i - 2];
  }
  return ret[n];
}

通过简单推导的动态规划

打家却舍

1. 问题
参考 leetcode:打家却舍

/**
 * 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,
 * 影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
 * 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
 *
 * 示例:
 * 输入:[1,2,3,1]
 * 输出:4
 * 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
 * 
 */

2. 思路
在这里插入图片描述

3. 解法

function rob(nums) {
  if (nums.length === 0) {
    return 0;
  }
  const n = nums.length;
  const dp = new Array(n + 1);
  dp[0] = 0;
  dp[1] = nums[0];
  for (let k = 2; k <= n; k++) {
    dp[k] = Math.max(dp[k-1], nums[k-1] + dp[k-2]);
  }
  return dp[n];
}

复杂公式的动态规划

1. 题目
参考 leetcode:最长回文子串
在这里插入图片描述
2. 思路
在这里插入图片描述
在这里插入图片描述

3. 解法

function longestPalindrome(s) {
  const len = s.length;
  if (len < 2) {
    return s;
  }

  let maxLen = 1;
  let begin = 0;
  // dp[i][j] 表示 s[i..j] 是否是回文串
  const dp = new Array(len).fill(0).map(() => new Array(len).fill(0));
  // 初始化:所有长度为 1 的子串都是回文串
  for (let i = 0; i < len; i++) {
    dp[i][i] = true;
  }

  // 递推开始
  // 先枚举子串长度
  for (let l = 2; l <= len; l++) {
    // 枚举左边界,左边界的上限设置可以宽松一些
    for (let i = 0; i < len; i++) {
      // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
      const j = l + i - 1;
      // 如果右边界越界,就可以退出当前循环
      if (j >= len) {
        break;
      }

      if (s[i] !== s[j]) {
        dp[i][j] = false;
      } else {
        if (j - i < 3) {
          dp[i][j] = true;
        } else {
          dp[i][j] = dp[i + 1][j - 1];
        }
      }

      // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
      if (dp[i][j] && j - i + 1 > maxLen) {
        maxLen = j - i + 1;
        begin = i;
      }
    }
  }
  return s.substring(begin, begin + maxLen);
}

无法用公式表达的动态规划

1. 题目

城市规划中,房屋需要按一定规划进行修建,规律如下图,根据规律求房子数为n时的排列情况
在这里插入图片描述
2. 思路及解法

function orderHouse(n) {
  // 初始数组 [2, 1, 2], 数组各数加起来是房子总数 n
  let ret = [2, 1, 2];

  if (n > 5) {
    // 开始循环,每次往数组内塞入一个房子
    for (let i = 0; i < n - 5; i++) {
      const max = ret[0]; // 当前每行最多可放置的房子数量
      const length = ret.length;

      // flag1234 表示前面是否没有使用这个房子,使用了后面就不再执行
      let flag1 = true;
      let flag2 = true;
      let flag3 = true;
      let flag4 = true;

      let lj = Math.round(length / 2); // 奇数行数量
      let lk = length - lj; // 偶数行数量

      // 1.奇数行补齐房子 = max
      for (let j = 0; j < length; j += 2) {
        if (ret[j] < max) {
          flag1 = false;
          ret[j] += 1;
          break;
        }
      }

      // 2.奇数行全部补完,偶数补齐房子 = max -1
      if (flag1) {
        for (let k = 1; k < length; k += 2) {
          if (ret[k] < max - 1) {
            flag2 = false;
            ret[k] += 1;
            break;
          }
        }

        // 3.奇偶行全部补完,偶数行需要补齐max或扩充
        if (flag2) {
          if (lj > lk) {
            if (max - 1 > lk) {
              // 3.1 偶数行扩充
              ret.push(1);
              flag3 = false
            } else {
              // 3.2 偶数行需要补齐max
              for (let m = 1; m < length; m += 2) {
                if (ret[m] < max) {
                  flag3 = false;
                  ret[m] += 1;
                  break;
                }
              }
            }
          } else if(lj === lk) {
            // 3.1 偶数行扩充
            ret.push(1);
            flag4 = false
          }

          // 4.前面都不满足,开始横向纵向扩充
          if (flag3 && flag4) {
            ret[0] = ret[0] + 1;
          }
        }
      }
    }
  }

  return ret;
}

总结

寻找规律并一步步得出最后的结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值