DP問題求解之連續子序列
continous subarrays類型問題是求數組中連續子序列是否滿足某些條件的類型問題。
入門級連續子序列
首先先從最簡單的連續子數組問題開始,題目詳見leetcode 53 Maximum Subarray。
在給定數組(數組中的元素有正有負)中找到有最大sum和的連續子數組。用status[i]記錄以第i個元素結尾的,連續子數組的sum和,那么很容易寫出狀態方程:
status[i] = status[i-1] > 0?(status[i-1]+nums[i]):nums[i]
如果前一個的status[i-1]是負的,那只會對當前的status[i]產生負的影響,所以應直接舍棄,并從當前元素開始重新計算。
注意循環求status數組的時候使用變量記錄最大sum和,最終返回即可。
參考代碼如下:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int status[n+1];
status[0] = 0;
int maxValue = nums[0];
for(int i = 1;i<=nums.size();i++){
status[i] = max(status[i-1],0)+nums[i-1];
if(status[i] > maxValue){
maxValue = status[i];
}
}
return maxValue;
}
略升級連續子序列
和上面的題型類似,但稍微有一點,題目參考leetcode 152. Maximum Product Subarray。
給定數組中有正有負,得到乘積最大的子序列的值。計算乘積和計算和不同,由于負負得正,所以不但要記錄以元素i結尾的最大子序列的值,也需要記錄最小子序列的值。寫出狀態轉移方程如下:
status(i,0) = max(status(i-1,0) * nums(i),max(nums(i),status(i-1,1) * nums(i)))
status(i,1) = min(status(i-1,0) * nums(i),min(nums(i),status(i-1,1) * nums(i)))
這樣就很容易寫出代碼,參考代碼如下:
int maxProduct(vector<int>& nums) {
int status[nums.size()][2];
status[0][0] = nums[0] > 0?nums[0]:0;
status[0][1] = nums[0] < 0?nums[0]:0;
int maxValue = nums[0];
for(int i = 1;i<nums.size();i++){
status[i][0] = max(status[i-1][0] * nums[i],max(status[i-1][1]*nums[i],nums[i]));
status[i][1] = min(status[i-1][0]*nums[i],min(status[i-1][1]*nums[i],nums[i]));
if(status[i][0] > maxValue){
maxValue = status[i][0];
}
}
return maxValue;
}
滿足條件的連續子序列(一)
下面介紹對要滿足某一條件的連續子序列的求解方法,如leetcode 560 Subarray Sum Equals K。嚴格的來說這不是一道DP問題,但是這道題的思路對后面問題的求解有借鑒意義。
求出給定數組中連續子序列的和為K的子序列個數。采用prefix sum的方法(prefix sum就是當前元素及其之前元素的和),用一個map記錄所有元素的prefix sum和對應個數(由于數組中元素可為負,所以會出現某幾個元素的prefix sum相等的情況)。記某個元素的前綴和為sum(i),若sum(i)-K出現在map中則說明存在某個子序列其和為K。這時候只需要將map[sum(i)-k]的值加入count中即可。
參考代碼如下:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int,int> res;
int count = 0;
int sum = 0;
res[0] = 1;
for(int i = 0;i<nums.size();i++){
sum += nums[i];
if(res.find(sum-k) != res.end()){
count += res[sum-k];
}
res[sum]++;
}
return count;
}
滿足條件的連續子序列(二)
有了上一道題的思路基礎,更進一步的來看參考leetcode 523. Continuous Subarray Sum。
給定一個非負數組,判斷數組中是否有連續子序列的和為K的整數倍,且子序列的元素個數應大于等于2。這道題的特殊情況比較多。
還是利用prefix sum記錄前綴和,但不同的是,對前綴和和K進行余數處理并保存在map中,如果發現余數在map中曾經出現過則說明,其中必有子序列是K的整數倍,但這里還需要對下標間隔進行判斷,所以map中的key為余數,value為當前元素的下標。
特殊情況的說明:
- k可以為0,當k為0的時候,求余運算會出錯
- 數組中的元素可以為0,0%任何數都為0,即整除。
由于這道題個人覺得可參考的地方很多,下面給出代碼和解釋:
bool checkSubarraySum(vector<int>& nums, int k) {
unordered_map<int, int> res;
int sum = 0;
//初始化這個值為-1,避免在數組中只有兩個元素時出錯。
res[0] = -1;
for(int i = 0;i<nums.size();i++){
sum += nums[i];
if(k != 0){
sum = sum % k;
}
//這里一定要注意只有在此余數沒有出現過的時候更新res,避免當數組中的元素都為0的時候一直在更新res中的值,而返回false。
if(res.find(sum) != res.end()){
//如果模擬過前面思路的計算過程,就會發現真正連續子序列是從res[sum]+1開始的,所以限制條件里沒有等于。
if(i - res[sum] > 1){
return true;
}
}
else{
res[sum] = i;
}
}
return false;
}
滿足條件的連續子序列(三)
類似的還有使連續子序列的乘積滿足某個條件的,如leetcode 713.Subarray Product Less Than K。
在給定的正數數組中返回所有乘積小于K的子序列的個數。采用滑動窗口,窗口內的元素乘積小于K,并使用start記錄滑動窗口的頭部下標,一旦滑動窗口中的元素的乘積大于等于K了,則就需要將start向后移動。
需要注意的是在移動過程中對滿足條件的子序列的個數的記錄,實際上滑動窗口每增加一個元素,相當于增加了(i-start+1)個滿足條件子序列,按此規律進行累加。
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int count = 0;
int product = 1;
int start = 0;
for(int i = 0;i<nums.size();i++){
product *= nums[i];
while(start <= i && product >= k){
product /= nums[start];
start++;
}
count += i - start + 1;
}
return count;
}
滿足條件的連續子序列(四)
和上面的那道題思路很類似,也是使用滑動窗口來解,題目詳見leetcode 209. Minimum Size Subarray Sum。
從給定正數組中,找到相加大于等于K的最短子序列。同樣的用一個滑動窗口,且窗口內的元素相加小于K,同時用start記錄滑動窗口的頭部,當滑動窗口內的元素大于等于K時,向后移動start,并更新最短子序列的值。
參考代碼如下:
int minSubArrayLen(int s, vector<int>& nums) {
int len = (int)nums.size()+1;
int sum = 0;
int start = 0;
for(int i = 0;i<(int)nums.size();i++){
sum += nums[i];
while(sum >= s){
if(i-start+1 < len){
len = i-start+1;
}
sum -= nums[start];
start++;
}
}
if(len == (int)nums.size()+1){
return 0;
}
return len;
}
多數組的連續子序列
上面介紹的都是單數組的連續子序列問題,其實多數組問題也很經典,比如leetcode 718 Maximum Length of Repeated Subarray。
判斷兩個給定數組中的最長連續子序列,這種題是典型的可以用空間換取時間來解決的,使用一個二維數組status(i,j)當前位置的最長子序列的長度,很容易寫出狀態轉移方程:
status(i,j) = status(i-1,j-1)+1 if A[i]=B[j]
同時使用變量在循環的時候更新記錄最長子序列的長度最終返回即可。
參考代碼如下:
int findLength(vector<int>& A, vector<int>& B) {
int len1 = (int)A.size();
int len2 = (int)B.size();
int status[len1+1][len2+1];
memset(status,0,sizeof(status));
int maxValue = 0;
for(int i = 1;i<=len1;i++){
for(int j = 1;j<=len2;j++){
if(A[i-1] == B[j-1]){
status[i][j] = status[i-1][j-1]+1;
if(status[i][j] > maxValue){
maxValue = status[i][j];
}
}
}
}
return maxValue;
}