力扣刷题笔记
力扣每日一题1.4有界数组中指定下标的最大值
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
力扣每日一题1.4(有界数组中指定下标的最大值),从思考到各种解决方法。
题目:
给你三个正整数 n、index 和 maxSum 。你需要构造一个同时满足下述所有条件的数组 nums(下标 从 0 开始 计数):
- nums.length == n
- nums[i] 是 正整数 ,其中 0 <= i < n
- abs(nums[i] - nums[i+1]) <= 1 ,其中 0 <= i < n-1
- nums 中所有元素之和不超过 maxSum
- nums[index] 的值被 最大化 返回你所构造的数组中的 nums[index] 。
注意:abs(x) 等于 x 的前提是 x >= 0 ;否则,abs(x) 等于 -x 。
一、题目分析
本题限制条件可理解为以下几个:
- 相邻元素值只有相同或者相邻这两种情况
- 元素的数量小于n
- 得到nums[index]为最大值
- 每个元素必须为正整数,即不能为0
二、解题方法
method1:贪心,从index下标处向两边遍历
即array[index]尽可能大,其他值尽可能小,从中间到两边逐渐递减(注意要求为正整数,所以最小值为1)
代码如下(示例):
class Solution {
public:
int acquireTempMaxSum(int n,int index,int tempMax){
int beforeIndexSum = 0;
if(tempMax > index){
int firstElementVal = tempMax - index;
beforeIndexSum = ((tempMax + firstElementVal) * (index + 1))/2;
}
else{
beforeIndexSum = (1 + tempMax)*tempMax /2 + (index - tempMax + 1);
}
cout << "aa " << beforeIndexSum << endl;
int afterIndexSum = 0;
if(tempMax >= (n - index)){
int lastElementVal = tempMax - (n - index - 1);
afterIndexSum = ((tempMax + lastElementVal)*(n - index))/2;
}
else{
afterIndexSum = (1 + tempMax)*tempMax /2 + n - index - tempMax;
}
cout << "bb " << afterIndexSum << endl;
int tempMaxSum = beforeIndexSum + afterIndexSum - tempMax;
return tempMaxSum;
}
int maxValue(int n, int index, int maxSum) {
int tempMax = 1;
int tempMaxSum = acquireTempMaxSum(n,index,tempMax);
while(tempMaxSum <= maxSum){
cout << tempMaxSum << endl;
tempMax = tempMax + 1;
tempMaxSum = acquireTempMaxSum(n,index,tempMax);
}
return tempMax - 1;
}
};
本代码的arraqy[index]的选择是从1开始不断加1,直到不满足第三个条件(sum <= maxsum),或者从maxsum不断减1,直到满足第三个条件,从逻辑上来说没有错误,可以实现,但时间复杂度超时(为O(maxSum)),因此需要对该方法进行改进,超时测试用例如下图所示:
method2:在method1贪心的基础上加上二分查找(降低时间复杂度)
即对于array[index]的取值不是从1开始累加(或从maxsum开始累减),而是左边界为1,右边界为maxsum,用二分查找查到满足条件的index,时间复杂度会降低到O(lg(maxsum))。
代码如下(示例):
class Solution {
public:
//强制转化符号(long)优先级大于运算符(+-*/)
long acquireTempMaxSum(int n,int index,int tempMax){
long beforeIndexSum = 0;
if(tempMax > index){
int firstElementVal = tempMax - index;
beforeIndexSum = (long)(tempMax + firstElementVal)*(index + 1)/2;
}
else{
beforeIndexSum = (long)(1 + tempMax)*tempMax /2 + (index - tempMax + 1);
}
//cout << "aa " << beforeIndexSum << endl;
long afterIndexSum = 0;
if(tempMax >= (n - index)){
int lastElementVal = tempMax - (n - index - 1);
afterIndexSum = (long)(tempMax + lastElementVal)*(n - index)/2;
}
else{
afterIndexSum = (long)(1 + tempMax)*tempMax /2 + n - index - tempMax;
}
// cout << "bb " << afterIndexSum << endl;
long tempMaxSum = beforeIndexSum + afterIndexSum - tempMax;
return tempMaxSum;
}
int maxValue(int n, int index, int maxSum) {
int tempMax;
int left = 1;
int right = maxSum;
while(left < right){
tempMax = (left + right) /2 + 1;
long tempMaxSum = acquireTempMaxSum(n,index,tempMax);
//cout << "///// " << tempMaxSum << endl;
if(tempMaxSum <= maxSum){
left = tempMax;
}
else{
right = tempMax - 1;
}
}
return left;
}
};
注意:本题的测试用例比较大,接近(int)的上线,因此很多步骤都需要进行强制转换,把int类型转化成long类型,否则会溢出,出现tempMaxSum值为负值的情况。改进后的贪心算法可以成功通过所有测试用例。
method3:数学推导
一边的元素和情况总共只有两种
- 记nums[index]为big,它离数组的某个边界距离为length,当big<= length+1时,还未到达边界,元素值就已降为1,并保持1直到边界,此时元素和为(1+big-1)(big-1)/2 + length - (big-1),合并之后为(big^2-3big)/2 + length + 1.。
- 若big > length+1时,则元素和为(big - 1 + big -length) * length / 2,合并后即(2 * big - 1 - length) * length / 2
数组之和由三部分组成,nums[index],index左边的数组元素之和,index右边的数组元素之和。左边的元素个数为left=index,右边元素个数为right = n - 1 - index。根据对称性,设left < right,则根据big由小到大可以用三种情况来表示:
- big <= left + 1,numsSum = (big^2-3big)/2 + left + 1 + big + (big^2-3big)/2 + right + 1,此时left + 1 >= big > 0,若big取最大值left + 1时,numsSum >= maxSum,则可证明,big必在此区间中,所以此时可合并成big^2 - 2*big + left + right + 2 - numsSum = 0,numsSum赋值为maxSum解该一元二次方程,向下取整,即可得到big在此种情况下的解。
- 若right + 1 >= big >= left + 1,则numsSum = (2 * big - left - 1)left / 2+ big + (big^2-3big)/2 + right + 1,同理判断big= right+1的上界,如若大于等于maxSum,则将numsSum = maxSum,求该一元二次方程,合并后该一元二次方程为0=big^2/2 + (left - 1/2)*big + right+ 1-maxSum-(left^2 + left) / 2,可求出big的值。
- 若前两种情况均不满足,则big > right + 1,此时numsSum = (2 * big - left - 1)left / 2 + big + (2 * big - right - 1)right / 2,求解方程即可,big = (2maxSum + left^2 + left +right^2 + right)/(2(left + right +1))。
代码如下所示
class Solution {
public:
int maxValue(int n, int index, int maxSum) {
double left = index;
double right = n - index - 1;
if (left > right) {
double temp = left;
left = right;
right = temp;
}
double upper = ((double) (left + 1) * (left + 1) - 3 * (left + 1)) / 2 + left + 1 + (left + 1) + ((left + 1) * (left + 1) - 3 * (left + 1)) / 2 + right + 1;
if (upper >= maxSum) {
double a = 1;
double b = -2;
double c = left + right + 2 - maxSum;
return (int) floor((-b + sqrt(b * b - 4 * a * c)) / (2 * a));
}
upper = ((double) 2 * (right + 1) - left - 1) * left / 2 + (right + 1) + ((right + 1) * (right + 1) - 3 * (right + 1)) / 2 + right + 1;
if (upper >= maxSum) {
double a = 1.0 / 2;
double b = left + 1 - 3.0 / 2;
double c = right + 1 + (-left - 1) * left / 2 - maxSum;
return (int) floor((-b + sqrt(b * b - 4 * a * c)) / (2 * a));
} else {
double a = left + right + 1;;
double b = (-left * left - left - right * right - right) / 2 - maxSum;
return (int) floor(-b / a);
}
}
};
时间复杂度为O(1)
总结
本题主要三种解法,但其实主要的解题思路还是贪心算法。第一种是纯粹的贪心算法,method2是为了优化时间复杂度,在贪心算法的基础上增加了二分查找。method3比较难想到,根据数学公式的推导,从而进一步降低时间复杂度。