DP問題求解(二)連續子序列

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;
    }
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,761評論 0 33
  • Maximum Subarray 由于簡書不支持latex語法,所以可以到下面的作業部落去看。https://ww...
    別時茫茫閱讀 2,356評論 0 2
  • 嗨! 這一聲問候,雖然只有一個字,可我卻足足準備了一個多月。 從12月份你告訴我你要來西安我就開始準備著,直到在北...
    納蘭蘇淺閱讀 180評論 0 0
  • 分享 尚雯婕 的歌曲《愛過誰》http://t.cn/RJw5IdM(分享自@蝦米音樂) 2017版《射雕英雄轉》...
    奇妙VQ閱讀 633評論 0 0
  • 昨天的閉幕演出上海歌劇院《國之當歌》劇終時,劇中男主“聶耳”指揮現場觀眾全體起立齊唱國歌。 我給來看戲的臺灣姑娘發...
    乃顧閱讀 757評論 6 7