Facebook Pixel

3388. Count Beautiful Splits in an Array


Problem Description

You are given an array nums.

A split of an array nums is beautiful if:

  1. The array nums is split into three non-empty subarrays: nums1, nums2, and nums3, such that nums can be formed by concatenating nums1, nums2, and nums3 in that order.
  2. The subarray nums1 is a prefix of nums2 OR nums2 is a prefix of nums3.

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 of nums2, which means LCP[0][i] must be at least i,
  • or nums2 is a prefix of nums3, which is satisfied if LCP[i][j] is at least j - 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:

  1. Initialize LCP Table: We first initialize a 2D list lcp where lcp[i][j] will store the length of the longest common prefix of subarrays nums[i:] and nums[j:]. The table size is (n+1) x (n+1) to accommodate the indices properly, initialized with zeros.

  2. 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], then lcp[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.
  3. Enumerate Possible Splits: We then iterate over possible indices i and j, where:

    • i is the end index for the first subarray nums1.
    • j is the end index for the second subarray nums2.
  4. Check Beautiful Split Condition: For each possible pair (i, j):

    • Check if nums1 is a prefix of nums2. This is true if i <= j - i and lcp[0][i] >= i.
    • Alternatively, check if nums2 is a prefix of nums3. This is true if j - i <= n - j and lcp[i][j] >= j - i.
    • If either condition is satisfied, increment the ans counter.
  5. 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 Evaluator

Example Walkthrough

Let's walk through a simple example using the solution approach described above. Consider the array nums = [1, 2, 1, 2, 1].

  1. Initialize LCP Table:

    • We'll create a 5 x 5 lcp table initialized with zeros, as our array length n = 5.
  2. Build LCP Table:

    • Starting from the end of the array:
      • Compare nums[4] and nums[3]. They are different, so lcp[4][3] = 0.
      • Compare nums[3] and nums[2]. They are the same, so lcp[3][2] = lcp[4][3] + 1 = 1.
      • Continue filling the table for other pairs in a similar fashion.
    • 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]
      ]
  3. Enumerate Possible Splits:

    • Let's iterate over possible indices i and j:
      • For example, set i = 1 and j = 3.
  4. Check Beautiful Split Condition:

    • Check if nums1 is a prefix of nums2:
      • For i = 1, j = 3, we need lcp[0][1] >= 1 and 1 <= 3 - 1.
    • Alternatively, check if nums2 is a prefix of nums3:
      • For i = 1, j = 3, check if lcp[1][3] >= 3 - 1.
    • If either holds, the split is beautiful. Count this in ans.
  5. Return the Result:

    • Continue checking other pairs (i, j). After evaluating all, return ans which holds the count of beautiful splits.

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.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More