一:題目
解釋:每次只能移除數組的邊界,移除的邊界的總和為x,要求返回你移除邊界的最小操作數!
也就是說你最少花幾次移除邊界,就能夠讓這些移除的邊界的和為x,則返回這個次數!
所以這個題目,肯定是要考慮三種情況
情況1:x為正數,小于整個數組之和,且有解決方案
例子:
輸入:nums = [1,1,4,2,3], x = 5 輸出:2 解釋:最佳解決方案是移除后兩個元素,將 x 減到 0 。
情況2:x為正數,小于整個數組之和 ,但無解決方案!
輸入:nums = [1,1,4,2,3], x = 8 輸出:-1 解釋:沒有解決方案
本質:x遠大于數組的和
情況3:x為正數,但大于整個數組之和 ,所以無解決方案!
輸入:nums = [1,1,4,2,3], x = 12 輸出:-1 解釋:沒有解決方案 數組總和都才10
本質:x遠大于數組的和
注:x不可能為負數,題目已經規定了x>=1
二:算法
這道題,我們正向解題,是很難的,所以正難則反:
找"一段連續的區域和為sum - x"就可以了
而題目要求的是最小操作數,
所以找的是"最長的連續區域,且區域和為sum - x"
所以上面說過的三種情況,我們就可以進行區分了,假設sum - x的值,用tager來存儲,則:
targe<0? 代表符合情況3直接返回-1,反之則是情況1和情況2,則仍需要通過計算才可以得知是否存在解決方案
解釋:
你只有x不大于數組之和,你是不可能一眼就能看出其有沒有解決方案的,所以我們只能實現定義一個返回值,假設為ret,如果ret從始至終都沒有被更新過,說明其沒有解決方案,反之有解決方案!所以,情況3可以一開始就避免掉,但是情況2和情況1還需根據結果來判斷!
①:暴力
O(N^2),left以不同元素作為起點,right向右遍歷,如果區間的和==targe,則記錄,反之遍歷完了都沒有符合的,則left++,right從left位置開始向右遍歷,繼續判斷....
注:保留的right肯定是從left開始遍歷的,而不是left+1的位置,因為可能left下標的元素就==targe!
②:滑動窗口
暴力能優化的點:
1:left++后,我們的right不需要再回退到left處,而是就在原地!
解釋:
left++是因為之前區間的和>targe了,所以更改起點left的位置來重新找,但是此時left++后,數組有三種情況:
a:數組之和仍大于targe,則right不需要懂,而left還需++
b:數組之和==targe,則判斷更新結果之后,right再++
c:數組之和<targe,則right++
所以綜上所述,right根本不需要回退到left處,只需要先保持不對,對區間的和判斷之后,無非就是先left++,再right++或者直接right++罷了!
所以雙指針同向移動,采用滑動窗口來解決即可!
三:代碼
①:ret為操作數
class Solution {
public:int minOperations(vector<int>& nums, int x) {int ret=INT_MAX;//ret代表操作數 要返回最小的int targe=0;//查找區間的和int sum=0;//數組的總和for(auto a:nums) sum+=a;targe=sum-x;//計算出窗口的目標和 賦給targeif(targe<0) return -1;//對情況3 x大于整個數組和 進行特判for(int left=0,right=0,total=0;right<nums.size();right++){total+=nums[right];//進窗口while(total>targe)//區間不符合我們的要求{total-=nums[left++];//出窗口}if(total==targe)//符合我們的要求ret=min(ret,(n-(right-left+1)));//則判斷更新ret 取最小操作數}return ret==INT_MAX?-1:ret;//有可能ret始終未更新 這就是情況2 反之就是情況1}
};
易錯:
①:情況3,x大于整個數組的情況,一定要在執行滑動窗口算法的之前進行特判,否則while循環中的total一定大于targe,這意味著不管你怎么出窗口,你的left會一直++,直到越界,會報錯
②:ret代表的是最小操作數,所以博主一開始就取了INT_MAX,其次即使不是情況3,也有可能是情況2,所以ret可能從未被更新,仍為INT_MAX,所以若是為INT_MAX,則返回-1,返回代表情況1,直接返回ret就行
③:像left right toal,這種只需要在循環中用到的變量,直接在for循環定義就行,反之其他的變量,不能再for循環中定義
④:更新結果的前提一定是窗口區間的和total==targe,而不是直接更新
⑤:ret代表操作數,所以我們更新ret的時候,是數組總長度-窗口區間長度得到的
有些寫法定義ret為符合要求的窗口區間的長度,所以在返回的時候,有一些變化
②:ret為區間長度
class Solution {
public:int minOperations(vector<int>& nums, int x) {int sum = 0;for (auto a : nums) sum += a;int target = sum - x;// 情況3if (target < 0) return -1;int ret = -1; // 初始化結果為 -1(表示暫時無解)// 滑動窗口:尋找和為 target 的最長子數組for (int left = 0, right = 0, tmp = 0; right < nums.size(); right++) {tmp += nums[right]; // 進窗口while (tmp > target) // 窗口不符合要求tmp -= nums[left++]; // 出窗口if (tmp == target) // 判斷更新ret = max(ret, right - left + 1); // 更新最大窗口長度}// 對情況2特判一下子if (ret == -1) return ret;else return nums.size() - ret;//ret是區間長度 所以操作數應該為總長度減去區間長度}
};
解釋:ret是區間長度,所以判斷更新的時候輕松一點,但是最后返回的時候麻煩一點