2950. Number of Divisible Substrings
Problem Description
In this problem, we have a string s
composed of lowercase English letters. Each letter is mapped to a digit according to a given keyboard layout that resembles an old phone keypad. Under this mapping, certain groups of letters correspond to the digits from 1 to 9. To determine if a substring of s
is "divisible", we sum the mapped values of all its characters, and if this sum is divisible by the length of the substring, then the substring is considered divisible.
Our task is to find the total count of these divisible substrings in the string s
. Remember, substrings are contiguous sequences of characters, and they must be non-empty.
For example, if s = "abc"
, and the mapping is { 'a': 1, 'b': 2, 'c': 3 }
, then "abc" (summing to 6) is a divisible substring since 6 (the sum of its mapped values) is divisible by 3 (the length of the substring).
Intuition
To solve this problem, we can use a brute force approach, which involves enumerating over every possible substring, calculating the sum of the numeric values of its characters according to the given mapping, and checking if this sum is divisible by the substring's length.
Here's a step-by-step breakdown of the intuition behind this approach:
-
Create a mapping from characters to digits based on the provided image or keyboard layout. This will be our reference to convert characters to their corresponding numerical values.
-
Iterate over all possible starting indices of substrings in the string
s
. -
For each starting index, iterate over all possible ending indices that follow it to generate every possible substring starting from that index.
-
For each substring, calculate the sum of its character's numerical values.
-
Check if the sum is divisible by the length of the substring. If it is, we've found a divisible substring, and we increment our counter.
This enumeration algorithm ensures we do not miss any possible substrings while systematically checking each one. While this is not the most efficient method, given the problem's constraints, it is an approach that is easy to understand and implement.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation of the solution is straightforward once the brute force approach has been understood. The solution uses a couple of basic concepts in Python: loops and hash tables (dictionaries).
-
Hash Table for Character to Number Mapping: We use a dictionary
mp
to map each character to its corresponding number. The mapping is taken from an image that displays characters on an old phone keypad layout. -
Nested Loops to Enumerate Substrings: Two nested loops are utilized to consider every possible substring of the given string
s
. The outer loop sets the starting index of the substring, while the inner loop sets the ending index. -
Sum of Mapped Characters: Inside the inner loop, as we extend our substring one character at a time, we keep adding the mapped numerical value of the recently included character to a running sum
s
. -
Checking for Divisibility: After including each character, we check if the current sum
s
is divisible by the length of the current substring, which isj - i + 1
(asi
is the starting index andj
is the ending index). -
Incrementing the Count: Each time we find that the sum is divisible by the substring's length, we increment
ans
by 1. -
Returning the Result: After all substrings have been considered,
ans
contains the total count of divisible substrings, which is then returned.
The algorithm's time complexity is O(n^2) in the worst case, where n is the length of string s
. This is due to the fact that we are checking every substring possible. The space complexity is O(1) or O(n) depending on whether we consider the space taken by the hash table which has a fixed size (there are 26 lowercase English letters).
Here is the key portion of the solution code, which represents the described logic:
# Map each character to a digit
mp = {}
for i, s in enumerate(d, 1):
for c in s:
mp[c] = i
# Count of divisible substrings
ans = 0
n = len(word)
# Enumerating all substrings
for i in range(n):
s = 0
for j in range(i, n):
s += mp[word[j]]
# Check if sum is divisible by the length of the substring
ans += s % (j - i + 1) == 0
# Return the total count of divisible substrings
return ans
The implementation exploits the elegance of Python's dictionaries for fast look-ups of character mappings and the efficiency of for-loops for checking each substring.
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 go through a small example using the string s = "abcde"
, with the mapping { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }
. According to this mapping, the numeric values for each character are their respective positions in the English alphabet. We aim to enumerate all substrings of s
and determine which ones are divisible.
Let's visualize the process of iterating over substrings and checking their divisibility:
-
We start with the outer loop at
i = 0
(character 'a'). -
The inner loop starts at
j = i
. -
For the first iteration,
i = 0
andj = 0
, the substring is "a" with a sum of1
. Since1 % 1 (length of the substring)
is0
, "a" is a divisible substring. -
Next,
i = 0
andj = 1
, the substring is "ab". The sum is1 + 2 = 3
. Since3 % 2
is not0
, "ab" is not divisible. -
Continuing this,
i = 0
andj = 2
, the substring is "abc" with sum1 + 2 + 3 = 6
. Since6 % 3
is0
, "abc" is divisible. -
We repeat this process, incrementing
i
andj
to explore all substrings.
Now for a visualization of the nested loops in action:
i = 0
, "a" (1), "ab" (3), "abc" (6), "abcd" (10), "abcde" (15)i = 1
, "b" (2), "bc" (5), "bcd" (9), "bcde" (14)i = 2
, "c" (3), "cd" (7), "cde" (12)i = 3
, "d" (4), "de" (9)i = 4
, "e" (5)
From this, we can count that the divisible substrings are: "a", "abc", "b", "c", "cde", "d", "e". There are a total of 7.
In our Python implementation, we would follow these steps:
- Map characters to numbers.
- Initialize the count (
ans
) to 0. - Use nested loops to extract all substrings.
- Calculate the sum of the numerical values of characters in the current substring.
- Check if this sum is divisible by the length of this substring; if so, increment the count (
ans
). - After considering all substrings,
ans
will represent the total count of divisible substrings.
Solution Implementation
1class Solution:
2 def countDivisibleSubstrings(self, word: str) -> int:
3 # Define substrings where each character maps to a number (1-9)
4 substrings = ["ab", "cde", "fgh", "ijk", "lmn", "opq", "rst", "uvw", "xyz"]
5 char_to_number = {} # Dictionary to map characters to their respective numbers
6 for index, substr in enumerate(substrings, 1):
7 for char in substr:
8 char_to_number[char] = index # Populate the mapping
9
10 total_count = 0 # Variable to keep track of divisible substrings count
11 word_length = len(word)
12
13 # Iterate over the word to check each possible substring
14 for i in range(word_length):
15 sum_of_numbers = 0 # Sum of numbers corresponding to characters in the current substring
16 for j in range(i, word_length):
17 sum_of_numbers += char_to_number[word[j]] # Add the number corresponding to current character
18 # Increase the count if the sum is divisible by the length of the substring
19 total_count += sum_of_numbers % (j - i + 1) == 0
20
21 # Return the total count of divisible substrings
22 return total_count
23
1class Solution {
2 public int countDivisibleSubstrings(String word) {
3 // Array of strings representing groups of characters
4 String[] groups = {"ab", "cde", "fgh", "ijk", "lmn", "opq", "rst", "uvw", "xyz"};
5 // Mapping for characters to their respective group values
6 int[] mapping = new int[26];
7
8 // Initialize the mapping for each character to its group value
9 for (int i = 0; i < groups.length; ++i) {
10 for (char c : groups[i].toCharArray()) {
11 mapping[c - 'a'] = i + 1;
12 }
13 }
14
15 // Initialize count of divisible substrings
16 int count = 0;
17 int length = word.length();
18
19 // Iterate over all possible starting points of substrings
20 for (int i = 0; i < length; ++i) {
21 // 'sum' will hold the sum of the group values for the current substring
22 int sum = 0;
23 // Iterate over all possible ending points of substrings
24 for (int j = i; j < length; ++j) {
25 // Add group value of the current character to 'sum'
26 sum += mapping[word.charAt(j) - 'a'];
27 // Increment the count if sum is divisible by the length of the substring
28 count += sum % (j - i + 1) == 0 ? 1 : 0;
29 }
30 }
31
32 // Return the total count of divisible substrings
33 return count;
34 }
35}
36
1class Solution {
2public:
3 // Function to count divisible substrings
4 int countDivisibleSubstrings(string word) {
5 // An array to hold strings that represent the respective remainders
6 string divisors[9] = {"ab", "cde", "fgh", "ijk", "lmn", "opq", "rst", "uvw", "xyz"};
7 int mappings[26] = {}; // Array to map characters to their divisor group number
8
9 // Populate the mappings array with corresponding group numbers
10 for (int i = 0; i < 9; ++i) {
11 for (char& c : divisors[i]) {
12 mappings[c - 'a'] = i + 1;
13 }
14 }
15
16 int count = 0; // Variable to keep the count of divisible substrings
17 int length = word.size(); // The length of the input word
18
19 // Iterate over all starting positions of the substrings in the word
20 for (int i = 0; i < length; ++i) {
21 int sum = 0; // To keep the sum of divisor group numbers
22
23 // Iterate over all possible ending positions of the substring
24 for (int j = i; j < length; ++j) {
25 // Increment the sum with the mapping of the current character
26 sum += mappings[word[j] - 'a'];
27
28 // Check if the sum is divisible by the length of the substring
29 // If it is, increment the count
30 count += sum % (j - i + 1) == 0 ? 1 : 0;
31 }
32 }
33
34 // Return the total count of divisible substrings
35 return count;
36 }
37};
38
1// Function to count the number of substrings where the sum of the mapped values of letters is divisible
2// by the length of the substring
3function countDivisibleSubstrings(word: string): number {
4 // Define an array that represents the groups of letters, each group has the same weight
5 const groups: string[] = ['ab', 'cde', 'fgh', 'ijk', 'lmn', 'opq', 'rst', 'uvw', 'xyz'];
6
7 // Initialize a mapping array with 26 elements set to 0 to store the weight of each letter
8 const letterWeights: number[] = Array(26).fill(0);
9
10 // Populate the letterWeights with the corresponding group index + 1 (as weights)
11 for (let groupIndex = 0; groupIndex < groups.length; ++groupIndex) {
12 for (const char of groups[groupIndex]) {
13 letterWeights[char.charCodeAt(0) - 'a'.charCodeAt(0)] = groupIndex + 1;
14 }
15 }
16
17 // Get the length of the word
18 const wordLength = word.length;
19 // Initialize the count of valid substrings
20 let count = 0;
21
22 // Check all substrings starting from each character in the word
23 for (let startIndex = 0; startIndex < wordLength; ++startIndex) {
24 // Initialize the sum of letter weights for the current substring
25 let sum = 0;
26 // Iterate over the substring from the current startIndex
27 for (let endIndex = startIndex; endIndex < wordLength; ++endIndex) {
28 // Add the corresponding letter weight to the sum
29 sum += letterWeights[word.charCodeAt(endIndex) - 'a'.charCodeAt(0)];
30 // If the sum is divisible by the substring length, increase the count
31 if (sum % (endIndex - startIndex + 1) === 0) {
32 count++;
33 }
34 }
35 }
36 // Return the final count of valid substrings
37 return count;
38}
39
Time and Space Complexity
Time Complexity
The time complexity of the given code is O(n^2)
. This is because there are two nested loops. The outer loop runs for n
iterations (n
being the length of the word), and for each iteration of the outer loop, the inner loop runs for at most n
iterations - starting from the current index of the outer loop to the end of the word. During each iteration of the inner loop, a constant number of operations are executed. So, for each element, we potentially loop through every other element to the right of it, leading to the n * (n-1) / 2
term, which simplifies to O(n^2)
.
Space Complexity
The space complexity of the code is O(C)
where C
is the size of the character set. In this case, C=26
as there are 26 lowercase English letters. The space is used to store the mapping of each character to its associated integer, which in this instance does not change with the size of the input string and is hence constant.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
LeetCode 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 we
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!