题目描述
给你一个下标从 0 开始的数组 nums ,它含有 n 个非负整数。
每一步操作中,你需要:
- 选择一个满足
1 <= i < n的整数i,且nums[i] > 0。 - 将
nums[i]减 1 。 - 将
nums[i - 1]加 1 。
你可以对数组执行 任意 次上述操作,请你返回可以得到的 nums 数组中最大值 最小 为多少。
示例 1:
输入:nums = [3,7,1,6]
输出:5
解释:
一串最优操作是:
1. 选择 i = 1 ,nums 变为 [4,6,1,6] 。
2. 选择 i = 3 ,nums 变为 [4,6,2,5] 。
3. 选择 i = 1 ,nums 变为 [5,5,2,5] 。
nums 中最大值为 5 。无法得到比 5 更小的最大值。
所以我们返回 5 。
示例 2:
输入:nums = [10,1]
输出:10
解释:
最优解是不改动 nums ,10 是最大值,所以返回 10 。
提示:
n == nums.length2 <= n <= 1050 <= nums[i] <= 109
解决方案:
问题目标
给定一个数组,可以通过操作(将某个元素减1,同时将其左侧相邻元素加1)来调整数组,目标是最小化调整后数组的最大值。
算法框架
1. 核心思想:二分查找答案
-
我们不知道最小化的最大值是多少,但可以猜测一个值
-
如果猜测值太小,无法通过操作使所有元素不超过它
-
如果猜测值足够大,总能满足要求
-
使用二分查找找到最小的满足条件的值
2. 二分查找部分
-
left初始为 -1(确保答案比它大) -
right初始为数组最大值(这是肯定可行的上限) -
不断二分,检查中间值
mid是否可行 -
最终
right就是最小可行值
3. 关键:check 函数(贪心验证)
检查给定限制 limit 是否可行:
-
从右向左遍历数组(因为只能向左传递数值)
-
维护
dx变量:表示需要向左传递的"超额"部分 -
对于每个位置
i,计算当前值加上传递来的值 -
如果超过
limit,超额部分继续向左传递 -
最后检查第一个元素加上传递来的值是否不超过
limit
直观例子
假设 nums = [3, 7, 1, 6],limit = 5:
-
从右向左处理:
-
i=3: 6 ≤ 5? false,超额1传递给左边
-
i=2: 1 + 1 = 2 ≤ 5,无超额
-
i=1: 7 ≤ 5? false,超额2传递给左边
-
i=0: 3 + 2 = 5 ≤ 5,可行
-
算法特点
-
时间复杂度:O(n log M),其中M是数组最大值
-
空间复杂度:O(1)
-
利用了单调性:如果limit可行,那么更大的limit也一定可行
-
贪心策略最优:从右向左尽量传递超额值是最有效的
函数源码:
cppclass Solution { public: bool check(vector<int>nums,int limit){ long long dx=0; for(int i=nums.size()-1;i>0;i--){ long long newNum=nums[i]+dx; dx=max(newNum-limit,0LL); } return nums[0]+dx<=limit; } int minimizeArrayValue(vector<int>& nums) { int max=*max_element(nums.begin(),nums.end()); int left=-1; int right=max; while(left+1<right){ int mid=(left+right)/2; if(check(nums,mid)){ right=mid; }else{ left=mid; } } return right; } };