3388. Count Beautiful Splits in an Array
Problem Description
You are given an array nums
.
A split of an array nums
is beautiful if:
- The array
nums
is split into three non-empty subarrays:nums1
,nums2
, andnums3
, such thatnums
can be formed by concatenatingnums1
,nums2
, andnums3
in that order. - The subarray
nums1
is a prefix ofnums2
ORnums2
is a prefix ofnums3
.
Return the number of ways you can make this split.
Intuition
To solve the problem, we use the concept of Longest Common Prefix (LCP) between suffixes of the array. The LCP[i][j]
table is built to store the length of the longest common prefix between the subarrays starting at indices i
and j
.
The problem requires us to check if either:
nums1
is a prefix ofnums2
, which meansLCP[0][i]
must be at leasti
,- or
nums2
is a prefix ofnums3
, which is satisfied ifLCP[i][j]
is at leastj - i
.
To avoid redundant calculations, we preprocess the LCP
table using a nested loop. Starting from the end of the array, if nums[i] == nums[j]
, we compute LCP[i][j] = LCP[i + 1][j + 1] + 1
. This allows quick access to LCP information when evaluating each potential split of the array.
We then iterate over all possible indices to split the array into nums1
, nums2
, and nums3
and evaluate the above conditions. For each successful check, we increment the count of beautiful splits.
The solution is efficient for the problem constraints and captures all potential ways to form beautiful splits.
Learn more about Dynamic Programming patterns.
Solution Approach
To solve the problem of finding the number of beautiful splits in the array, we use a dynamic programming approach with the Longest Common Prefix (LCP) table. Here's a step-by-step breakdown of the solution:
-
Initialize LCP Table: We first initialize a 2D list
lcp
wherelcp[i][j]
will store the length of the longest common prefix of subarraysnums[i:]
andnums[j:]
. The table size is(n+1) x (n+1)
to accommodate the indices properly, initialized with zeros. -
Build LCP Table: We fill in the
lcp
table starting from the end of the array. For each pair of indices(i, j)
:- If
nums[i] == nums[j]
, thenlcp[i][j] = lcp[i + 1][j + 1] + 1
. This means if the current elements are the same, the common prefix length is extended by one, based on the previously calculated LCP.
- If
-
Enumerate Possible Splits: We then iterate over possible indices
i
andj
, where:i
is the end index for the first subarraynums1
.j
is the end index for the second subarraynums2
.
-
Check Beautiful Split Condition: For each possible pair
(i, j)
:- Check if
nums1
is a prefix ofnums2
. This is true ifi <= j - i
andlcp[0][i] >= i
. - Alternatively, check if
nums2
is a prefix ofnums3
. This is true ifj - i <= n - j
andlcp[i][j] >= j - i
. - If either condition is satisfied, increment the
ans
counter.
- Check if
-
Return the Result: After all possible indices are evaluated,
ans
contains the total number of beautiful splits.
By precomputing the LCP, the solution efficiently checks the prefix conditions needed to determine beautiful splits, ensuring that the algorithm runs efficiently even for larger input sizes.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a simple example using the solution approach described above. Consider the array nums = [1, 2, 1, 2, 1]
.
-
Initialize LCP Table:
- We'll create a
5 x 5
lcp
table initialized with zeros, as our array lengthn = 5
.
- We'll create a
-
Build LCP Table:
- Starting from the end of the array:
- Compare
nums[4]
andnums[3]
. They are different, solcp[4][3] = 0
. - Compare
nums[3]
andnums[2]
. They are the same, solcp[3][2] = lcp[4][3] + 1 = 1
. - Continue filling the table for other pairs in a similar fashion.
- Compare
- The completed LCP table might look something like this (this is illustrative):
lcp = [ [2, 0, 2, 0, 1], [0, 2, 0, 1, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 0] ]
- Starting from the end of the array:
-
Enumerate Possible Splits:
- Let's iterate over possible indices
i
andj
:- For example, set
i = 1
andj = 3
.
- For example, set
- Let's iterate over possible indices
-
Check Beautiful Split Condition:
- Check if
nums1
is a prefix ofnums2
:- For
i = 1
,j = 3
, we needlcp[0][1] >= 1
and1 <= 3 - 1
.
- For
- Alternatively, check if
nums2
is a prefix ofnums3
:- For
i = 1
,j = 3
, check iflcp[1][3] >= 3 - 1
.
- For
- If either holds, the split is beautiful. Count this in
ans
.
- Check if
-
Return the Result:
- Continue checking other pairs
(i, j)
. After evaluating all, returnans
which holds the count of beautiful splits.
- Continue checking other pairs
This example illustrates how you construct and utilize the LCP table to efficiently find beautiful splits within the given constraints.
Solution Implementation
1from typing import List
2
3class Solution:
4 def beautifulSplits(self, nums: List[int]) -> int:
5 n = len(nums) # Get the length of the input list
6 # Initialize a 2D list to store longest common prefix values
7 lcp = [[0] * (n + 1) for _ in range(n + 1)]
8
9 # Calculate and populate the lcp matrix
10 for i in range(n - 1, -1, -1):
11 for j in range(n - 1, i - 1, -1):
12 if nums[i] == nums[j]:
13 lcp[i][j] = lcp[i + 1][j + 1] + 1 # Update the lcp value
14
15 answer = 0
16 # Iterate through the possible splits of the array
17 for i in range(1, n - 1):
18 for j in range(i + 1, n):
19 # Check if the first part (A) up to i and the second part (B) from i to j can form a beautiful split
20 can_split_a = (i <= j - i) and (lcp[0][i] >= i)
21 can_split_b = (j - i <= n - j) and (lcp[i][j] >= j - i)
22
23 # If either condition for a beautiful split is met, increment the answer
24 if can_split_a or can_split_b:
25 answer += 1
26
27 return answer
28
1class Solution {
2 public int beautifulSplits(int[] nums) {
3 int n = nums.length;
4 // Create a 2D array to store the length of common prefixes
5 int[][] lcp = new int[n + 1][n + 1];
6
7 // Compute the longest common prefixes for each pair (i, j)
8 for (int i = n - 1; i >= 0; i--) {
9 for (int j = n - 1; j > i; j--) {
10 // If nums[i] is equal to nums[j], increment the lcp count
11 if (nums[i] == nums[j]) {
12 lcp[i][j] = lcp[i + 1][j + 1] + 1;
13 }
14 }
15 }
16
17 int ans = 0; // Initialize the answer count
18 // Iterate through all potential split points in the array
19 for (int i = 1; i < n - 1; i++) {
20 for (int j = i + 1; j < n; j++) {
21 // Check two conditions to determine if the split is valid
22 boolean conditionA = (i <= j - i) && (lcp[0][i] >= i);
23 boolean conditionB = (j - i <= n - j) && (lcp[i][j] >= j - i);
24 // If any of the conditions are met, it's a beautiful split
25 if (conditionA || conditionB) {
26 ans++;
27 }
28 }
29 }
30
31 return ans; // Return the total count of beautiful splits
32 }
33}
34
1class Solution {
2public:
3 int beautifulSplits(vector<int>& nums) {
4 int n = nums.size(); // Get the size of the input array
5 // Create a 2D vector to store the longest common prefixes
6 vector<vector<int>> lcp(n + 1, vector<int>(n + 1, 0));
7
8 // Compute the longest common prefix for each pair (i, j)
9 for (int i = n - 1; i >= 0; i--) {
10 for (int j = n - 1; j > i; j--) {
11 // If the elements are equal, add 1 to the LCP of the next indices
12 if (nums[i] == nums[j]) {
13 lcp[i][j] = lcp[i + 1][j + 1] + 1;
14 }
15 }
16 }
17
18 int ans = 0; // Initialize the count of beautiful splits
19 // Iterate over all possible split points
20 for (int i = 1; i < n - 1; i++) {
21 for (int j = i + 1; j < n; j++) {
22 // Check if the left and right partitions satisfy the "beautiful" condition
23 bool conditionLeft = (i <= j - i) && (lcp[0][i] >= i);
24 bool conditionRight = (j - i <= n - j) && (lcp[i][j] >= j - i);
25 // Increment the count if either condition is true
26 if (conditionLeft || conditionRight) {
27 ans++;
28 }
29 }
30 }
31
32 return ans; // Return the total number of beautiful splits
33 }
34};
35
1function beautifulSplits(nums: number[]): number {
2 const n = nums.length;
3 // Create a 2D array for storing longest common prefixes (lcp)
4 const lcp: number[][] = Array.from({ length: n + 1 }, () => Array(n + 1).fill(0));
5
6 // Fill the lcp array with lengths of longest common suffixes of nums[i..n-1] and nums[j..n-1]
7 for (let i = n - 1; i >= 0; i--) {
8 for (let j = n - 1; j > i; j--) {
9 // If the current elements are equal, increment the lcp value from the following indices
10 if (nums[i] === nums[j]) {
11 lcp[i][j] = lcp[i + 1][j + 1] + 1;
12 }
13 }
14 }
15
16 let ans = 0; // Answer to store count of "beautiful" splits
17 // Iterate over possible split points i, and j, such that i < j
18 for (let i = 1; i < n - 1; i++) {
19 for (let j = i + 1; j < n; j++) {
20 // Check condition a: the prefix ending at i can extend to form a repeat in the next section
21 const a = i <= j - i && lcp[0][i] >= i;
22 // Check condition b: the segment between i and j can extend to form a repeat in subsequent section
23 const b = j - i <= n - j && lcp[i][j] >= j - i;
24 // If either condition is satisfied, increment answer
25 if (a || b) {
26 ans++;
27 }
28 }
29 }
30
31 return ans; // Return the total count of beautiful splits
32}
33
Time and Space Complexity
The time complexity of the code is O(n^2)
. This complexity arises from the nested loops used to fill the lcp
array and to compute the final result. Specifically, the loop that creates the longest common prefix (LCP) array iterates over all pairs (i, j)
with i < j
, leading to a quadratic number of iterations. Further loops process these results with similar complexity.
The space complexity of the code is O(n^2)
. This is due to the creation of the LCP table lcp
, which is a 2D list of size (n+1) x (n+1)
, storing values for each pair of indices in the input list nums
.
Learn more about how to find time and space complexity quickly.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!