划分数组得到最小值之和
明确:题目中得到的可能的 最小 子数组 值 之和。表示的应该是每个子数组最后一个元素值的和。这样比较符合给出的测试用例。
思路:从nums[0]开始考虑某一个元素是否可以作为最后一个元素,并且计算and&结果正好等于val[j],然后依次递推,当等于val[j]的时候,就需要考虑,以当前元素结尾,不以当前元素结尾,两种情况。
- 以当前元素结尾就进行一次划分
- 不以当前元素结尾就继续向下加一个元素,(前面元素&结果是and_)如果在判断的过程出现,划分剩余元素无法划分出对应val.size()个数组的情况,就直接返回递归的上一层,然后使用记忆的缓存在上一层完成划分
明确了题中要求之后,确定只需要在题目中知道几个参数,当前递归到那个nums参数i,当前划分的对应val数组的那个数,也就是val数组下标j,以及当前划分子数组按位和&的and_值,因为从左向右统计,所以and_是左面所有子数组元素nums[i]&and_的结果。
需要考虑的有选或者不选和递归边界
- 选或者不选
- 不划分,向右继续递归
dfs(i + 1, j, and_)
- 划分,
and_ == val[j]
,dfs(i + 1, j + 1, -1)
,and_ = -1
是因为-1 & n = n
(二进制-1全是1) - 两种情况的最小值就是当前
dfs(i, j, and_)
的结果
- 不划分,向右继续递归
- 边界
n - i < m - j
:前部分是数组剩余划分的元素,后面是目标数组待划分的元素,意思是剩余元素不足以进行划分到m(val.size()),返回一个无穷大的数用来最后确定是否可以完成划分j == m && i < n
:划分完了但是还有元素没有划分,同上返回∞j == m && i == n
:划分完成,返回0
code:
class Solution {
public:
int minimumValueSum(vector<int>& nums, vector<int>& andValues) {
const int INF = INT_MAX / 2;
int n = nums.size(), m = andValues.size();
unordered_map<long long, int> memo;
auto dfs = \
[&](auto&& dfs, int i, int j, int and_) -> int
{
if(n - i < m - j)
return INF;
if(j == m)
return i == n ? 0 : INF;
and_ &= nums[i];
long long mask = \
(long long) i << 36 | (long long) j << 32 | and_;
if(memo.contains(mask))
return memo[mask];
int res = dfs(dfs, i + 1, j, and_);
if(and_ == andValues[j])
res = min(res, dfs(dfs, i + 1, j + 1, -1) + nums[i]);
return memo[mask] = res;
};
int ans = dfs(dfs, 0, 0, -1);
return ans < INF ? ans : -1;
}
};
分解说明
- 规定一个较大的大值,用于以后的比较,
/2
防止+nums[i]越界
const int INF = INT_MAX / 2;
- auto:用于对参数i、j、and_函数的类型推导
- auto&&:用于递归调用自身同时也是右值引用,因为lambda是纯右值使用auto&&也实现了lambda的自递归移动语义。(没有找到这样写的具体解释,应该是c++14引入的lambda参数auto推导)
auto dfs = [&](auto&& dfs, int i, int j, int and_) -> int{...}
if(n - i < m - j)
:位数不足if(j == m) return i == n ? 0 : INF;
:两边都正好处理完所有的元素,返回0否则INFand_ &= nums[i];
:记录最后按位和的结果(因为只关心结果)long long mask = \
:使用二进制把元素放在不同的位掩码位置,使用位掩码在哈希表中存储递归来的最小结果值,左移不同的位置也是为了防止位掩码的冲突- 其余部分分别是判断是否在哈希表(记忆存储)中存在之前的位掩码如果存在就将位掩码对应的res结果返回,不存在这个位掩码的时候就将递归的第一个结果赋值给res,然后当and_和对应位置的值相等的时候,尝试结束当前的子数组并开始下一个子数组,缓存并返回当前状态的最小数组
if(memo.contains(mask))
return memo[mask];
int res = dfs(dfs, i + 1, j, and_);
if(and_ == andValues[j])
res = min(res, dfs(dfs, i + 1, j + 1, -1) + nums[i]);
return memo[mask] = res;
- 其余部分一个是函数入口,一个是判断是否可以完成划分,划分失败的时候,返回-1。