Before attempting this problem, you should be comfortable with:
Recursion - Breaking problems into smaller subproblems with base cases
Dynamic Programming - Using memoization or tabulation to avoid redundant calculations
Array Traversal - Iterating through arrays and checking element relationships
1. Recursion
Intuition
We need to partition the array into subarrays where each subarray is either two equal elements, three equal elements, or three consecutive increasing elements. At each position, we can try taking 2 or 3 elements if they form a valid pattern, then recursively check if the remainder can also be validly partitioned.
Algorithm
Define a recursive function starting at index 0.
Base case: if we reach the end of the array, return true (valid partition found).
At each position, try two options:
If the next two elements are equal, recursively check from index i+2.
If the next three elements are either all equal or form consecutive increasing values, recursively check from index i+3.
Return true if any recursive path leads to a valid partition.
Return false if no valid partition is possible from the current position.
classSolution:defvalidPartition(self, nums: List[int])->bool:defdfs(i):if i ==len(nums):returnTrue
res =Falseif i <len(nums)-1and nums[i]== nums[i +1]:
res = dfs(i +2)if i <len(nums)-2:if((nums[i]== nums[i +1]== nums[i +2])or(nums[i]+1== nums[i +1]and nums[i +1]+1== nums[i +2])):
res = res or dfs(i +3)return res
return dfs(0)
publicclassSolution{publicbooleanvalidPartition(int[] nums){returndfs(nums,0);}privatebooleandfs(int[] nums,int i){if(i == nums.length)returntrue;boolean res =false;if(i < nums.length -1&& nums[i]== nums[i +1]){
res =dfs(nums, i +2);}if(i < nums.length -2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||dfs(nums, i +3);}}return res;}}
classSolution{public:boolvalidPartition(vector<int>& nums){returndfs(nums,0);}private:booldfs(vector<int>& nums,int i){if(i == nums.size())returntrue;bool res =false;if(i < nums.size()-1&& nums[i]== nums[i +1]){
res =dfs(nums, i +2);}if(i < nums.size()-2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||dfs(nums, i +3);}}return res;}};
publicclassSolution{publicboolValidPartition(int[] nums){returnDfs(nums,0);}privateboolDfs(int[] nums,int i){if(i == nums.Length)returntrue;bool res =false;if(i < nums.Length -1&& nums[i]== nums[i +1]){
res =Dfs(nums, i +2);}if(i < nums.Length -2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||Dfs(nums, i +3);}}return res;}}
funcvalidPartition(nums []int)bool{var dfs func(i int)bool
dfs =func(i int)bool{if i ==len(nums){returntrue}
res :=falseif i <len(nums)-1&& nums[i]== nums[i+1]{
res =dfs(i +2)}if i <len(nums)-2{if(nums[i]== nums[i+1]&& nums[i+1]== nums[i+2])||(nums[i]+1== nums[i+1]&& nums[i+1]+1== nums[i+2]){
res = res ||dfs(i+3)}}return res
}returndfs(0)}
class Solution {funvalidPartition(nums: IntArray): Boolean {fundfs(i: Int): Boolean {if(i == nums.size)returntruevar res =falseif(i < nums.size -1&& nums[i]== nums[i +1]){
res =dfs(i +2)}if(i < nums.size -2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||dfs(i +3)}}return res
}returndfs(0)}}
classSolution{funcvalidPartition(_ nums:[Int])->Bool{funcdfs(_ i:Int)->Bool{if i == nums.count {returntrue}var res =falseif i < nums.count -1&& nums[i]== nums[i +1]{
res =dfs(i +2)}if i < nums.count -2{if(nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2]){
res = res ||dfs(i +3)}}return res
}returndfs(0)}}
Time & Space Complexity
Time complexity: O(2n)
Space complexity: O(n)
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution has overlapping subproblems because we might reach the same index through different partition choices. By memoizing results for each starting index, we avoid redundant computations. Once we compute whether a valid partition exists from index i, we store it and reuse it.
Algorithm
Create a memoization map to store results for each starting index.
Define a recursive function that first checks if the result for the current index is already computed.
Base case: if we reach the end of the array, return true.
Try forming a valid subarray of length 2 (two equal elements) and recurse.
Try forming a valid subarray of length 3 (three equal or three consecutive) and recurse.
classSolution:defvalidPartition(self, nums: List[int])->bool:
dp ={len(nums):True}defdfs(i):if i in dp:return dp[i]
res =Falseif i <len(nums)-1and nums[i]== nums[i +1]:
res = dfs(i +2)if i <len(nums)-2:if((nums[i]== nums[i +1]== nums[i +2])or(nums[i]+1== nums[i +1]and nums[i +1]+1== nums[i +2])):
res = res or dfs(i +3)
dp[i]= res
return res
return dfs(0)
publicclassSolution{privateMap<Integer,Boolean> memo =newHashMap<>();publicbooleanvalidPartition(int[] nums){returndfs(nums,0);}privatebooleandfs(int[] nums,int i){if(i == nums.length)returntrue;if(memo.containsKey(i))return memo.get(i);boolean res =false;if(i < nums.length -1&& nums[i]== nums[i +1]){
res =dfs(nums, i +2);}if(i < nums.length -2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||dfs(nums, i +3);}}
memo.put(i, res);return res;}}
classSolution{public:
unordered_map<int,bool> memo;boolvalidPartition(vector<int>& nums){returndfs(nums,0);}private:booldfs(vector<int>& nums,int i){if(i == nums.size())returntrue;if(memo.count(i))return memo[i];bool res =false;if(i < nums.size()-1&& nums[i]== nums[i +1]){
res =dfs(nums, i +2);}if(i < nums.size()-2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||dfs(nums, i +3);}}return memo[i]= res;}};
publicclassSolution{privateDictionary<int,bool> memo =newDictionary<int,bool>();publicboolValidPartition(int[] nums){returnDfs(nums,0);}privateboolDfs(int[] nums,int i){if(i == nums.Length)returntrue;if(memo.ContainsKey(i))return memo[i];bool res =false;if(i < nums.Length -1&& nums[i]== nums[i +1]){
res =Dfs(nums, i +2);}if(i < nums.Length -2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||Dfs(nums, i +3);}}
memo[i]= res;return res;}}
funcvalidPartition(nums []int)bool{
memo :=make(map[int]bool)var dfs func(i int)bool
dfs =func(i int)bool{if i ==len(nums){returntrue}if val, ok := memo[i]; ok {return val
}
res :=falseif i <len(nums)-1&& nums[i]== nums[i+1]{
res =dfs(i +2)}if i <len(nums)-2{if(nums[i]== nums[i+1]&& nums[i+1]== nums[i+2])||(nums[i]+1== nums[i+1]&& nums[i+1]+1== nums[i+2]){
res = res ||dfs(i+3)}}
memo[i]= res
return res
}returndfs(0)}
class Solution {privateval memo = HashMap<Int, Boolean>()funvalidPartition(nums: IntArray): Boolean {returndfs(nums,0)}privatefundfs(nums: IntArray, i: Int): Boolean {if(i == nums.size)returntrue
memo[i]?.let{return it }var res =falseif(i < nums.size -1&& nums[i]== nums[i +1]){
res =dfs(nums, i +2)}if(i < nums.size -2){if((nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2])){
res = res ||dfs(nums, i +3)}}
memo[i]= res
return res
}}
classSolution{funcvalidPartition(_ nums:[Int])->Bool{var memo =[Int:Bool]()funcdfs(_ i:Int)->Bool{if i == nums.count {returntrue}iflet val = memo[i]{return val }var res =falseif i < nums.count -1&& nums[i]== nums[i +1]{
res =dfs(i +2)}if i < nums.count -2{if(nums[i]== nums[i +1]&& nums[i +1]== nums[i +2])||(nums[i]+1== nums[i +1]&& nums[i +1]+1== nums[i +2]){
res = res ||dfs(i +3)}}
memo[i]= res
return res
}returndfs(0)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of starting from the beginning and recursing forward, we can build our solution from the ground up. We define dp[i] as whether the first i elements can be validly partitioned. We iterate through the array and for each position, check if we can extend a valid partition by adding a valid 2-element or 3-element subarray.
Algorithm
Create a DP array of size n+1, initialized to false.
Set dp[0] = true (empty prefix is valid).
Iterate from index 2 to n:
If elements at positions i-1 and i-2 are equal, and dp[i-2] is true, set dp[i] = true.
If i > 2 and the last three elements form a valid pattern (all equal or consecutive), and dp[i-3] is true, set dp[i] = true.
Since each DP state only depends on the previous three states (dp[i-1], dp[i-2], dp[i-3]), we do not need to store the entire DP array. We can use just three variables and rotate them as we iterate through the array from right to left.
Algorithm
Use three variables to represent the DP states for positions i, i+1, and i+2.
Initialize them to represent the base cases (true for positions at or beyond the array end).
Iterate from the second-to-last index down to 0.
At each position, compute the new DP value based on:
Whether taking 2 elements forms a valid subarray and the state two positions ahead is true.
Whether taking 3 elements forms a valid subarray and the state three positions ahead is true.
Shift the variables to prepare for the next iteration.
Return the final value which represents whether the entire array can be partitioned.
The three-element partition allows either three equal elements OR three consecutive increasing elements, but not both conditions mixed. Writing nums[i] == nums[i+1] + 1 instead of nums[i] + 1 == nums[i+1] reverses the direction and checks for decreasing sequences.
Forgetting to Check Both 2-Element and 3-Element Options
At each position, you must consider both taking 2 elements (if they're equal) AND taking 3 elements (if they form a valid pattern). Only checking one option or using else if when both could be valid may miss the correct partition path.
# Must use OR logic to try both options:
res = dfs(i +2)if valid_pair elseFalse
res = res or dfs(i +3)if valid_triple else res
Off-by-One Errors in DP Array Indexing
In the bottom-up DP approach, dp[i] represents whether the first i elements can be partitioned. When checking if elements form a valid pattern, you must use nums[i-1], nums[i-2], nums[i-3] (0-indexed array access) rather than nums[i], which would be out of bounds or reference the wrong elements.