2575. Find the Divisibility Array of a String
Problem Description
You are given a string word
consisting of digits (0-9) and a positive integer m
. The string is 0-indexed with length n
.
Your task is to create a divisibility array div
of the same length as word
. For each position i
in the array:
div[i] = 1
if the number formed by the substring from index 0 to indexi
(inclusive) is divisible bym
div[i] = 0
otherwise
For example, if word = "123"
and m = 3
:
- At index 0: The substring is "1", which represents the number 1. Since 1 is not divisible by 3,
div[0] = 0
- At index 1: The substring is "12", which represents the number 12. Since 12 is divisible by 3,
div[1] = 1
- At index 2: The substring is "123", which represents the number 123. Since 123 is divisible by 3,
div[2] = 1
- The result would be
[0, 1, 1]
The solution uses a clever approach to avoid overflow issues with large numbers. Instead of converting the entire substring to an integer each time, it maintains a running remainder x
. For each new digit, it updates the remainder using the formula x = (x * 10 + digit) % m
. This works because of the modular arithmetic property: (a * b + c) % m = ((a % m) * b + c) % m
.
The algorithm processes each character in word
, updates the running remainder, and appends 1 to the result array if the remainder is 0 (meaning divisible by m
), or 0 otherwise.
Intuition
The naive approach would be to convert each prefix substring to an integer and check if it's divisible by m
. However, this runs into a major problem: the string could be very long, creating numbers far too large to store in standard integer types.
The key insight is recognizing that we don't need the actual number - we only need to know if it's divisible by m
. This means we only care about the remainder when divided by m
.
Consider how we build numbers digit by digit. When we have a number like 12 and want to append 3 to get 123, we multiply 12 by 10 and add 3: 12 * 10 + 3 = 123
.
Now, here's the crucial mathematical property: when checking divisibility, we can work with remainders throughout the entire process. The remainder of a sum or product can be computed from the remainders of its parts:
(a + b) % m = ((a % m) + (b % m)) % m
(a * b) % m = ((a % m) * (b % m)) % m
This means instead of computing 123 % m
, we can:
- Start with remainder of 1:
1 % m
- For the next digit 2:
((1 % m) * 10 + 2) % m
gives us the remainder of 12 - For the next digit 3:
((12 % m) * 10 + 3) % m
gives us the remainder of 123
By maintaining only the remainder at each step (which is always less than m
), we avoid overflow issues entirely. Whenever this remainder equals 0, we know the current prefix is divisible by m
.
This transforms an impossible problem (storing potentially massive numbers) into a simple one (tracking a single remainder value that never exceeds m - 1
).
Learn more about Math patterns.
Solution Approach
The implementation follows a straightforward traversal pattern with modular arithmetic:
-
Initialize variables:
- Create an empty list
ans
to store the divisibility array - Set
x = 0
to track the running remainder
- Create an empty list
-
Process each digit:
- Iterate through each character
c
in the stringword
- Update the remainder using the formula:
x = (x * 10 + int(c)) % m
x * 10
shifts the previous remainder to make room for the new digit+ int(c)
adds the current digit value% m
keeps only the remainder, preventing overflow
- Iterate through each character
-
Record divisibility:
- After updating
x
, check ifx == 0
- If true, append
1
toans
(current prefix is divisible bym
) - Otherwise, append
0
toans
- After updating
-
Return the result:
- After processing all digits, return the complete divisibility array
ans
- After processing all digits, return the complete divisibility array
Example walkthrough with word = "123"
and m = 3
:
- Initial:
x = 0
,ans = []
- Process '1':
x = (0 * 10 + 1) % 3 = 1
, append0
→ans = [0]
- Process '2':
x = (1 * 10 + 2) % 3 = 0
, append1
→ans = [0, 1]
- Process '3':
x = (0 * 10 + 3) % 3 = 0
, append1
→ans = [0, 1, 1]
The time complexity is O(n)
where n
is the length of the string, as we process each character exactly once. The space complexity is O(n)
for storing the result array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with word = "4872"
and m = 6
.
We want to check if each prefix ("4", "48", "487", "4872") is divisible by 6.
Initial State:
x = 0
(running remainder)ans = []
(result array)
Step 1: Process digit '4'
- Update remainder:
x = (0 * 10 + 4) % 6 = 4
- Check divisibility:
4 ≠ 0
, so prefix "4" is NOT divisible by 6 - Append
0
to result:ans = [0]
Step 2: Process digit '8'
- Update remainder:
x = (4 * 10 + 8) % 6 = 48 % 6 = 0
- Check divisibility:
0 == 0
, so prefix "48" IS divisible by 6 - Append
1
to result:ans = [0, 1]
Step 3: Process digit '7'
- Update remainder:
x = (0 * 10 + 7) % 6 = 7 % 6 = 1
- Check divisibility:
1 ≠ 0
, so prefix "487" is NOT divisible by 6 - Append
0
to result:ans = [0, 1, 0]
Step 4: Process digit '2'
- Update remainder:
x = (1 * 10 + 2) % 6 = 12 % 6 = 0
- Check divisibility:
0 == 0
, so prefix "4872" IS divisible by 6 - Append
1
to result:ans = [0, 1, 0, 1]
Final Result: [0, 1, 0, 1]
This means:
- "4" (4) is not divisible by 6
- "48" (48) is divisible by 6
- "487" (487) is not divisible by 6
- "4872" (4872) is divisible by 6
The key insight is that we never store the actual numbers (4, 48, 487, 4872). We only track remainders (4, 0, 1, 0), which always stay between 0 and 5, avoiding any overflow issues even for extremely long strings.
Solution Implementation
1class Solution:
2 def divisibilityArray(self, word: str, m: int) -> List[int]:
3 """
4 Creates a divisibility array where each position indicates if the prefix
5 number formed up to that position is divisible by m.
6
7 Args:
8 word: A string representation of a large number
9 m: The divisor to check divisibility against
10
11 Returns:
12 A list where ans[i] = 1 if prefix[0..i] is divisible by m, else 0
13 """
14 result = []
15 remainder = 0
16
17 # Process each digit in the word string
18 for digit_char in word:
19 # Update remainder by shifting left (multiply by 10) and adding new digit
20 # Use modulo to prevent overflow and maintain only the remainder
21 remainder = (remainder * 10 + int(digit_char)) % m
22
23 # Check if current prefix number is divisible by m
24 # Append 1 if divisible (remainder is 0), otherwise append 0
25 result.append(1 if remainder == 0 else 0)
26
27 return result
28
1class Solution {
2 /**
3 * Creates a divisibility array where each element indicates if the prefix
4 * forms a number divisible by m.
5 *
6 * @param word A string representing a large number (digits only)
7 * @param m The divisor to check divisibility against
8 * @return An array where ans[i] = 1 if prefix[0..i] is divisible by m, 0 otherwise
9 */
10 public int[] divisibilityArray(String word, int m) {
11 int length = word.length();
12 int[] result = new int[length];
13
14 // Track the running remainder as we process each digit
15 long currentRemainder = 0;
16
17 // Process each digit from left to right
18 for (int i = 0; i < length; i++) {
19 // Extract the current digit from the character
20 int currentDigit = word.charAt(i) - '0';
21
22 // Update remainder: (previous * 10 + current digit) mod m
23 // This simulates appending the digit to form the prefix number
24 currentRemainder = (currentRemainder * 10 + currentDigit) % m;
25
26 // If remainder is 0, the prefix up to index i is divisible by m
27 if (currentRemainder == 0) {
28 result[i] = 1;
29 }
30 }
31
32 return result;
33 }
34}
35
1class Solution {
2public:
3 vector<int> divisibilityArray(string word, int m) {
4 vector<int> result;
5 long long remainder = 0;
6
7 // Process each digit character in the string
8 for (char& digit : word) {
9 // Build the number incrementally and keep track of remainder
10 // Convert character to digit: digit - '0'
11 // Multiply previous remainder by 10 and add current digit
12 // Take modulo m to prevent overflow and maintain remainder
13 remainder = (remainder * 10 + digit - '0') % m;
14
15 // If remainder is 0, the prefix is divisible by m (store 1)
16 // Otherwise, it's not divisible (store 0)
17 result.push_back(remainder == 0 ? 1 : 0);
18 }
19
20 return result;
21 }
22};
23
1/**
2 * Determines divisibility of prefixes of a numeric string by a given divisor.
3 * For each prefix of the input string (treated as a number), checks if it's divisible by m.
4 *
5 * @param word - A string representing a large number (each character is a digit)
6 * @param m - The divisor to check divisibility against
7 * @returns An array where result[i] = 1 if prefix[0..i] is divisible by m, 0 otherwise
8 */
9function divisibilityArray(word: string, m: number): number[] {
10 const result: number[] = [];
11 let remainder: number = 0;
12
13 // Process each digit in the string
14 for (const digit of word) {
15 // Update remainder using modular arithmetic to avoid overflow
16 // New remainder = (previous remainder * 10 + current digit) mod m
17 remainder = (remainder * 10 + Number(digit)) % m;
18
19 // If remainder is 0, the prefix is divisible by m
20 result.push(remainder === 0 ? 1 : 0);
21 }
22
23 return result;
24}
25
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string word
. This is because the algorithm iterates through each character in the string exactly once, performing constant-time operations (multiplication, addition, modulo, and append) for each character.
The space complexity is O(n)
, not O(1)
as stated in the reference answer. While the algorithm uses only a constant amount of extra variables (x
and c
), the ans
list grows proportionally with the input size, storing one integer (0 or 1) for each character in the input string. Therefore, the space required for the output list is O(n)
.
Note: If we consider only the auxiliary space (excluding the space needed for the output), then the space complexity would be O(1)
, as we only use a fixed amount of extra variables regardless of input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Attempting to Convert Entire Substring to Integer
The most critical pitfall is trying to convert the substring directly to an integer for divisibility checking:
Incorrect Approach:
def divisibilityArray(self, word: str, m: int) -> List[int]:
result = []
for i in range(len(word)):
# WRONG: This will cause overflow for large strings!
num = int(word[:i+1])
result.append(1 if num % m == 0 else 0)
return result
Why it fails: The string word
can be extremely long (up to 10^5 characters), creating numbers far exceeding Python's integer limits in other languages or causing severe performance issues.
Solution: Use the modular arithmetic approach shown in the correct implementation, maintaining only the remainder at each step.
2. Incorrect Remainder Update Formula
A subtle mistake is incorrectly updating the remainder:
Incorrect:
# Missing parentheses or wrong order of operations
remainder = remainder * 10 + int(digit_char) % m # WRONG!
This applies modulo only to the new digit, not the entire expression.
Correct:
remainder = (remainder * 10 + int(digit_char)) % m
3. Using Wrong Initial Value for Remainder
Incorrect:
remainder = 1 # WRONG: Should start at 0
Starting with a non-zero remainder would incorrectly process the first digit, as it would compute (1 * 10 + first_digit) % m
instead of just first_digit % m
.
4. Forgetting to Handle Edge Cases
While the problem guarantees valid input, in practice you might want to validate:
- Empty string (though problem states positive length)
- Non-digit characters
- Division by zero (m = 0)
Robust version with validation:
def divisibilityArray(self, word: str, m: int) -> List[int]:
if not word or m == 0:
return []
result = []
remainder = 0
for digit_char in word:
if not digit_char.isdigit():
raise ValueError(f"Invalid character: {digit_char}")
remainder = (remainder * 10 + int(digit_char)) % m
result.append(1 if remainder == 0 else 0)
return result
5. Confusion About What Constitutes "Divisible"
Some might incorrectly check if the remainder equals 1 or use the wrong condition:
Incorrect:
result.append(1 if remainder == 1 else 0) # WRONG! result.append(remainder) # WRONG: Should be binary 0 or 1
Correct: A number is divisible by m when remainder is exactly 0.
Which type of traversal does breadth first search do?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview 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
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 assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!