这道题作者一开始用的暴力解法,效果还不错,但是对于特别大的数据是过不去的。
先讲一下我暴力的思路:作者用了双指针的解法,我们可以先定义一个数组,就是把数组从1开始考虑,n个连续整数输入到里面去。
然后定义两个指针left和right,一个指向第一个元素,一个指向最后一个元素,然后让这两个数相加。如果nums[left]+nums[right]>target,我们就把right往后移动;如果nums[left]+nums[right]<target,那么就把left往前移(因为我们的初始化数组是有顺序的)。遇到==target的情况,我们需要更换数据:
由于题目中要求我们最小和,我们需要把nums[left]和nums[right]之中比较大的数字更换。那么更换成什么呢?我们知道,数组里面不能出现重复的数字,所以我们只能将这个数字变大,而不能变小,因为变小可能就会引起数组的元素重复了,并且数字变大需要变大到比这个数组的最大元素要大1,这样才能保证最小和。
那么怎么移动呢?这就是语言基础了,我们直接找到需要更改的元素位置,然后将这个元素后面的整体移动覆盖它,再在最后面更换上那个更新后的数字,这样既能保证有序,又能保证最小和(其实这里的思路具有贪心的思想,证明的话不能完全给出)。
这一道题跟某一次leetcode周赛上的题很像,但那个题是可以暴力解出来的,这个题由于数据过大,有10个样例没通过,原因就是时间超出限制。时间复杂度都已经到O(logn)2了,这没办法,思路就到这里了。
class Solution {
public:int minimumPossibleSum(int n, int target) {vector<int>nums(n+1,0);for(int i=0;i<n;i++){nums[i]=i+1;}int left=0;int right=n-1;int st=0;int index=0;while(left<right){if(nums[left]+nums[right]>target)right--;else if(nums[left]+nums[right]<target)left++;else{index=nums[n-1];if(right==n-1)nums[n-1]=index+1;else{st=right;for(int i=st;i<n;i++)nums[i]=nums[i+1];nums[n-1]=index+1;}left=0;right=n-1;}}int sum=0;for(int i=0;i<n;i++){sum=(sum%1000000007+nums[i]%1000000007)%1000000007;}return sum;}
};
至于数学的做法,应该会很好想一点:
以target为基准,我们直接可以判断:1,target-1
2,target-2
...
一直到target/2这里,target/2是直接可以选择的,没必要关乎什么奇数偶数。
前面的那些列举,我们只能选择一个去用,那我们就只选择小的就可以。
这样的话,我们其实已经完成了一部分的答案了,设m=min(target/2,n),显然,在target/2之前的那m个数字我们直接用等差数列的公式计算出来就行了也就是m*(m+1)/2。
至于剩下的那n-m个数,我们知道,刚刚已经从小于等于target的范围里选过了,所以之后需要选择比target大的那一部分了。因此,我们再次使用等差数列的公式就行了:
也就是(n-m)*(target+(target+(n-m)-1)/2,简化一下也就是(n-m)*(2*target+n-m-1)/2。
注意:有些人可能不懂target+(n-m)-1这一部分是啥,因为target后面的部分都是连续的,也就是target+1,target+2...一直加到n-m-1的时候才停止,加上target本身一共n-m个。这个式子就是这样来的。
上代码:
class Solution {
public:int minimumPossibleSum(int n, int target) {long long m=min(target/2,n);return (m*(m+1)+(n-m-1+2*target)*(n-m))/2%1000000007;}
};