[LeetCode] Split Array with Equal Sum 分割數組成和相同的子數組


 

Given an array with n integers, you need to find if there are triplets (i, j, k) which satisfies following conditions:

  1. 0 < i, i + 1 < j, j + 1 < k < n - 1
  2. Sum of subarrays (0, i - 1), (i + 1, j - 1), (j + 1, k - 1) and (k + 1, n - 1) should be equal.

where we define that subarray (L, R) represents a slice of the original array starting from the element indexed L to the element indexed R.

Example:

Input: [1,2,1,2,1,2,1]
Output: True
Explanation:
i = 1, j = 3, k = 5. 
sum(0, i - 1) = sum(0, 0) = 1
sum(i + 1, j - 1) = sum(2, 2) = 1
sum(j + 1, k - 1) = sum(4, 4) = 1
sum(k + 1, n - 1) = sum(6, 6) = 1

Note:

  1. 1 <= n <= 2000.
  2. Elements in the given array will be in range [-1,000,000, 1,000,000].
 
這道題給了我們一個數組,讓我們找出三個位置,使得數組被分為四段,使得每段之和相等,問存不存在這樣的三個位置,注意三個位置上的數字不屬於任何一段。剛開始博主覺得這題貌似跟之前那道  Partition Equal Subset Sum 很像,所以在想能不能用DP來做,可是想了半天不知道DP該如何定義,更別說推導狀態轉移方程了。於是就嘗試了建立累加和數組,並搜索所有的可能組合,進行暴力破解,結果卻TLE了。說明OJ不接受時間復雜度為三次方的解法,那么就要想辦法來優化了,博主只好上網學習大神們的解法,發現大神們的解法果然巧妙,只是改變了一個查找順序,就輕易的將時間復雜度降到了平方級,碉堡了有木有。思路是這樣的,因為我們需要找三個位置i,j,k,如果我們按正常的順序來暴力搜索,那么就會遍歷所有的情況,其實大部分的情況都是不符合題意的,會有大量的無用的運算。而如果我們換一個角度,先搜索j的位置,那么i和k的位置就可以固定在一個小的范圍內了,而且可以在j的循環里面同時進行,這樣就少嵌套了一個循環,所以時間復雜度會降一維度。確定j的范圍應該左右各留3個數字,因為四段均不能為空,而且分割位上的數字不能算入四段。再確定了j的位置后,i和k的位置就能分別確定了,我們要做的是先遍歷i的所有可能位置,然后遍歷所有的拆分情況,如果拆出的兩段和相等,則把這個相等的值加入一個集合中,然后再遍歷k的所有情況,同樣遍歷所有的拆分情況,如果拆出兩段和相等,再看這個相等的和是否在集合中,如果存在,說明拆出的四段和都可以相同,那么返回true即可,否則當遍歷結束了,返回false。唉,為啥自己就想不到呢,估計這就是和大神的差距吧,淚目中。。
 
解法一:
class Solution {
public:
    bool splitArray(vector<int>& nums) {
        if (nums.size() < 7) return false;
        int n = nums.size();
        vector<int> sums = nums;
        for (int i = 1; i < n; ++i) {
            sums[i] = sums[i - 1] + nums[i];
        }
        for (int j = 3; j < n - 3; ++j) {
            unordered_set<int> s;
            for (int i = 1; i < j - 1; ++i) {
                if (sums[i - 1] == (sums[j - 1] - sums[i])) {
                    s.insert(sums[i - 1]);
                }
            }
            for (int k = j + 1; k < n - 1; ++k) {
                int s3 = sums[k - 1] - sums[j], s4 = sums[n - 1] - sums[k];
                if (s3 == s4 && s.count(s3)) return true;
            }
        }
        return false;
    }
};

 

下面這種解法是遞歸的暴力破解寫法,剛開始博主還納悶了,為啥博主之前寫的迭代形式的暴力破解過不了OJ,而這個遞歸版本的確能通過呢,仔細研究了一下,發現這種解法有兩個地方做了優化。第一個優化是在for循環里面,如果i不等於1,且當前數字和之前數字均為0,那么跳過這個位置,因為加上0也不會對target有任何影響,那為什么要加上i不等於1的判斷呢,因為輸入數組如果是七個0,那么實際上應該返回true的,而如果沒有i != 1這個條件限制,后面的代碼均不會得到執行,那么就直接返回false了,是不對的。第二個優化的地方是在遞歸函數里面,只有當curSum等於target了,才進一步調用遞歸函數,這樣就相當於做了剪枝處理,減少了大量的不必要的運算,這可能就是其可以通過OJ的原因吧。

再來說下子函數for循環的終止條件 i < n - 5 + 2*cnt 是怎么得來的,是的,這塊的確是個優化,因為i的位置是題目中三個分割點i,j,k的位置,所以其分別有自己可以取值的范圍,由於只有三個分割點,所以cnt的取值可以是0,1,2。
-> 當cnt=0時,說明是第一個分割點,那么i < n - 5,表示后面必須最少要留5個數字,因為分割點本身的數字不記入子數組之和,那么所留的五個數字為:數字,第二個分割點,數字,第三個分割點,數字。
-> 當cnt=1時,說明是第二個分割點,那么i < n - 3,表示后面必須最少要留3個數字,因為分割點本身的數字不記入子數組之和,那么所留的三個數字為:數字,第三個分割點,數字。
-> 當cnt=2時,說明是第三個分割點,那么i < n - 1,表示后面必須最少要留1個數字,因為分割點本身的數字不記入子數組之和,那么所留的一個數字為:數字。

 

解法二:

class Solution {
public:
    bool splitArray(vector<int>& nums) {
        if (nums.size() < 7) return false;
        int n = nums.size(), target = 0;
        int sum = accumulate(nums.begin(), nums.end(), 0);
        for (int i = 1; i < n - 5; ++i) {
            if (i != 1 && nums[i] == 0 && nums[i - 1] == 0) continue;
            target += nums[i - 1];
            if (helper(nums, target, sum - target - nums[i], i + 1, 1)) {
                return true;
            }
        }
        return false;
    }
    bool helper(vector<int>& nums, int target, int sum, int start, int cnt) {
        if (cnt == 3) return sum == target;
        int curSum = 0, n = nums.size();
        for (int i = start + 1; i < n - 5 + 2 * cnt; ++i) {
            curSum += nums[i - 1];
            if (curSum == target && helper(nums, target, sum - curSum - nums[i], i + 1, cnt + 1)) {
                return true;
            }
        }
        return false;
    }
};

 

基於上面遞歸的優化方法的啟發,博主將兩個優化方法加到了之前寫的迭代的暴力破解解法上,就能通過OJ了,perfect!

 

解法三:

class Solution {
public:
    bool splitArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> sums = nums;
        for (int i = 1; i < n; ++i) {
            sums[i] = sums[i - 1] + nums[i];
        }
        for (int i = 1; i <= n - 5; ++i) {
            if (i != 1 && nums[i] == 0 && nums[i - 1] == 0) continue;
            for (int j = i + 2; j <= n - 3; ++j) {
                if (sums[i - 1] != (sums[j - 1] - sums[i])) continue;
                for (int k = j + 2; k <= n - 1; ++k) {
                    int sum3 = sums[k - 1] - sums[j];
                    int sum4 = sums[n - 1] - sums[k];
                    if (sum3 == sum4 && sum3 == sums[i - 1]) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
};

 

參考資料:

https://leetcode.com/problems/split-array-with-equal-sum/

https://leetcode.com/problems/split-array-with-equal-sum/discuss/101484/java-solution-dfs

https://leetcode.com/problems/split-array-with-equal-sum/discuss/101481/simple-java-solution-on2

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM