2369. 检查数组是否存在有效划分
来源第 305 场周赛 Q3难度中等分数1779
题目描述
给你一个下标从 0 开始的整数数组 nums ,你必须将数组划分为一个或多个 连续 子数组。
如果获得的这些子数组中每个都能满足下述条件 之一 ,则可以称其为数组的一种 有效 划分:
- 子数组 恰 由
2个相等元素组成,例如,子数组[2,2]。 - 子数组 恰 由
3个相等元素组成,例如,子数组[4,4,4]。 - 子数组 恰 由
3个连续递增元素组成,并且相邻元素之间的差值为1。例如,子数组[3,4,5],但是子数组[1,3,5]不符合要求。
如果数组 至少 存在一种有效划分,返回 true ,否则,返回 false 。
示例 1:
输入:nums = [4,4,4,5,6] 输出:true 解释:数组可以划分成子数组 [4,4] 和 [4,5,6] 。 这是一种有效划分,所以返回 true 。
示例 2:
输入:nums = [1,1,1,2] 输出:false 解释:该数组不存在有效划分。
提示:
2 <= nums.length <= 1051 <= nums[i] <= 106
解法
方法一:记忆化搜索
思考
划分由「两相等」「三相等」或「三连续递增」组成。 \(n \le 10^5\),递归划分会重叠。从下标 \(i\) 出发是否合法只取决于 \(i\) 之后的后缀。
记忆化 \(dfs(i)\):尝试长度 \(2\) 或 \(3\) 的合法块并跳到块末。越界视为成功。
我们设计一个函数 \(dfs(i)\),表示从下标 \(i\) 开始是否存在一种有效划分。那么答案就是 \(dfs(0)\)。
函数 \(dfs(i)\) 的执行过程如下:
- 如果 \(i \ge n\),返回 \(true\)。
- 如果 \(i\) 和 \(i+1\) 下标的元素相等,那么可以选择将 \(i\) 和 \(i+1\) 作为一个子数组,递归调用 \(dfs(i+2)\)。
- 如果 \(i\), \(i+1\) 和 \(i+2\) 下标的元素相等,那么可以选择将 \(i\), \(i+1\) 和 \(i+2\) 作为一个子数组,递归调用 \(dfs(i+3)\)。
- 如果 \(i\), \(i+1\) 和 \(i+2\) 下标的元素依次递增 \(1\),那么可以选择将 \(i\), \(i+1\) 和 \(i+2\) 作为一个子数组,递归调用 \(dfs(i+3)\)。
- 如果上述情况都不满足,返回 \(false\),否则返回 \(true\)。
即:
为了避免重复计算,我们使用记忆化搜索的方法。
时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为数组的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
方法二:动态规划
思考
方法一从末尾填后缀, \(f[i]\) 表示从下标 \(i\) 起能否划分。同样三种块也可以按前缀来写: \(f[i]\) 表示前 \(i\) 个元素能否划分,由 \(f[i-2]\) 与 \(f[i-3]\) 转入。
自左向右填表,答案为 \(f[n]\)。时间仍为 \(O(n)\)。
方法一从末尾判断后缀能否划分。下面改为判断前缀,转移仍是同样的三种块。
设 \(f[i]\) 表示数组的前 \(i\) 个元素是否存在一种有效划分,初始时 \(f[0] = true\),答案就是 \(f[n]\)。
状态转移方程如下:
时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为数组的长度。
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
方法三:动态规划
思考
划分只能是两相等、三相等或三连续递增。 \(n \le 10^5\),按块枚举会重叠,而且两相等的块总会先尝试。
全相等时这次尝试连成一条长度为 \(n/2\) 的调用链,栈会溢出。
更靠后的下标在从末尾往前走时已经就绪。令 \(f[i]\) 表示从下标 \(i\) 起能否划分, \(f[n]\) 为真,从 \(i=n-1\) 填到 \(0\)。合法块仍是跳到 \(i+2\) 或 \(i+3\)。
令 \(f[i]\) 表示从下标 \(i\) 开始是否存在一种有效划分。答案为 \(f[0]\), \(f[n] = true\)。
从 \(i = n - 1\) 填到 \(0\)。两相邻元素相等时,可以接上 \(f[i + 2]\);三个元素相等,或依次递增 \(1\) 时,可以接上 \(f[i + 3]\)。三种情况取或,都不成立则为 \(false\):
时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为数组的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |