题干描述
给你两个数组 nums
和 andValues
,长度分别为 n
和 m
。数组的 值 等于该数组的 最后一个 元素。
你需要将 nums
划分为 m
个 不相交的连续 子数组,对于第 ith
个子数组 [li, ri]
,子数组元素的按位 AND
运算结果等于 andValues[i]
,换句话说,对所有的 1 <= i <= m
,nums[li] & nums[li + 1] & ... & nums[ri] == andValues[i]
,其中 &
表示按位 AND
运算符。
返回将 nums
划分为 m
个子数组所能得到的可能的 最小 子数组 值 之和。如果无法完成这样的划分,则返回 -1
。
示例 1:
输入: nums = [1,4,3,3,2], andValues = [0,3,3,2]
输出: 12
解释:
唯一可能的划分方法为:
[1,4]
因为1 & 4 == 0
[3]
因为单元素子数组的按位AND
结果就是该元素本身[3]
因为单元素子数组的按位AND
结果就是该元素本身[2]
因为单元素子数组的按位AND
结果就是该元素本身
这些子数组的值之和为 4 + 3 + 3 + 2 = 12
示例 2:
输入: nums = [2,3,5,7,7,7,5], andValues = [0,7,5]
输出: 17
解释:
划分 nums
的三种方式为:
[[2,3,5],[7,7,7],[5]]
其中子数组的值之和为5 + 7 + 5 = 17
[[2,3,5,7],[7,7],[5]]
其中子数组的值之和为7 + 7 + 5 = 19
[[2,3,5,7,7],[7],[5]]
其中子数组的值之和为7 + 7 + 5 = 19
子数组值之和的最小可能值为 17
示例 3:
输入: nums = [1,2,3,4], andValues = [2]
输出: -1
解释:
整个数组 nums
的按位 AND
结果为 0
。由于无法将 nums
划分为单个子数组使得元素的按位 AND
结果为 2
,因此返回 -1
。
题干分析
题干解读
已知存在两个数组nums(长度为n)、andValues(长度为m)。我们需要将数组nums划分为m个不相交的连续子数组,满足以下条件:
- 每个子数组逇按位与(AND)运算结果等于andValues中对应的元素。比如第i个子数组[li,ri]中的所有元素的按位与运算结果应该等于andValues[i]。具体来说就是,对于第i个子数组[li,ri],我们要确保nums[li] & nums[li + 1] & ... & nums[ri] == andValues[i] (其中&是按位与运算符)
所以问题就是我们需要找到一种划分nums的方法,使得这些子数组的最后一个元素的总和最小。如果无法完成这样的划分,则返回-1。举个例子就是:
当输入是nums = [1,4,3,3,2], andValues = [0,3,3,2]时,我们需要将nums划分为四个子数组(由andValues决定),并满足以下四个条件:
- 第一个子数组的按位与结果为0。
- 第二个子数组的按位与结果为3。
- 第三个子数组的按位与结果是3。
- 第四个子数组的按位与结果是2。
那么所存在的唯一可能的划分方法为 :
- [1,4]因为1&4 == 0
- [3]因为3的按位与结果就是3。
- [3]因为3的按位与结果就是3。
- [2]因为2的按位与结果就是2。
那么这些子数组的值之和为:4 + 3 + 3 + 2 = 12。所以输出应该是12。
解题思路
1.动态规划的涉及
采用深度优先搜索(DFS)结合动态规划来解决问题。递归的目标是找到从数组nums的第i个位置开始,能够匹配andValues中第j个值的最小子数组和。
2.递归过程
- 如果i到达nums的末位且j达到andValues的末位,则说明成功匹配,返回0作为最小和。
- 如果i到达nums或j到达andValues的末位丹未完全匹配,返回无穷大表示不可能实现。
- 更新当前的cur值为当前子数组的按位与结果。
- 如果cur的值为当前子数组的按位与结果。
- 如果cur的值小于andValues[j],则说明当前子数组无法满足要求,返回无穷大。
- 尝试继续匹配当前的andValues[j]或者将当前子数组结束,开始匹配下一个andValues[j+1]。
3.哈希表缓存优化
为了避免重复计算,使用哈希表缓存已经计算过的i和j对应的结果,从而减少递归的深度。
完整的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>
#include "uthash.h"//引入uthash库
const int INF = (1 << 20) - 1;//定义一个极大值,用于表示无穷大
//哈希表项结构体定义
typedef struct {
int key;//存储哈希表项的键
int val;//存储哈希表项的值
UT_hash_handle hh;//uthash库所需的哈希句柄
} HashItem;
//查找哈希表中的项
HashItem* hashFindItem(HashItem** obj, int key) {
HashItem* pEntry = NULL;
HASH_FIND_INT(*obj, &key, pEntry);
return pEntry;
}
//向哈希表中添加项
bool hashAddItem(HashItem** obj, int key, int val) {
if (hashFindItem(obj, key))
{
return false;
}
HashItem* pEntry = (HashItem*)malloc(sizeof(HashItem));
pEntry->key = key;
pEntry->val = val;
HASH_ADD_INT(*obj, key, pEntry);
return true;
}
//从哈希表中获取项的值
int hashGetItem(HashItem** obj, int key, int defaultVal) {
HashItem* pEntry = hashFindItem(obj, key);
if (!pEntry)
{
return defaultVal;
}
return pEntry->val;
}
//释放哈希表的内存
void hashFree(HashItem** obj) {
HashItem* curr = NULL, * tmp = NULL;
HASH_ITER(hh, *obj, curr, tmp) {
HASH_DEL(*obj, curr);
free(curr);
}
}
//递归的动态规划函数,用于计算最小值和
int dfs(int i, int j, int cur, const int* nums, int numsSize, const int* andValues, int andValuesSize, HashItem** memo) {
int n = numsSize, m = andValuesSize, key = i * m + j;
if (i == n && j == m)//如果到达数组末尾,且所有andValues都匹配,返回0
{
return 0;
}
if (i == n || j == m)//如果到达任意数组的末位丹未匹配完,返回INF
{
return INF;
}
if (hashFindItem(&memo[key], cur))//如果当前状态以及计算过,之急返回存储的结果
{
return hashGetItem(&memo[key], cur, 0);
}
cur &= nums[i];//更新当前cur值
if ((cur & andValues[j]) < andValues[j])//如果当前值小于目标值,返回INF
{
return INF;
}
int res = dfs(i + 1, j, cur, nums, numsSize, andValues, andValuesSize, memo); // 递归继续寻找
if (cur == andValues[j]) { // 如果当前cur值等于目标andValue,尝试匹配下一个
res = fmin(res, dfs(i + 1, j + 1, INF, nums, numsSize, andValues, andValuesSize, memo) + nums[i]);
}
hashAddItem(&memo[key], cur, res); // 将结果存入哈希表
return res;
}
// 主函数,计算最小值和
int minimumValueSum(int* nums, int numsSize, int* andValues, int andValuesSize) {
int n = numsSize, m = andValuesSize;
HashItem* memo[m * n]; // 创建哈希表数组
for (int i = 0; i < m * n; i++) {
memo[i] = NULL;
}
int res = dfs(0, 0, INF, nums, numsSize, andValues, andValuesSize, memo); // 调用递归函数
for (int i = 0; i < m * n; i++) {
hashFree(&memo[i]); // 释放哈希表内存
}
return res < INF ? res : -1; // 返回结果,如果结果为INF表示无法达到目标
}
// 示例使用
int main() {
int nums[] = { 4, 2, 5 };
int andValues[] = { 4, 2 };
int numsSize = sizeof(nums) / sizeof(nums[0]);
int andValuesSize = sizeof(andValues) / sizeof(andValues[0]);
int result = minimumValueSum(nums, numsSize, andValues, andValuesSize);
printf("最小值和为: %d\n", result);
return 0;
}