Given a string s consisting only of digits, the task is to count the number of valid groupings possible.
- A grouping is formed by splitting s into one or more non-empty contiguous substrings.
- A grouping is considered valid if the sums of digits of the sub-groups form a non-decreasing sequence from left to right.
Examples:
Input: s = "1119"
Output: 7
Explanation:
One valid grouping is ["1", "11", "9"].
The sum of digits of the first sub-group ("1") is 1,
for the second sub-group ("11"), it is 2,
and for the third sub-group ("9"), it is 9.
Since the sums are in non-decreasing order (1 ≤ 2 ≤ 9), this is a valid grouping.
The other valid groupings are:
["1", "119"], ["1", "1", "19"], ["1", "1", "1", "9"], ["11", "19"], ["111", "9"], and ["1119"].
Thus, the total number of valid groupings is 7.Input: s = "12"
Output: 2
Explanation:
["1","2"] and ["12"] are two valid groupings.Input: s = "123"
Output: 4
Table of Content
[Naive Approach] Using Recursion - O(2^n) Time and O(n) Space
The idea is to recursively try every possible split by traversing from left to right.
- Base Case: If the current index reaches the end of the string, we have formed a valid grouping, so return 1.
- Recurrence Relation: At each position, try all substring splits from current index and recur if the current sum is ≥ previous sum.
// C++ program to count the number of
// valid groupings using recursion
#include <bits/stdc++.h>
using namespace std;
// Recursive helper function to count valid groupings
int countWays(string &s, int index, int prevSum)
{
// Base case: reached the end,
// count as 1 valid grouping
if (index == s.length())
{
return 1;
}
int res = 0;
int currSum = 0;
// Try every possible split starting from index
for (int i = index; i < s.length(); i++)
{
// Add current digit to currSum
currSum += s[i] - '0';
// Proceed only if currSum is ≥ previous group sum
if (currSum >= prevSum)
{
res += countWays(s, i + 1, currSum);
}
}
return res;
}
int validGroups(string &s)
{
// Start recursion from index
// 0 with initial prevSum = 0
return countWays(s, 0, 0);
}
// Driver code
int main()
{
string s = "1119";
cout << validGroups(s);
return 0;
}
import java.util.*;
class GFG {
// Recursive helper function to count valid groupings
static int countWays(String s, int index, int prevSum)
{
// Base case: reached the end,
// count as 1 valid grouping
if (index == s.length()) {
return 1;
}
int res = 0;
int currSum = 0;
// Try every possible split starting from index
for (int i = index; i < s.length(); i++) {
// Add current digit to currSum
currSum += s.charAt(i) - '0';
// Proceed only if currSum is ≥ previous group
// sum
if (currSum >= prevSum) {
res += countWays(s, i + 1, currSum);
}
}
return res;
}
static int validGroups(String s)
{
// Start recursion from index
// 0 with initial prevSum = 0
return countWays(s, 0, 0);
}
public static void main(String[] args)
{
String s = "1119";
System.out.println(validGroups(s));
}
}
# Recursive helper function to count valid groupings
def countWays(s, index, prevSum):
# Base case: reached the end,
# count as 1 valid grouping
if index == len(s):
return 1
res = 0
currSum = 0
# Try every possible split starting from index
for i in range(index, len(s)):
# Add current digit to currSum
currSum += int(s[i])
# Proceed only if currSum is ≥ previous group sum
if currSum >= prevSum:
res += countWays(s, i + 1, currSum)
return res
def validGroups(s):
# Start recursion from index
# 0 with initial prevSum = 0
return countWays(s, 0, 0)
# Driver code
if __name__ == "__main__":
s = "1119"
print(validGroups(s))
using System;
class GFG {
// Recursive helper function to count valid groupings
static int countWays(string s, int index, int prevSum)
{
// Base case: reached the end,
// count as 1 valid grouping
if (index == s.Length) {
return 1;
}
int res = 0;
int currSum = 0;
// Try every possible split starting from index
for (int i = index; i < s.Length; i++) {
// Add current digit to currSum
currSum += s[i] - '0';
// Proceed only if currSum is ≥ previous group
// sum
if (currSum >= prevSum) {
res += countWays(s, i + 1, currSum);
}
}
return res;
}
static int validGroups(string s)
{
// Start recursion from index
// 0 with initial prevSum = 0
return countWays(s, 0, 0);
}
public static void Main()
{
string s = "1119";
Console.WriteLine(validGroups(s));
}
}
// Recursive helper function to count valid groupings
function countWays(s, index, prevSum)
{
// Base case: reached the end,
// count as 1 valid grouping
if (index === s.length) {
return 1;
}
let res = 0;
let currSum = 0;
// Try every possible split starting from index
for (let i = index; i < s.length; i++) {
// Add current digit to currSum
currSum += Number(s[i]);
// Proceed only if currSum is ≥ previous group sum
if (currSum >= prevSum) {
res += countWays(s, i + 1, currSum);
}
}
return res;
}
function validGroups(s)
{
// Start recursion from index
// 0 with initial prevSum = 0
return countWays(s, 0, 0);
}
// Driver code
let s = "1119";
console.log(validGroups(s));
Output
7
[Better Approach] Using Top-Down DP (Memoization) - O(n^3) Time and O(n^2) Space
Why do we use Dynamic Programming?
If we take a closer look at the above recursive solution, we notice that there may be overlapping subproblems. For example, if the input number is 12345, then for index = 2 and prevSum = 3, we recur two times. Similarly, for index 4 and prevSum = 7, we recur two times. Therefore the above solution can be optimized using Dynamic Programming.
What should be the dimensions and size of array?
- There are two parameters that change in input, prevSum and index. So we need a two dimensional array.
- The maximum sum of digits can be 9*length where 'length' is length of input num. So the maximum value for prevSum would be 9*prevSum and maximum value for index would be n.
The maximum sum of digits can be 9*length where 'length' is length of input number.
How do we track solved problems?
The solution first checks the memo table before recursive calls, ensuring that each subproblem is computed only once. We initialize the memo table as -1.
// C++ code using Top-Down DP (Memoization)
// to count total number of valid groupings
#include <bits/stdc++.h>
using namespace std;
// Recursive function with memoization
int countWays(string &s, int index, int prevSum, vector<vector<int>> &memo)
{
// Base case: if entire string is processed
if (index == s.length())
{
return 1;
}
// If already computed, return memoized value
if (memo[index][prevSum] != -1)
{
return memo[index][prevSum];
}
int currSum = 0;
int total = 0;
// Try all possible groupings starting from index
for (int i = index; i < s.length(); i++)
{
// Add current digit to sum
currSum += s[i] - '0';
// Recurse only if non-decreasing sum condition holds
if (currSum >= prevSum)
{
total += countWays(s, i + 1, currSum, memo);
}
}
// Memoize and return
memo[index][prevSum] = total;
return total;
}
// Function to initialize memo and call the DP
int validGroups(string &s)
{
int n = s.length();
// memo[i][j]: number of ways from index i with prev sum j
vector<vector<int>> memo(n + 1, vector<int>(n * 9, -1));
return countWays(s, 0, 0, memo);
}
// Driver code
int main()
{
string s = "1119";
cout << validGroups(s) << endl;
return 0;
}
import java.util.*;
class GFG {
// Recursive function with memoization
static int countWays(String s, int index, int prevSum,
int[][] memo)
{
// Base case: if entire string is processed
if (index == s.length()) {
return 1;
}
// If already computed, return memoized value
if (memo[index][prevSum] != -1) {
return memo[index][prevSum];
}
int currSum = 0;
int total = 0;
// Try all possible groupings starting from index
for (int i = index; i < s.length(); i++) {
// Add current digit to sum
currSum += s.charAt(i) - '0';
// Recurse only if non-decreasing sum condition
// holds
if (currSum >= prevSum) {
total += countWays(s, i + 1, currSum, memo);
}
}
// Memoize and return
memo[index][prevSum] = total;
return total;
}
// Function to initialize memo and call the DP
static int validGroups(String s)
{
int n = s.length();
// memo[i][j]: number of ways from index i with prev
// sum j
int[][] memo = new int[n + 1][n * 9];
for (int[] row : memo)
Arrays.fill(row, -1);
return countWays(s, 0, 0, memo);
}
public static void main(String[] args)
{
String s = "1119";
System.out.println(validGroups(s));
}
}
# Recursive function with memoization
def countWays(s, index, prevSum, memo):
# Base case: if entire string is processed
if index == len(s):
return 1
# If already computed, return memoized value
if memo[index][prevSum] != -1:
return memo[index][prevSum]
currSum = 0
total = 0
# Try all possible groupings starting from index
for i in range(index, len(s)):
# Add current digit to sum
currSum += int(s[i])
# Recurse only if non-decreasing sum condition holds
if currSum >= prevSum:
total += countWays(s, i + 1, currSum, memo)
# Memoize and return
memo[index][prevSum] = total
return total
# Function to initialize memo and call the DP
def validGroups(s):
n = len(s)
# memo[i][j]: number of ways from index i with prev sum j
memo = [[-1] * (n * 9) for _ in range(n + 1)]
return countWays(s, 0, 0, memo)
# Driver code
if __name__ == "__main__":
s = "1119"
print(validGroups(s))
using System;
class GFG {
// Recursive function with memoization
static int countWays(string s, int index, int prevSum,
int[, ] memo)
{
// Base case: if entire string is processed
if (index == s.Length) {
return 1;
}
// If already computed, return memoized value
if (memo[index, prevSum] != -1) {
return memo[index, prevSum];
}
int currSum = 0;
int total = 0;
// Try all possible groupings starting from index
for (int i = index; i < s.Length; i++) {
// Add current digit to sum
currSum += s[i] - '0';
// Recurse only if non-decreasing sum condition
// holds
if (currSum >= prevSum) {
total += countWays(s, i + 1, currSum, memo);
}
}
// Memoize and return
memo[index, prevSum] = total;
return total;
}
// Function to initialize memo and call the DP
static int validGroups(string s)
{
int n = s.Length;
// memo[i][j]: number of ways from index i with prev
// sum j
int[, ] memo = new int[n + 1, n * 9];
for (int i = 0; i <= n; i++)
for (int j = 0; j < n * 9; j++)
memo[i, j] = -1;
return countWays(s, 0, 0, memo);
}
public static void Main()
{
string s = "1119";
Console.WriteLine(validGroups(s));
}
}
// Recursive function with memoization
function countWays(s, index, prevSum, memo)
{
// Base case: if entire string is processed
if (index === s.length) {
return 1;
}
// If already computed, return memoized value
if (memo[index][prevSum] !== -1) {
return memo[index][prevSum];
}
let currSum = 0;
let total = 0;
// Try all possible groupings starting from index
for (let i = index; i < s.length; i++) {
// Add current digit to sum
currSum += Number(s[i]);
// Recurse only if non-decreasing sum condition
// holds
if (currSum >= prevSum) {
total += countWays(s, i + 1, currSum, memo);
}
}
// Memoize and return
memo[index][prevSum] = total;
return total;
}
// Function to initialize memo and call the DP
function validGroups(s)
{
let n = s.length;
// memo[i][j]: number of ways from index i with prev sum
// j
let memo = Array.from({length : n + 1},
() => new Array(n * 9).fill(-1));
return countWays(s, 0, 0, memo);
}
// Driver code
let s = "1119";
console.log(validGroups(s));
Output
7
[Expected Approach] Using Bottom-Up DP (Tabulation) - O(n^3) Time and O(n^2) Space
The approach is similar to the previous one; we iteratively build up the solution in a bottom-up manner.
- We maintain a dp table where dp[i][prevSum] stores the number of ways to split the substring starting from index i such that the previous group’s digit sum is prevSum.
- Base Case: For any prevSum, dp[n][prevSum] = 1 because an empty suffix can be split in one valid way.
- Recursive Case: For each index i and sum prevSum, we try all substrings starting at i, calculate the current digit sum, and add dp[j + 1][currSum] to dp[i][prevSum] if currSum ≥ prevSum.
// C++ program using Tabulation (Bottom-Up DP)
// to count all valid groupings of digit string
#include <bits/stdc++.h>
using namespace std;
// Function to count valid groupings
int validGroups(string &s)
{
int n = s.length();
// Maximum possible sum of digits for a
// string of length n is 9 * n
int maxSum = n * 9;
// dp[i][prevSum] => number of ways to split
// from index i with last group sum = prevSum
vector<vector<int>> dp(n + 1, vector<int>(maxSum + 1, 0));
// Base Case: 1 valid way to split an empty string
for (int prevSum = 0; prevSum <= maxSum; prevSum++)
{
dp[n][prevSum] = 1;
}
// Iterate from n-1 down to 0
for (int i = n - 1; i >= 0; i--)
{
for (int prevSum = 0; prevSum <= maxSum; prevSum++)
{
int currSum = 0;
// Try all substrings starting at index i
for (int j = i; j < n; j++)
{
currSum += s[j] - '0';
// Only proceed if sum is valid
if (currSum >= prevSum)
{
dp[i][prevSum] += dp[j + 1][currSum];
}
}
}
}
// Start from index 0 with prevSum = 0
return dp[0][0];
}
int main()
{
string s = "1119";
cout << validGroups(s) << endl;
return 0;
}
import java.util.*;
class GFG {
// Function to count valid groupings
static int validGroups(String s)
{
int n = s.length();
// Maximum possible sum of digits for a
// string of length n is 9 * n
int maxSum = n * 9;
// dp[i][prevSum] => number of ways to split
// from index i with last group sum = prevSum
int[][] dp = new int[n + 1][maxSum + 1];
// Base Case: 1 valid way to split an empty string
for (int prevSum = 0; prevSum <= maxSum;
prevSum++) {
dp[n][prevSum] = 1;
}
// Iterate from n-1 down to 0
for (int i = n - 1; i >= 0; i--) {
for (int prevSum = 0; prevSum <= maxSum;
prevSum++) {
int currSum = 0;
// Try all substrings starting at index i
for (int j = i; j < n; j++) {
currSum += s.charAt(j) - '0';
// Only proceed if sum is valid
if (currSum >= prevSum) {
dp[i][prevSum]
+= dp[j + 1][currSum];
}
}
}
}
// Start from index 0 with prevSum = 0
return dp[0][0];
}
public static void main(String[] args)
{
String s = "1119";
System.out.println(validGroups(s));
}
}
# Python program using Tabulation (Bottom-Up DP)
# to count all valid groupings of digit string
# Function to count valid groupings
def validGroups(s):
n = len(s)
# Maximum possible sum of digits for a
# string of length n is 9 * n
maxSum = n * 9
# dp[i][prevSum] => number of ways to split
# from index i with last group sum = prevSum
dp = [[0] * (maxSum + 1) for _ in range(n + 1)]
# Base Case: 1 valid way to split an empty string
for prevSum in range(maxSum + 1):
dp[n][prevSum] = 1
# Iterate from n-1 down to 0
for i in range(n - 1, -1, -1):
for prevSum in range(maxSum + 1):
currSum = 0
# Try all substrings starting at index i
for j in range(i, n):
currSum += ord(s[j]) - ord('0')
# Only proceed if sum is valid
if currSum >= prevSum:
dp[i][prevSum] += dp[j + 1][currSum]
# Start from index 0 with prevSum = 0
return dp[0][0]
# Driver code
if __name__ == "__main__":
s = "1119"
print(validGroups(s))
using System;
class GFG {
// Function to count valid groupings
static int validGroups(string s)
{
int n = s.Length;
// Maximum possible sum of digits for a
// string of length n is 9 * n
int maxSum = n * 9;
// dp[i][prevSum] => number of ways to split
// from index i with last group sum = prevSum
int[, ] dp = new int[n + 1, maxSum + 1];
// Base Case: 1 valid way to split an empty string
for (int prevSum = 0; prevSum <= maxSum;
prevSum++) {
dp[n, prevSum] = 1;
}
// Iterate from n-1 down to 0
for (int i = n - 1; i >= 0; i--) {
for (int prevSum = 0; prevSum <= maxSum;
prevSum++) {
int currSum = 0;
// Try all substrings starting at index i
for (int j = i; j < n; j++) {
currSum += s[j] - '0';
// Only proceed if sum is valid
if (currSum >= prevSum) {
dp[i, prevSum]
+= dp[j + 1, currSum];
}
}
}
}
// Start from index 0 with prevSum = 0
return dp[0, 0];
}
public static void Main()
{
string s = "1119";
Console.WriteLine(validGroups(s));
}
}
// JavaScript program using Tabulation (Bottom-Up DP)
// to count all valid groupings of digit string
// Function to count valid groupings
function validGroups(s)
{
let n = s.length;
// Maximum possible sum of digits for a
// string of length n is 9 * n
let maxSum = n * 9;
// dp[i][prevSum] => number of ways to split
// from index i with last group sum = prevSum
let dp = Array.from({length : n + 1},
() => Array(maxSum + 1).fill(0));
// Base Case: 1 valid way to split an empty string
for (let prevSum = 0; prevSum <= maxSum; prevSum++) {
dp[n][prevSum] = 1;
}
// Iterate from n-1 down to 0
for (let i = n - 1; i >= 0; i--) {
for (let prevSum = 0; prevSum <= maxSum;
prevSum++) {
let currSum = 0;
// Try all substrings starting at index i
for (let j = i; j < n; j++) {
currSum += s[j].charCodeAt(0)
- "0".charCodeAt(0);
// Only proceed if sum is valid
if (currSum >= prevSum) {
dp[i][prevSum] += dp[j + 1][currSum];
}
}
}
}
// Start from index 0 with prevSum = 0
return dp[0][0];
}
// Driver code
let s = "1119";
console.log(validGroups(s));
Output
7