前端算法必刷题系列[93]

933 阅读3分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

168. 分割等和子集 (partition-equal-subset-sum)

标签

  • 中等

题目

leetcode 传送门

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1

输入: nums = [1,5,11,5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2

输入: nums = [1,2,3,5]
输出: false
解释: 数组不能分割成两个元素和相等的子集。

基本思路

看上去是可以分割成小问题的问题都能用动态规划或者递归解决。

动态规划这篇我们了解到动态规划的基本步骤是下面三步:

  1. 寻找最优子结构(状态表示)
  2. 归纳状态转移方程(状态计算)
  3. 边界初始化

我们还是根据之前的基本步骤来

  1. 状态表示:

要使和为整个数组和的一半, 我们想 就定一个 halfSum = sum / 2

那下面还是用dp[i][j] 来看, 其实我们可以设 从数组的[0, i] 这个子区间内选取若干元素,令和等于 j

那么 dp[n][halfSum] 就是我们要求的最终答案

  1. 状态转移方程:

接下来就是遍历每一个数, 这又有点像背包问题, 遇到每个数有两个选择, 放入背包和不放入。

i 从 index 的[0...n] 遍历

  • 不选取 nums[i]: dp[i][j] = dp[i-1][j]
  • 选取 nums[i]: dp[i][j] = dp[i-1][j - nums[i]]
  1. 边界初始化:
  • 上面选择有个边界前提 j - nums[i] >= 0
  • halfSum 必须是整数
  • 如果不选取任何正整数,则被选取的正整数等于 0。因此对于 i[0...n] 都有 dp[i][0] = true

写法实现

var canPartition = function(nums) {
  let len = nums.length
  if (len < 2) return false;
  let sum = nums.reduce((cur, acc) => cur + acc, 0)
  // 判断奇偶性
  if (sum % 2 !== 0) return false
  // 取一半
  let halfSum = sum / 2
  // 建立 dp[i][j]
  let dp = new Array(len).fill(0).map(it => new Array(halfSum + 1).fill(false))

  // 边界, 从 i ~ n 一个不选的话,和为 0 肯定满足
  for (let i = 0; i < len; i++) {
    dp[i][0] = true;
  }
  for (let i = 1; i < len; i++) {
    for (let j = 1; j <= halfSum; j++) {
      if (j >= nums[i]) {
        // || 或者,只要有一个成立,说明整能找到一个,能找到一个就成
        dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
      } else {
        // 如果 j < nums[i] 当前元素比和都大,那还加入啥,只能期待等于前面的结果有找到的
        dp[i][j] = dp[i - 1][j];
      }
    }
  }
  return dp[len - 1][halfSum]
};

nums = [1, 5, 11, 5]
console.log(canPartition(nums))

注释什么的我觉得已经写清楚了。

今天是8月更文的最后两天,看上去动态规划有很多种类了,我们稍微对这种类型的问题总结下。

首先我们如何想到这个题目用动态规划来做呢?

我的思路就是,当我其他数据结构或算法我一下子反应不过来,或者感觉不太好做,条件要求考虑太多的时候,又遇到问题中有 求最XX, 怎样XX最好,还有没啥简单思路字符串问题,我就会考虑到用动态规划的时间又到了。

分析动态规划问题,要从最后的结果入手,反推出你的状态定义,如何设置,能让你能轻易表示出最后的答案结果呢,比如这题就是怎么定义让你能表示出 dp[len-1][halfSum] 就是结果

然后就根据条件来进行小问题求解往大问题上转化,如何进行状态转移,其实就是找递推公式

最后思考边界条件,一半都是 0 行 0 列 有问题

最后,画图是解决这类问题的好方式,画图辅助思考事半功倍,而且优化拍平降维操作画图更直观。

另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友 Or 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考