力扣刷题之3117.划分数组得到最小的值之和

题干描述

给你两个数组 nums 和 andValues,长度分别为 n 和 m。数组的 值 等于该数组的 最后一个 元素。

你需要将 nums 划分为 m 个 不相交的连续 子数组,对于第 ith 个子数组 [li, ri],子数组元素的按位 AND 运算结果等于 andValues[i],换句话说,对所有的 1 <= i <= mnums[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. [1,4] 因为 1 & 4 == 0
  2. [3] 因为单元素子数组的按位 AND 结果就是该元素本身
  3. [3] 因为单元素子数组的按位 AND 结果就是该元素本身
  4. [2] 因为单元素子数组的按位 AND 结果就是该元素本身

这些子数组的值之和为 4 + 3 + 3 + 2 = 12

示例 2:

输入: nums = [2,3,5,7,7,7,5], andValues = [0,7,5]

输出: 17

解释:

划分 nums 的三种方式为:

  1. [[2,3,5],[7,7,7],[5]] 其中子数组的值之和为 5 + 7 + 5 = 17
  2. [[2,3,5,7],[7,7],[5]] 其中子数组的值之和为 7 + 7 + 5 = 19
  3. [[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;
}

 

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值