打败算法 —— 按摩师

本文参考

出自LeetCode上的题库 —— 按摩师,本篇文章主要是参考LeetCode上的题解@Sweetiee

https://leetcode-cn.com/problems/the-masseuse-lcci/

按摩师问题

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

示例 1:
输入: [1,2,3,1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4

示例 2:
输入: [2,7,9,3,1
输出: 12
解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12

示例 3:
输入: [2,1,4,5,3,1,1,3]
输出: 12
解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12

解题思路

动态规划问题往往就是求最值的问题,并且我们可以看到将大问题转化为小问题的可能性 —— 每个预约都可以选择接与不接,但是不能接相邻的预约,假设当前状态为f(n),n代表第n个请求,f(n)代表从第1个请求到第n个请求为止的最长预约时间,若当前状态不接受预约,则f(n) = f(n - 1),若当前状态接受预约,则f(n) = f(n - 2) + t(n),t(n)代表第n个预约的时间,两种情况下的两种子结构互相独立,都能获取到最优值,因此具备最优子结构性质

那么,当前状态到底应不应该接受请求呢?我们需要对两个子状态进行对比,得到最大值赋给当前状态,即f(n) = Max(f(n - 1),f(n - 2) + t(n)),这也就是状态转移方程。有没有觉得这个式子有点眼熟,是不是很像斐波那契数列?我们知道斐波那契数列的暴力递归算法效率很低,同样的,在这个按摩师问题中,我们也不建议采取暴力递归的算法,下面直接上我们的dp数组解法

但是要自底向上进行dp数组算法的递推,我们还需要定义基状态(base case),在最长公共子序列中我们定义了dp数组的第一行和第一列的值为 空串 或者是 0,在这里我们定义基状态为dp[0] = t(0),第一个预约需要的时间赋给dp数组第一位,dp[1] = Max(t(0),t(1)),第二个预约时间和第一个预约时间相比的最大值赋给dp数组第二位,因为对于仅有两个预约的子问题,就是简单的求出二者中的最大值(按摩师问题的基状态和斐波那契数列问题的基状态也有相似之处,不知注意到没~)

暴力递归

代码:

def force(nums: Array[Int], i: Int): Int = {
  if (i < 0) return 0
  if (i == 0) return nums(0)
  if (i == 1) return Math.max(nums(0), nums(1))

  Math.max(force(nums, i - 1), force(nums, i - 2) + nums(i))
}

def main(args: Array[String]): Unit = {
  val nums = Array(2, 1, 4, 5, 3, 1, 1, 3)
  println(force(nums, nums.length - 1))
}

输出:

12

尽管是暴力递归,但不得不说代码简洁易懂呢

自底向上的dp数组算法

代码:

def massage(nums: Array[Int]): Int = {
  val n = nums.length
  if (n == 0) return 0
  if (n == 1) return nums(0)
  val dp = new Array[Int](n)
  dp(0) = nums(0)
  dp(1) = Math.max(nums(0), nums(1))
  for (i <- 2 until n) {
    dp(i) = Math.max(dp(i - 1), dp(i - 2) + nums(i))
  }
  dp(n - 1)
}

def main(args: Array[String]): Unit = {
  val nums = Array(2, 1, 4, 5, 3, 1, 1, 3)
  println(massage(nums))
}

输出:

12

原本指数级别的时间复杂度,瞬间降到了线性级别,掌握动态规划真是太重要啦

原文地址:https://www.cnblogs.com/kuluo/p/12584820.html