Given two integers n and k, count the number of distinct words of length n that can be formed using lowercase English letters such that no sequence of consecutive vowels has length greater than k. Return the result modulo 109 + 7. The vowels are a, e, i, o, and u. All other lowercase English letters are consonants.
Examples:
Input: n = 2, k = 0
Output: 441
Explanation: No vowels are allowed. Therefore, each position can contain only one of the 21 consonants, giving 21 * 21 = 441 valid words.Input: n = 1, k = 1
Output: 26
Explanation: Any lowercase English letter can be chosen since a single vowel is allowed.
Table of Content
- [Naive Approach] Generate All Words - O(26 ^ n * n) Time and O(n) Space
- [Better Approach - 1] Using Dynamic Programming (Memoization) - O(n * k) Time and O(n * k) Space
- [Better Approach - 2] Using Dynamic Programming (Tabulation) - O(n * k) Time and O(n * k) Space
- [Expected Approach] Using Space Optimized Dynamic Programming - O(n * k) Time and O(k) Space
[Naive Approach] Generate All Words - O(26 ^ n * n) Time and O(n) Space
The idea is to generate every possible word of length n using recursion. For each generated word, count the maximum number of consecutive vowels.
Working of Approach:
- Generate all possible words of length n.
- Keep track of the current word using recursion.
- After generating a word, count its consecutive vowels.
- If no vowel streak exceeds k, increment the answer.
- Return the total number of valid words.
#include <iostream>
#include <string>
using namespace std;
const int MOD = 1000000007;
int res;
// Check whether a character is a vowel.
bool isVowel(char ch)
{
return ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u';
}
// Check whether the generated word is valid.
bool isValid(string &word, int k)
{
int cnt = 0;
for (char ch : word)
{
if (isVowel(ch))
cnt++;
else
cnt = 0;
if (cnt > k)
return false;
}
return true;
}
// Generate all possible words.
void generate(int idx, int n, int k, string &word)
{
// A complete word is formed.
if (idx == n)
{
if (isValid(word, k))
res = (res + 1) % MOD;
return;
}
// Try every lowercase letter.
for (char ch = 'a'; ch <= 'z'; ch++)
{
word.push_back(ch);
generate(idx + 1, n, k, word);
word.pop_back();
}
}
int countWords(int n, int k)
{
res = 0;
string word;
generate(0, n, k, word);
return res;
}
int main()
{
int n = 2, k = 0;
cout << countWords(n, k);
return 0;
}
import java.util.Arrays;
public class GFG {
static final int MOD = 1000000007;
static int res;
// Check whether a character is a vowel.
static boolean isVowel(char ch)
{
return ch == 'a' || ch == 'e' || ch == 'i'
|| ch == 'o' || ch == 'u';
}
// Check whether the generated word is valid.
static boolean isValid(String word, int k)
{
int cnt = 0;
for (char ch : word.toCharArray()) {
if (isVowel(ch))
cnt++;
else
cnt = 0;
if (cnt > k)
return false;
}
return true;
}
// Generate all possible words.
static void generate(int idx, int n, int k,
StringBuilder word)
{
// A complete word is formed.
if (idx == n) {
if (isValid(word.toString(), k))
res = (res + 1) % MOD;
return;
}
// Try every lowercase letter.
for (char ch = 'a'; ch <= 'z'; ch++) {
word.append(ch);
generate(idx + 1, n, k, word);
word.deleteCharAt(word.length() - 1);
}
}
static int countWords(int n, int k)
{
res = 0;
StringBuilder word = new StringBuilder();
generate(0, n, k, word);
return res;
}
public static void main(String[] args)
{
int n = 2, k = 0;
System.out.println(countWords(n, k));
}
}
MOD = 1000000007
res = 0
# Check whether a character is a vowel.
def isVowel(ch):
return ch == 'a' or ch == 'e' or ch == 'i' or ch == 'o' or ch == 'u'
# Check whether the generated word is valid.
def isValid(word, k):
cnt = 0
for ch in word:
if isVowel(ch):
cnt += 1
else:
cnt = 0
if cnt > k:
return False
return True
# Generate all possible words.
def generate(idx, n, k, word):
global res
# A complete word is formed.
if idx == n:
if isValid(word, k):
res = (res + 1) % MOD
return
# Try every lowercase letter.
for ch in range(ord('a'), ord('z') + 1):
word.append(chr(ch))
generate(idx + 1, n, k, word)
word.pop()
def countWords(n, k):
global res
res = 0
word = []
generate(0, n, k, word)
return res
if __name__ == '__main__':
n = 2
k = 0
print(countWords(n, k))
using System;
using System.Text;
using System.Linq;
public class GFG {
const int MOD = 1000000007;
static int res;
// Check whether a character is a vowel.
static bool IsVowel(char ch)
{
return ch == 'a' || ch == 'e' || ch == 'i'
|| ch == 'o' || ch == 'u';
}
// Check whether the generated word is valid.
static bool IsValid(string word, int k)
{
int cnt = 0;
foreach(char ch in word)
{
if (IsVowel(ch))
cnt++;
else
cnt = 0;
if (cnt > k)
return false;
}
return true;
}
// Generate all possible words.
static void Generate(int idx, int n, int k,
StringBuilder word)
{
// A complete word is formed.
if (idx == n) {
if (IsValid(word.ToString(), k))
res = (res + 1) % MOD;
return;
}
// Try every lowercase letter.
for (char ch = 'a'; ch <= 'z'; ch++) {
word.Append(ch);
Generate(idx + 1, n, k, word);
word.Length--;
}
}
static int countWords(int n, int k)
{
res = 0;
StringBuilder word = new StringBuilder();
Generate(0, n, k, word);
return res;
}
public static void Main()
{
int n = 2, k = 0;
Console.WriteLine(countWords(n, k));
}
}
const MOD = 1000000007;
let res = 0;
// Check whether a character is a vowel.
function isVowel(ch)
{
return ch === "a" || ch === "e" || ch === "i"
|| ch === "o" || ch === "u";
}
// Check whether the generated word is valid.
function isValid(word, k)
{
let cnt = 0;
for (let ch of word) {
if (isVowel(ch)) {
cnt++;
}
else {
cnt = 0;
}
if (cnt > k) {
return false;
}
}
return true;
}
// Generate all possible words.
function generate(idx, n, k, word)
{
// A complete word is formed.
if (idx === n) {
if (isValid(word, k)) {
res = (res + 1) % MOD;
}
return;
}
// Try every lowercase letter.
for (let chCode = "a".charCodeAt(0);
chCode <= "z".charCodeAt(0); chCode++) {
let ch = String.fromCharCode(chCode);
word.push(ch);
generate(idx + 1, n, k, word);
word.pop();
}
}
function countWords(n, k)
{
res = 0;
let word = [];
generate(0, n, k, word);
return res;
}
// Driver Code
let n = 2, k = 0;
console.log(countWords(n, k));
Output
441
[Better Approach - 1] Using Dynamic Programming (Memoization) - O(n * k) Time and O(n * k) Space
The idea is to use recursion with memoization where dp[pos][cnt] stores the number of valid words that can be formed starting from position pos with cnt consecutive vowels already placed.
At each position,
- Either place a consonant to reset the vowel count
- Or place a vowel to increase the count if it does not exceed k.
Working of Approach:
- Define a recursive function with the current position and consecutive vowel count.
- If all n positions are filled, return 1.
- Place a consonant and reset the consecutive vowel count to 0.
- Place a vowel only if the consecutive vowel count is less than k.
- Store the computed result in the DP table to avoid recomputation.
#include <iostream>
#include <vector>
using namespace std;
const int MOD = 1000000007;
int ways(int pos, int cnt, int n, int k, vector<vector<int>> &dp)
{
// A valid word is formed.
if (pos == n)
return 1;
if (dp[pos][cnt] != -1)
return dp[pos][cnt];
int res = 0;
// Place a consonant.
res = (21LL * ways(pos + 1, 0, n, k, dp)) % MOD;
// Place a vowel.
if (cnt < k)
res = (res + 5LL * ways(pos + 1, cnt + 1, n, k, dp)) % MOD;
return dp[pos][cnt] = res;
}
int countWords(int n, int k)
{
// dp[pos][cnt] stores the answer for the state
// (position, consecutive vowels).
vector<vector<int>> dp(n + 1, vector<int>(k + 1, -1));
return ways(0, 0, n, k, dp);
}
int main()
{
int n = 2, k = 0;
cout << countWords(n, k);
return 0;
}
import java.util.Arrays;
public class GFG {
static final int MOD = 1000000007;
static int ways(int pos, int cnt, int n, int k,
int[][] dp)
{
// A valid word is formed.
if (pos == n)
return 1;
if (dp[pos][cnt] != -1)
return dp[pos][cnt];
int res = 0;
// Place a consonant.
res = (int)((21L * ways(pos + 1, 0, n, k, dp))
% MOD);
// Place a vowel.
if (cnt < k)
res = (int)((res
+ 5L
* ways(pos + 1, cnt + 1, n,
k, dp))
% MOD);
return dp[pos][cnt] = res;
}
static int countWords(int n, int k)
{
// dp[pos][cnt] stores the answer for the state
// (position, consecutive vowels).
int[][] dp = new int[n + 1][k + 1];
for (int[] row : dp)
Arrays.fill(row, -1);
return ways(0, 0, n, k, dp);
}
public static void main(String[] args)
{
int n = 2, k = 0;
System.out.println(countWords(n, k));
}
}
def ways(pos, cnt, n, k, dp):
MOD = 1000000007
# A valid word is formed.
if pos == n:
return 1
if dp[pos][cnt] != -1:
return dp[pos][cnt]
res = 0
# Place a consonant.
res = (21 * ways(pos + 1, 0, n, k, dp)) % MOD
# Place a vowel.
if cnt < k:
res = (res + 5 * ways(pos + 1, cnt + 1, n, k, dp)) % MOD
dp[pos][cnt] = res
return res
def countWords(n, k):
# dp[pos][cnt] stores the answer for the state
# (position, consecutive vowels).
dp = [[-1] * (k + 1) for _ in range(n + 1)]
return ways(0, 0, n, k, dp)
if __name__ == '__main__':
n = 2
k = 0
print(countWords(n, k))
using System;
public class GFG {
static readonly int MOD = 1000000007;
static int Ways(int pos, int cnt, int n, int k,
int[, ] dp)
{
// A valid word is formed.
if (pos == n)
return 1;
if (dp[pos, cnt] != -1)
return dp[pos, cnt];
int res = 0;
// Place a consonant.
res = (int)((21L * Ways(pos + 1, 0, n, k, dp))
% MOD);
// Place a vowel.
if (cnt < k)
res = (int)((res
+ 5L
* Ways(pos + 1, cnt + 1, n,
k, dp))
% MOD);
return dp[pos, cnt] = res;
}
static int countWords(int n, int k)
{
// dp[pos][cnt] stores the answer for the state
// (position, consecutive vowels).
int[, ] dp = new int[n + 1, k + 1];
for (int i = 0; i <= n; i++)
for (int j = 0; j <= k; j++)
dp[i, j] = -1;
return Ways(0, 0, n, k, dp);
}
public static void Main()
{
int n = 2, k = 0;
Console.WriteLine(countWords(n, k));
}
}
const MOD = 1000000007;
function ways(pos, cnt, n, k, dp)
{
// A valid word is formed.
if (pos === n)
return 1;
if (dp[pos][cnt] !== -1)
return dp[pos][cnt];
let res = 0;
// Place a consonant.
res = (21n * BigInt(ways(pos + 1, 0, n, k, dp)))
% BigInt(MOD);
// Place a vowel.
if (cnt < k)
res = (res
+ 5n
* BigInt(
ways(pos + 1, cnt + 1, n, k, dp)))
% BigInt(MOD);
dp[pos][cnt] = Number(res);
return res;
}
function countWords(n, k)
{
// dp[pos][cnt] stores the answer for the state
// (position, consecutive vowels).
let dp = Array.from({length : n + 1},
() => Array(k + 1).fill(-1));
return Number(ways(0, 0, n, k, dp));
}
// Driver Code
const n = 2, k = 0;
console.log(countWords(n, k));
Output
441
[Better Approach - 2] Using Dynamic Programming (Tabulation) - O(n * k) Time and O(n * k) Space
The idea is to use Dynamic Programming where dp[i][j] stores the number of valid words of length i ending with exactly j consecutive vowels.
Working of Approach:
- Create a DP table of size (n + 1) Ă— (k + 1).
- Initialize dp[0][0] = 1.
- Append a consonant to reset consecutive vowels to 0.
- Append a vowel to increase the consecutive vowel count.
- Sum all states of length n to get the answer.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int countWords(int n, int k)
{
const int MOD = 1000000007;
// dp[i][j] stores the number of valid words of
// length i ending with j consecutive vowels.
vector<vector<long long>> dp(n + 1, vector<long long>(k + 1, 0));
// Base case.
dp[0][0] = 1;
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= k; j++)
{
if (dp[i][j] == 0)
continue;
// Append a consonant.
dp[i + 1][0] = (dp[i + 1][0] + 21LL * dp[i][j]) % MOD;
// Append a vowel.
if (j < k)
{
dp[i + 1][j + 1] = (dp[i + 1][j + 1] + 5LL * dp[i][j]) % MOD;
}
}
}
int res = 0;
// Sum all valid ending states.
for (int j = 0; j <= k; j++)
res = (res + dp[n][j]) % MOD;
return res;
}
int main()
{
int n = 2, k = 0;
cout << countWords(n, k);
return 0;
}
import java.util.*;
public class GFG {
static final int MOD = 1000000007;
static int countWords(int n, int k)
{
// dp[i][j] stores the number of valid words of
// length i ending with j consecutive vowels.
long[][] dp = new long[n + 1][k + 1];
// Base case.
dp[0][0] = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= k; j++) {
if (dp[i][j] == 0)
continue;
// Append a consonant.
dp[i + 1][0]
= (dp[i + 1][0] + 21 * dp[i][j]) % MOD;
// Append a vowel.
if (j < k) {
dp[i + 1][j + 1]
= (dp[i + 1][j + 1] + 5 * dp[i][j])
% MOD;
}
}
}
int res = 0;
// Sum all valid ending states.
for (int j = 0; j <= k; j++)
res = (res + (int)dp[n][j]) % MOD;
return res;
}
public static void main(String[] args)
{
int n = 2, k = 0;
System.out.println(countWords(n, k));
}
}
def countWords(n, k):
MOD = 1000000007
# dp[i][j] stores the number of valid words of
# length i ending with j consecutive vowels.
dp = [[0] * (k + 1) for _ in range(n + 1)]
# Base case.
dp[0][0] = 1
for i in range(n):
for j in range(k + 1):
if dp[i][j] == 0:
continue
# Append a consonant.
dp[i + 1][0] = (dp[i + 1][0] + 21 * dp[i][j]) % MOD
# Append a vowel.
if j < k:
dp[i + 1][j + 1] = (dp[i + 1][j + 1] + 5 * dp[i][j]) % MOD
res = 0
# Sum all valid ending states.
for j in range(k + 1):
res = (res + dp[n][j]) % MOD
return res
if __name__ == '__main__':
n = 2
k = 0
print(countWords(n, k))
using System;
public class GFG {
const int MOD = 1000000007;
static int countWords(int n, int k)
{
// dp[i][j] stores the number of valid words of
// length i ending with j consecutive vowels.
long[, ] dp = new long[n + 1, k + 1];
// Base case.
dp[0, 0] = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= k; j++) {
if (dp[i, j] == 0)
continue;
// Append a consonant.
dp[i + 1, 0]
= (dp[i + 1, 0] + 21 * dp[i, j]) % MOD;
// Append a vowel.
if (j < k) {
dp[i + 1, j + 1]
= (dp[i + 1, j + 1] + 5 * dp[i, j])
% MOD;
}
}
}
int res = 0;
// Sum all valid ending states.
for (int j = 0; j <= k; j++) {
res = (res + (int)dp[n, j]) % MOD;
}
return res;
}
public static void Main()
{
int n = 2, k = 0;
Console.WriteLine(countWords(n, k));
}
}
const MOD = 1000000007;
function countWords(n, k)
{
// dp[i][j] stores the number of valid words of
// length i ending with j consecutive vowels.
let dp = Array.from({length : n + 1},
() => Array(k + 1).fill(0));
// Base case.
dp[0][0] = 1;
for (let i = 0; i < n; i++) {
for (let j = 0; j <= k; j++) {
if (dp[i][j] === 0)
continue;
// Append a consonant.
dp[i + 1][0]
= (dp[i + 1][0] + 21 * dp[i][j]) % MOD;
// Append a vowel.
if (j < k) {
dp[i + 1][j + 1]
= (dp[i + 1][j + 1] + 5 * dp[i][j])
% MOD;
}
}
}
let res = 0;
// Sum all valid ending states.
for (let j = 0; j <= k; j++)
res = (res + dp[n][j]) % MOD;
return res;
}
// Driver Code
let n = 2, k = 0;
console.log(countWords(n, k));
Output
441
[Expected Approach] Using Space Optimized Dynamic Programming - O(n * k) Time and O(k) Space
The idea is to observe that only the previous DP row is required to compute the current row. So, instead of storing the entire DP table, maintain a single array representing the previous length and update it for each new character.
Working of Approach:
- Let dp[i] store the number of words ending with i consecutive vowels.
- Keep the total number of valid words of the previous length.
- Append a consonant to reset the vowel count to 0.
- Append a vowel to increase the consecutive vowel count by 1.
- Update the DP array for every word length and return the final total.
Let us understand with an example:
Input: n = 2, k = 0
- Initialize dp = [1], where dp[0] represents the number of valid words ending with 0 consecutive vowels. Initially, the empty word is the only valid word, so total = 1.
- For length 1, append only consonants since k = 0. Thus, curr[0] = 21 Ă— 1 = 21. Update dp = [21] and total = 21.
- For length 2, again only consonants can be appended. So, curr[0] = 21 Ă— 21 = 441. Update dp = [441] and total = 441.
- After processing all 2 positions, the total number of valid words is 441.
- Hence, the answer returned is 441.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int countWords(int n, int k)
{
const int mod = 1000000007;
// dp[cnt] stores the number of words ending with cnt consecutive vowels.
vector<int> dp(k + 1, 0);
dp[0] = 1;
// Total valid words for the previous length.
int total = 1;
for (int len = 1; len <= n; len++)
{
vector<int> curr(k + 1, 0);
// Append a consonant and reset the vowel streak.
curr[0] = (21LL * total) % mod;
int nextTotal = curr[0];
for (int cnt = 1; cnt <= k; cnt++)
{
// Extend the current vowel streak by one vowel.
curr[cnt] = (5LL * dp[cnt - 1]) % mod;
nextTotal += curr[cnt];
nextTotal %= mod;
}
// Move to the next word length.
dp = curr;
// Store the total count for the current length.
total = nextTotal;
}
return total;
}
int main()
{
int n = 2, k = 0;
cout << countWords(n, k);
return 0;
}
import java.util.*;
public class GFG {
int mod = 1000000007;
int countWords(int n, int k)
{
// dp[cnt] stores the number of words ending with
// cnt consecutive vowels.
int[] dp = new int[k + 1];
Arrays.fill(dp, 0);
dp[0] = 1;
// Total valid words for the previous length.
int total = 1;
for (int len = 1; len <= n; len++) {
int[] curr = new int[k + 1];
Arrays.fill(curr, 0);
// Append a consonant and reset the vowel
// streak.
curr[0] = (int)((21L * total) % mod);
int nextTotal = curr[0];
for (int cnt = 1; cnt <= k; cnt++) {
// Extend the current vowel streak by one
// vowel.
curr[cnt] = (int)((5L * dp[cnt - 1]) % mod);
nextTotal += curr[cnt];
nextTotal %= mod;
}
// Move to the next word length.
dp = curr;
// Store the total count for the current length.
total = nextTotal;
}
return total;
}
public static void main(String[] args)
{
GFG obj = new GFG();
int n = 2, k = 0;
System.out.println(obj.countWords(n, k));
}
}
def countWords(n, k):
mod = 1000000007
# dp[cnt] stores the number of words ending with cnt consecutive vowels.
dp = [0] * (k + 1)
dp[0] = 1
# Total valid words for the previous length.
total = 1
for len in range(1, n + 1):
curr = [0] * (k + 1)
# Append a consonant and reset the vowel streak.
curr[0] = (21 * total) % mod
nextTotal = curr[0]
for cnt in range(1, k + 1):
# Extend the current vowel streak by one vowel.
curr[cnt] = (5 * dp[cnt - 1]) % mod
nextTotal += curr[cnt]
nextTotal %= mod
# Move to the next word length.
dp = curr
# Store the total count for the current length.
total = nextTotal
return total
if __name__ == "__main__":
n = 2
k = 0
print(countWords(n, k))
using System;
class GFG {
static int mod = 1000000007;
static int countWords(int n, int k)
{
// dp[cnt] stores the number of words ending with
// cnt consecutive vowels.
int[] dp = new int[k + 1];
dp[0] = 1;
// Total valid words for the previous length.
int total = 1;
for (int len = 1; len <= n; len++) {
int[] curr = new int[k + 1];
// Append a consonant and reset the vowel
// streak.
curr[0] = (int)((21L * total) % mod);
int nextTotal = curr[0];
for (int cnt = 1; cnt <= k; cnt++) {
// Extend the current vowel streak by one
// vowel.
curr[cnt] = (int)((5L * dp[cnt - 1]) % mod);
nextTotal += curr[cnt];
nextTotal %= mod;
}
// Move to the next word length.
dp = curr;
// Store the total count for the current length.
total = nextTotal;
}
return total;
}
static void Main()
{
int n = 2, k = 0;
Console.WriteLine(countWords(n, k));
}
}
const mod = 1000000007;
function countWords(n, k)
{
// dp[cnt] stores the number of words ending with cnt
// consecutive vowels.
let dp = Array(k + 1).fill(0);
dp[0] = 1;
// Total valid words for the previous length.
let total = 1;
for (let len = 1; len <= n; len++) {
let curr = Array(k + 1).fill(0);
// Append a consonant and reset the vowel streak.
curr[0] = (21n * BigInt(total)) % BigInt(mod);
let nextTotal = Number(curr[0]);
for (let cnt = 1; cnt <= k; cnt++) {
// Extend the current vowel streak by one vowel.
curr[cnt] = Number((5n * BigInt(dp[cnt - 1]))
% BigInt(mod));
nextTotal += curr[cnt];
nextTotal %= mod;
}
// Move to the next word length.
dp = curr;
// Store the total count for the current length.
total = nextTotal;
}
return total;
}
// Driver Code
let n = 2, k = 0;
console.log(countWords(n, k));
Output
441