1675 数组的最小偏移量

数组操作最小偏移量问题解析
这篇博客讨论了如何通过两类操作(偶数除以2,奇数乘以2)找到数组最小偏移量的问题。提供了两种解决方案,一种利用multiset,另一种使用优先队列,通过寻找最大值和最小值的差值来确定最小偏移量。示例展示了具体的操作过程和结果。

题目描述:
给你一个由 n 个正整数组成的数组 nums 。
你可以对数组的任意元素执行任意次数的两类操作:
如果元素是 偶数 ,除以 2
例如,如果数组是 [1,2,3,4] ,那么你可以对最后一个元素执行此操作,使其变成 [1,2,3,2]
如果元素是 奇数 ,乘上 2
例如,如果数组是 [1,2,3,4] ,那么你可以对第一个元素执行此操作,使其变成 [2,2,3,4]
数组的 偏移量 是数组中任意两个元素之间的 最大差值 。
返回数组在执行某些操作之后可以拥有的 最小偏移量 。

示例 1:
输入:nums = [1,2,3,4]
输出:1
解释:你可以将数组转换为 [1,2,3,2],然后转换成 [2,2,3,2],偏移量是 3 - 2 = 1

示例 2:
输入:nums = [4,1,5,20,3]
输出:3
解释:两次操作后,你可以将数组转换为 [4,2,5,5,3],偏移量是 5 - 2 = 3

示例 3:
输入:nums = [2,10,8]
输出:3

提示:
n == nums.length
2 <= n <= 105
1 <= nums[i] <= 109

方法1:
主要思路:
(1)使用multiset来存储各个元素,可以在含有重复元素的提下,进行排序存储;
(2)按照题意,奇数只能乘以2,偶数只能除以2,但为了统一变化方向,可以将奇数先乘以2,放入multiset中,来实现所有的元素向一个方向变化;
(3)初始的multiset中的所有元素是各个元素可能的最大值,则每次找出当前所有元素中的最大值和最小值,计算其差,看该差是否有减小,若差异没减到0或最大值是奇数(既不能够再减小),则说明找到了最小的偏移量;

class Solution {
   
   
public:
    int minimumDeviation(vector<int>& nums) {
   
   
        multiset<int> sort_nums;
        for(int&n:nums){
   
   //初始化multiset元素
            if(n&1){
   
   
                sort_nums.insert(2*n);//奇数的情形
            }
            
### **问题分析** 当数组中存在**最小元素小于 \(-S\)**(\(S\) 为数组总和)时,动态规划的子集和范围需要重新评估。例如: ```cpp nums = [-5, 3, 2]; // 最小元素为-5,总和S=0 ``` 此时: - 理论子集和范围:\([-5, 5]\)(选 \(-5\) 或全选正数 \(3+2=5\))。 - 但若错误地以 \(S=0\) 为中心定义范围(如 \([-S, S] = [0, 0]\)),会遗漏可能的和。 --- ### **关键观察** 1. **子集和的最小值**: - 最坏情况下,子集和的最小值为 **数组最小元素**(如单独选该元素)。 - 最大值为 **数组所有正数的和**(全选正数)。 2. **总和 \(S\) 的误导性**: - \(S\) 是所有元素的和,但子集和的范围可能远超 \([-|S|, |S|]\)。 - 例如:`[-100, 1, 1]` 的 \(S=-98\),但子集和范围是 \([-100, 2]\)。 --- ### **解决方案** #### **1. 动态计算子集和的范围** 无需依赖 \(S\),直接遍历数组计算: - **最小和**:数组最小元素(或部分负数的和)。 - **最大和**:所有正数的和。 ```cpp int min_sum = *min_element(nums.begin(), nums.end()); int max_sum = accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + max(b, 0); }); // 仅累加正数 ``` #### **2. 调整动态规划的范围** 基于 `min_sum` 和 `max_sum` 定义偏移量: ```cpp int offset = -min_sum; // 将最小和映射到0 int max_j = max_sum + offset; // 最大索引 vector<bool> dp(max_j + 1, false); dp[offset] = true; // 初始和为0 ``` #### **3. 完整代码实现** ```cpp #include <vector> #include <algorithm> #include <numeric> #include <climits> using namespace std; bool canSumToTargetWithExtremes(const vector<int>& nums, int target = 0) { // 计算子集和的最小值和最大值 int min_sum = *min_element(nums.begin(), nums.end()); int max_sum = accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + max(b, 0); }); // 调整target到有效范围 if (target < min_sum || target > max_sum) return false; // 动态规划 int offset = -min_sum; int max_j = max_sum + offset; vector<bool> dp(max_j + 1, false); dp[offset] = true; // 和为0可行 for (int num : nums) { vector<bool> new_dp = dp; for (int j = 0; j <= max_j; j++) { if (dp[j]) { int new_j = (j - offset) + num; if (new_j >= min_sum && new_j <= max_sum) { int new_k = new_j + offset; if (new_k >= 0 && new_k <= max_j) { new_dp[new_k] = true; } } } } dp = move(new_dp); if (dp[target + offset]) return true; // 提前终止 } return dp[target + offset]; } ``` --- ### **边界情况处理** 1. **目标超出范围**: - 若 `target < min_sum` 或 `target > max_sum`,直接返回 `false`。 2. **空数组**: - 仅当 `target == 0` 时返回 `true`。 3. **所有元素为负数**: - 如 `nums = [-3, -2]`,`max_sum = 0`(无正数),子集和范围是 \([-3, 0]\)。 --- ### **优化与变种** #### **1. 哈希表法(无需预计算范围)** ```cpp bool canSumToTargetHash(const vector<int>& nums, int target = 0) { unordered_set<int> dp = {0}; for (int num : nums) { unordered_set<int> new_dp = dp; for (int sum : dp) { new_dp.insert(sum + num); } dp = move(new_dp); if (dp.count(target)) return true; } return dp.count(target); } ``` - **优点**:自动处理任意范围的和。 - **缺点**:空间复杂度较高。 #### **2. 统计方案数** 将 `bool` 改为 `int` 计数: ```cpp vector<int> dp(max_j + 1, 0); dp[offset] = 1; // 初始和为0的方案数为1 // 更新逻辑类似,改为 += ``` --- ### **复杂度分析** - **时间复杂度**:\(O(n \cdot \text{range})\),其中 `range = max_sum - min_sum`。 - **空间复杂度**: - 动态规划表:\(O(\text{range})\)。 - 哈希表法:最坏 \(O(2^n)\)(所有子集和唯一时)。 --- ### **总结** - **核心问题**:总和 \(S\) 不能直接界定子集和范围,需单独计算 `min_sum` 和 `max_sum`。 - **关键步骤**: 1. 计算子集和的实际范围(`min_sum` 到 `max_sum`)。 2. 通过偏移量 `offset = -min_sum` 映射到非负索引。 3. 动态规划或哈希表求解。 - **推荐方法**:哈希表法更通用,动态规划表在范围可控时更高效。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值