第39-40天学习笔记——动态规划DP

  • 动态规划算法的核心就是记住已经解决过的子问题的解
  • 斐波那契数列是一个非常好的题目,既与递归有关,也与DP有关。可以从这个简单问题,思考递归与DP的内在关联。
    • 递归往往是从后往前计算
    • DP往往是从前往后计算
  • 用DP解题,一般要思考三个重要问题。
    • DP数组的定义是什么?
    • 动态转移方程是什么?
    • 如何对DP数组进行初始化?
    • 解答了这三个问题,代码基本上呼之欲出。千万不要拘泥于复杂的定义,要重理解而轻概念。
  • 动态规划(序列DP)
    • 入门问题(理解DP基础概念)
      • LeetCode 509、斐波那契数
        • 传统法一:如果使用递归,需要调用次数呈指数级增长,非常低效!
        • 法二:记忆化搜索/备忘录写法 调用次数呈线性增长,高效
          • 初始化一个长度为N + 1的数组memo,如果发现memo[N]为初始化的值,说明当前memo还没有计算,就执行递归,其实是一种记忆法,计算过的就不需要重复计算了
          • if self.memo[N] == -1:
            • self.memo[N] = self.myFib2(N - 1) + self.myFib2(N - 2)
        • 法三:DP解法
          • 初始化长度为N + 1的数组memo,遍历2到N,我们只考虑当前i位置的值和什么有关,memo[i] = memo[i-1] + memo[i-2]

      • LeetCode 70、爬楼梯(和509题一样)
    • 路径问题
      以LC62. 不同路径为种子题的二维路径问题,本质上大同小异,仅需在种子题的加以简单的修改,即可完成多道题目。
      • 对于路径问题而言,移动方向往往是具有限制条件的,譬如从矩阵的左上方移动到右下方,只能向下和向右移动等等,如果不加这种限制条件,那么较难使用DP算法解题,而应该转而考虑DFS/BFS等搜寻算法来完成。
      • LeetCode 62、不同路径(模板题)
        • 机器人到达(i,j)位置有两种可能,即从(i-1, j)和从(i, j-1)到达
          • 第一步:定义dp数组
            • dp[i][j]表示从第0行第0列到达第 i 行 第 j 列时不同路径的数量

          • 第二步:确定动态转移方程
            • dp[i][j] = dp[i-][j] + dp[i][j-1]
          • 第三步:对DP数组进行初始化
            • dp[0][j] = 1,dp[i][0] = 1 (因为机器人只能向右或者下走)
      • LeetCode 63、不同路径II
        • 和62的模板一样,只是遇到了有障碍物的情况
        • 一旦出现了障碍,那么后面所有的位置都是到达不了的,都是默认的 0
        • 初始化中:遇到障碍,直接break,说明之后的位置都到不了
        • 动态转移方程中:如果此时出现了障碍,那么由于无法到达这个位置,因此不用处理,continue即可
      • LeetCode 64、最小路径和
        • 第一步:定义dp数组
          • dp[i][j]表示从第0行第0列到达第 i 行 第 j 列时最小的路径和
        • 第二步:确定动态转移方程
          • dp[i][j] = dp[i-][j]+当前格子值或dp[i][j-1]+当前格子值
          • 比较上述的两种情况哪个最小,最为dp[i][j]的最终结果

        • 第三步:对DP数组进行初始化
          • dp[0][j] = 1,dp[i][0] = 1 (因为机器人只能向右或者下走)
    • 打家劫舍问题
      • LeetCode198、 打家劫舍(模板题)
        • 定义子问题:从k个房子中能偷到的最大金额
          • 方案一:只偷前k-1间房子,最后一间不偷
          • 方案二:偷前k-2间房子,再偷最后一间
        • 定义DP数组:其中的元素vi为前i个房间中能偷到的最大金额
        • 那么对于每一个vi,我们比较“前i-2的金额(v_i-2)+偷当前房间金额vi”与“前i-1的金额(v_i-1)+不偷当前房间金额”的大小,大的为vi

      • LeetCode213、 打家劫舍II
        • 相比于198题,要加入一个环的处理:我们自己来把这个环断掉,即在不选择第一个房间的情况下,按照198计算一个结果,在不选择最后一个房间的情况下,计算一个结果,比较这两个结果的最大值即为最终的最大金额。

    • 最长递增子序列(LIS)问题(极其重要,掌握理解,面试高频)
      LIS问题是非常经典的序列DP问题,其DP解法不难理解,同时属于高频考题,强烈建议掌握。
      LIS问题还存在更优的贪心+二分查找的解法,属于较难想到、较难理解的思路,感兴趣且学有余力的话可以学习一下。通常而言,DP解法已经足够解决10^4这个数据量级的题目了。
      • LeetCode300. 最长递增子序列 (必懂题)
        • DP数组的定义:设置数组 dp,用来存储 nums 中以每个元素结尾的最长递增序列的程度
        • 首先将数组 dp 里面的值都初始化为 1 ,1 表示以当前元素结尾的最长递增序列的长度为 1 ,即最长递增序列就是当前元素本身

        • 为什么要dp[i] < dp[j] + 1 ?
          • 因为dp[j] + 1表示如果dp[i]采纳了当前的选择会更新的值,那么如果更新的这个值大于了现在的dp[i] 的话,就说明比当前的长度长,所以要更新
      • LeetCode673. 最长递增子序列的个数
        • 和300题的区别在于加入了count[i]: 计算以nums[i]为结尾的LIS的个数
          • 方框内的语句怎么理解?就是说如果当前遍历的dp[j]+1(以nums[j]结尾的最长递增子序列长度加上1,即加上nums[i]后),比dp[i]大,说明我们需要更新dp[i],同时因为更新了,所以“该位置的最长递增子序列的个数”是会变成“以nums[j]结尾的最长递增子序列的个数”的。如果是相等关系,则说明不需要更新,所以“该位置的最长递增子序列的个数”会累加上“以nums[j]结尾的最长递增子序列的个数”

      • LeetCode334. 递增的三元组
        • 这里用300题的代码做(判断maxLength是否≥3)是可以的,但是会超时!因为DP对于10^4这个数据量级的题目有用,但是超过这个范围的数据就不行了,需要加入二分查找、贪心等思想
        • 贪心思想
          • 我们设置最小值、中间值、最大值,当发现最大值(比如4)小于中间值(比如6)的时候,我们就把中间值更新为这个最大值(4)(因为中间值更新为这个较小的值后,我们发现更好地找到正确的最大值,毕竟4要比6小,那么4~6之间的数字就可以当最大值了)

    • 最长公共子序列(LCS)问题(极其重要,掌握理解,面试高频)
      • LCS问题是OD考试非常高频的考点,其难点在于dp数组的构建与定义较难想到。其实用一个二维dp数组来表示两个序列之间的关系,这是一种非常常用的技巧,但如果从来没有见过这样的做法是很难想到应该这样完成的。
      • LC1143. 最长公共子序列和LC718. 最长重复子数组两道题之间的差别仅仅在于序列是否可以连续地取,在思路和代码上是非常类似的。
      • 如果觉得理解上有些困难,那么这类题是可以直接背诵代码的,因为代码不长。
      • LeetCode 718. 最长重复子数组(HJ75. 公共子串计算) (必懂题)(连续的取,所以状态转移方程只能为 dp[i][j] = dp[i - 1][j - 1] + 1)
        • DP数组的定义:
          • dp[i][j]表示A前ⅰ个元素和B前 j 个元素的公共的、长度最长的子数组的长度

          • 那么更新dp状态方程的过程为 dp[i][j] = dp[i - 1][j - 1] + 1

          • # dp[i][j] 表示 nums1 前 i 个元素和 nums2 前 j 个元素的公共的、长度最长的子数组的长度
          • # dp[i - 1][j - 1] 表示 nums1 前 i - 1 个元素和 nums2 前 j - 1 个元素的公共的、长度最长的子数组的长度
        • 整体代码
      • LeetCode 1143. 最长公共子序列(可以跳着取,所以还可以为dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]))
        • 可以跳着取,所以状态转移方程还可以为dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
        • 注意这里不加1,因为第i和j个元素现在是不相同的,也就不能算入dp里

    • 【DP】2023B-跳格子(1) (即LeetCode198、 打家劫舍)
    • 【DP】2023B-跳格子(2) (即LeetCode213、 打家劫舍II)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,关于 jQuery 的事件,我可以和你分享一些学习笔记。 1. 绑定事件 在 jQuery 中,我们可以通过以下方式来绑定事件: ``` $(selector).event(function(){ // 事件处理程序 }) ``` 其中,`selector` 表示要绑定事件的元素,`event` 表示要绑定的事件类型,比如 `click`、`mouseover` 等等。事件处理程序则是在事件触发时要执行的代码块。 2. 多个事件绑定 我们可以通过 `on()` 方法来同时绑定多个事件: ``` $(selector).on({ event1: function(){ // 事件处理程序1 }, event2: function(){ // 事件处理程序2 } }) ``` 这样,当 `event1` 或 `event2` 中任意一个事件触发时,对应的处理程序都会被执行。 3. 解除事件 如果需要解除某个元素的事件处理程序,可以使用 `off()` 方法: ``` $(selector).off(event); ``` 其中,`event` 表示要解除的事件类型。如果不指定事件类型,则会解除该元素上所有的事件处理程序。 4. 事件委托 在 jQuery 中,我们可以使用事件委托来提高性能。事件委托是指将事件绑定到父元素上,而不是绑定到子元素上,然后通过事件冒泡来判断是哪个子元素触发了该事件。这样,当子元素数量较多时,只需要绑定一次事件,就可以监听到所有子元素的事件。 ``` $(selector).on(event, childSelector, function(){ // 事件处理程序 }) ``` 其中,`selector` 表示父元素,`event` 表示要绑定的事件类型,`childSelector` 表示要委托的子元素的选择器,事件处理程序则是在子元素触发事件时要执行的代码块。 以上是 jQuery 中事件的一些基本操作,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值