Last Updated: 28 Feb, 2022

Separate Numbers

Hard

Problem statement

You are given a string ‘NUM’, the string is made up of digits (0-9),. Your task is to find the number of ways to split the string of digits into a list of non-decreasing positive integers and no integer should have leading zeros.

Note :
The answer may be very large so return it modulo ‘10^9 + 7’.
For Example :
You are given ‘NUM’ = “329”, Here you can split the string as [3, 29] and [329]. Since there are 2 ways the answer will be 2.
Note that a split [32, 9] is invalid because the list is not in non-decreasing order.
Input Format :
The first line of input contains a single integer ‘T’ representing the number of test cases.

The first and only line of each test case contains a string ‘NUM’ representing the string given in the problem.
Output Format :
For each test case print a single integer representing the number of ways you can split the string with the given conditions.

Print a separate line for each test case.
Constraints :
1  <= T <= 10
1 <= NUM.size <= 500
NUM[i] is a digit from 0 to 9

Time Limit: 1 sec
Note :
You do not need to print anything. It has already been taken care of. Just implement the given function.

Approaches

01 Approach

In this approach, we can see that if any string starts with a 0 then it has no was to be separated into a list of non-decreasing positive integers.

 

Let's imagine we know the number of ways to split characters according to the rules before each position in the string and all possible lengths of the previous part. Then for the next position, the number of ways to split characters from start to this position can be calculated recursively.

And what if we know for each position in the string number of ways to split it into parts no longer than D. Then for D +1 we can calculate it from previous values for each position.

 

We will create a function ‘checkString(nums, start1, start2, length)’ which will check if the number formed by starting at ‘start1’ index is less than the number formed by starting at the ‘start2’ index, where both are of ‘length’ length

 

Algorithm:

  • In the checkString(nums, start1, start2, length)
    • While ‘length’ is greater than 0
      • If ‘nums[start1]’ is less than ‘nums[start2]’ return True
      • If ‘nums[start2]’ is less than ‘nums[start1]’ return False.
      • Increase ‘start1’ and ‘start2’ by 1.
    • Return the ‘True’
  • In the given function
    • Set ‘MOD’ as 10<sup>9</sup> + 7
    • If nums[0]’ is ‘0’ return 0
    • If the length of ‘nums’ is 1 return 1
    • Set ‘n’ as the length of ‘nums
    • Create two arrays of ‘n + 1’ lengths, ‘prev’ and ‘next
    • Set ‘prev[0]’ and ‘next[0]’ as 1
    • Iterate ‘d’ from 1 to ‘n
      • Copy all the values of ‘prev’ from index ‘1’ to ‘n’ into ‘next
      • Iterate ‘j1’ from 0 to ‘n - d
        • Set ‘j2’ as ‘j1’ - ‘d’, and i1 as ‘j1’ + ‘d
        • If ‘nums’[j1]’ is ‘0’ continue the loop
        • If i1 is greater than ‘0’ or nums[i1] is not ‘0’ and checkString(nums, i1, j1, d)
          • Increase next[j2] by next[j1]
        • Otherwise
          • Increase next[j2] by prev[j1]
        • Set ‘next[j2]’ as ‘next[j2]’ mod ‘MOD’
    • Finally, return ‘prev[n]

02 Approach

In this approach, we will use the idea behind the previous approach, which is if we know the number of ways to split characters according to the rules before each position in the string and all possible lengths of the previous part. Then for the next position, the number of ways to split characters from start to this position can be calculated.

Suppose the number of ways to form the list when the last number starts at index ‘i’ and ends at index ‘j’ is ‘dp[i][j].

Then ‘dp’ transition is Let length = j-i+1 = length of last number

Thus, we can choose all numbers ending at nums[i-1], and have length < length

=> dp[i][j] += dp[i-length+1][i-1] + ... + dp[i-1][i-1]

Now, if num[i-length : i-1] is smaller or equal to num[i : j]

i.e., if (second last number of length = length) is smaller or equal to last number, then

=> dp[i][j] += dp[i-length][i-1]

 

This is equal to the previous approach which is O(N ^ 3). To optimise it we Use prefix sum DP to calculate sums fast

Thus, in prefSum[i][j] we store dp[0][j] + dp[1][j] ... + dp[i][j]

 

We create a compare(i, j, length, same, nums), which will compare two strings fastly (O(1)). This is due to the ‘same’ matrix that stores the length of the common prefix for strings starting at ‘i’ and ‘j

 

Algorithm:

  • In the compare(i, j, length same, nums) function
    • Set ‘common’ as same[i][j]
    • If ‘common’ is greater than length return True
    • If ‘nums[common + i]’ is greater than ‘nums[common + j]’ return True
    • Otherwise, return False
  • In the main function if nums[0] is ‘0’ return 0
  • Set ‘n’ as the length of ‘nums’, Set ‘MOD’ as 10<sup>9</sup> + 7
  • Create a 2D matrix with 0’s of size nXn, called ‘same’
  • Iterate i and j over every row and column index of ‘same’ in reverse order
    • If nums[i] is equal to nums[j]
      • Set same[i][j] equal to same[i + 1][j + 1] + 1
  • Create a 2D matrix ‘prefSum’ of n + 1 X n + 1 dimensions
  • Set the first row of ‘prefSum’ as all 0s
  • Iterate i from 1 to n - 1
    • If ‘nums[i]’ is ‘0’ continue the loop
    • Iterate j from i to n - 1
      • Set length as ‘j - i + 1’, set ‘count’ as ‘0’ and set ‘prevStart’ as i - 1 - (length - 1)
      • If prevStart is less than 0
        • Set ‘count’ as prefSum[i - 1][j - 1]
      • Otherwise,
        • Set ‘count’ as ‘(prefSum[i - 1][i - 1] - prefSum[prevStart][i - 1])’
        • If compare(prevStart, i, length,same, nums) is True
          • Set ‘cnt’ as ‘prefSum[prevStart][i - 1]
          • If ‘prevStart’ is not 0 subtract prefSum[prevStart - 1][i - 1] from it
          • Add ‘cnt’ to ‘count
        • Add ‘cnt’ to ‘count’
      • Set prefSum[i][j] as prefSum[i - 1][j - 1] + ‘count’
  • Return the prefSum[n][n] % MOD