Given an array `A` of `0`s and `1`s, divide the array into 3 non-empty parts such that all of these parts represent the same binary value.
If it is possible, return any [i, j]
with i+1 < j
, such that:
A[0], A[1], ..., A[i]
is the first part;A[i+1], A[i+2], ..., A[j-1]
is the second part, andA[j], A[j+1], ..., A[A.length - 1]
is the third part.- All three parts have equal binary value.
If it is not possible, return [-1, -1]
.
Note that the entire part is used when considering what binary value it represents. For example, [1,1,0]
represents 6
in decimal, not 3
. Also, leading zeros are allowed, so [0,1,1]
and [1,1]
represent the same value.
Example 1:
Input: [1,0,1,0,1]
Output: [0,3]
Example 2:
Input: [1,1,0,1,1]
Output: [-1,-1]
Note:
3 <= A.length <= 30000
A[i] == 0
orA[i] == 1
這道題給了我們一個只有0和1的數組,讓將其分為三段,使得每一段組成的二進制的數相同,注意數組左邊的數字是高位,且開頭可能會存在多余的0。最開始博主使用的方法是最直接的暴力方法,遍歷所有的分成三段的可能,然后將每一段的二進制都計算出來比較是否相等,結果毫不意外的超時了,絲毫沒有尊重這道題的 Hard 標簽。仔細分析以下題目,既然要分成三段,且二進制數字相等,那么這三個二進制數中1的個數一定是相等的,因為轉十進制的時候,只有1才會累加,而由於開頭0的存在,所以0的個數不一定相同。那么1的個數就是突破口了,先遍歷整個數組,統計出1的個數。假如數組中沒有1的話,那就簡單了,隨便分三段都行了,因為都是0,所以返回 {0, n-1} 就行。再來想一下,假如1的個數不是3的個數,就絕無可能分為相等值的三段,直接返回 {-1,-1} 即可。假如個數是3的倍數,也不表示一定能成功分為3段,但如果能分的話,每段的1的個數一定是 cntOne/3,現在從末尾開始,找出正好有 cntOnt/3 個的位置 idxThird,此時雖然范圍 [idxThird, n-1] 不一定是第三段,因為前面可能有0,但如果能成功分的話,其1的個數及位置一定是正確的。此時要做的是,從開頭0開始,略去連續0,然后和 [idxThird, n-1] 區間對比,一旦有不一樣的位置,說明無法正確分,返回 {-1,-1}。若能找到相同的一段話,說明此時第一段和第三段存在了,再來檢測第二段,此時中間段的1的個數已經確定為 cntOne/3 了,只需要再確定其位置是否一樣就可以了,其實主要比的是1的位置,因為開頭連續0可以略去,就算每個區間末尾還有0,這些0是可以移動到下一個區間的開頭的,從而可以保證對應的1的位置還是一樣的,參見代碼如下:
解法一:
class Solution {
public:
vector<int> threeEqualParts(vector<int>& A) {
int cntOne = 0, n = A.size();
for (int num : A) {
if (num == 1) ++cntOne;
}
if (cntOne == 0) return {0, n - 1};
if (cntOne % 3 != 0) return {-1, -1};
int idxThird = 0, cnt = 0;
for (int i = n - 1; i >= 0; --i) {
if (A[i] == 0) continue;
++cnt;
if (cnt == cntOne / 3) {
idxThird = i;
break;
}
}
int idx1 = helper(A, 0, idxThird);
if (idx1 < 0) return {-1, -1};
int idx2 = helper(A, idx1 + 1, idxThird);
if (idx2 < 0) return {-1, -1};
return {idx1, idx2 + 1};
}
int helper(vector<int>& A, int left, int right) {
while (A[left] == 0) ++left;
while (right < A.size()) {
if (A[left] != A[right]) return -1;
++left; ++right;
}
return left - 1;
}
};
再來看一種比較類似的方法,開始的操作還是一樣的,統計1的個數 cntOne,然后計算出每段的1的個數 k=cntOne/3,再用三個變量 start, mid, end 來標記每段的第一個1的位置,因為想要跳過開頭的連續0。再用另一個變量 cnt 來重新在遍歷的過程中統計1的個數,在 cnt 為0的時候,一直更新 start,這樣可以跳過開頭連續0,當 cnt=k+1 時,更新 mid 為i,因為這是第二段中第一個1的位置,當 cnt=2*k+1 時,更新 end 為i,因為這是第三段中第一個1的位置,然后此時驗證三個區間,即 A[start],A[mid],和 A[end] 必須相等,然后三個指針同時向后移動一位,若有任何不相等,就 break 掉,最后看若 end 等於n,說明三段區間中1的位置都相等,是符合題意的,返回 {start-1, mid},否則返回 {-1,-1} 即可,參見代碼如下:
解法二:
class Solution {
public:
vector<int> threeEqualParts(vector<int>& A) {
int cntOne = 0, n = A.size();
for (int num : A) {
if (num == 1) ++cntOne;
}
if (cntOne == 0) return {0, n - 1};
if (cntOne % 3 != 0) return {-1, -1};
int k = cntOne / 3, start = 0, mid = 0, end = 0, cnt = 0;
for (int i = 0; i < n; ++i) {
if (A[i] == 0) continue;
if (cnt == 0) start = i;
++cnt;
if (cnt == k + 1) mid = i;
if (cnt == 2 * k + 1) {end = i; break;}
}
while (end < n && A[start] == A[mid] && A[mid] == A[end]) {
++start; ++mid; ++end;
}
if (end == n) return {start - 1, mid};
return {-1, -1};
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/927
參考資料:
https://leetcode.com/problems/three-equal-parts/
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)