划分数组得到最小值之和(leetcde.3117)

划分数组得到最小值之和

明确:题目中得到的可能的 最小 子数组 之和。表示的应该是每个子数组最后一个元素值的和。这样比较符合给出的测试用例。

思路:从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否则INF
  • and_ &= 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。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值