1018. Binary Prefix Divisible By 5
Problem Description
You are given a binary array nums
containing only 0s and 1s, where the array is 0-indexed.
For each position i
in the array, we define x_i
as the decimal number represented by the binary subarray from index 0 to index i
(inclusive). The binary representation reads from left to right, with the leftmost bit being the most significant bit.
For example, if nums = [1,0,1]
:
x_0 = 1
(binary "1" equals decimal 1)x_1 = 2
(binary "10" equals decimal 2)x_2 = 5
(binary "101" equals decimal 5)
Your task is to return an array of booleans answer
where answer[i]
is true
if x_i
is divisible by 5, and false
otherwise.
The key insight is that as we traverse the array, each new bit extends the binary number. When we encounter a new bit, the previous number gets shifted left (multiplied by 2) and the new bit is added. To check divisibility by 5, we only need to track the remainder when divided by 5, since (a * 2 + b) % 5 = ((a % 5) * 2 + b) % 5
. This optimization prevents integer overflow for large arrays.
Intuition
When we read a binary number from left to right, each new bit we encounter effectively doubles the current value and adds the new bit. For instance, if we have binary "10" (decimal 2) and add a "1", we get "101" which is 2 * 2 + 1 = 5
.
The straightforward approach would be to build the complete decimal number at each position and check if it's divisible by 5. However, this could lead to very large numbers that cause overflow issues.
The key observation is that we only care about divisibility by 5, not the actual value. This means we only need to track the remainder when divided by 5. This works because of the modular arithmetic property: (a * 2 + b) % 5 = ((a % 5) * 2 + b) % 5
.
Let's trace through an example with nums = [1,0,1]
:
- Start with
x = 0
- First bit is 1:
x = (0 * 2 + 1) % 5 = 1
. Since1 ≠ 0
, not divisible by 5 - Second bit is 0:
x = (1 * 2 + 0) % 5 = 2
. Since2 ≠ 0
, not divisible by 5 - Third bit is 1:
x = (2 * 2 + 1) % 5 = 0
. Since0 = 0
, divisible by 5
By maintaining only the remainder modulo 5, we avoid overflow and can efficiently determine divisibility. The operation x << 1 | v
is a bitwise way to express x * 2 + v
, where left shift by 1 doubles the value and OR with v
adds the new bit.
Solution Approach
The solution uses a simulation approach with modular arithmetic to efficiently track divisibility by 5.
Implementation Steps:
-
Initialize variables:
- Create an empty list
ans
to store the boolean results - Initialize
x = 0
to represent the current remainder modulo 5
- Create an empty list
-
Iterate through the binary array: For each bit
v
innums
:- Update
x
using the formula:x = (x << 1 | v) % 5
x << 1
performs a left shift, equivalent to multiplying by 2| v
performs a bitwise OR, which adds the current bit (sincev
is either 0 or 1)% 5
takes the remainder to keep the value bounded
- Check if
x == 0
:- If true, the current prefix is divisible by 5, append
True
toans
- Otherwise, append
False
toans
- If true, the current prefix is divisible by 5, append
- Update
-
Return the result array
Why this works:
The expression (x << 1 | v)
is equivalent to x * 2 + v
, which represents adding a new bit to the right of our binary number. By taking modulo 5 at each step, we maintain only the remainder, which is sufficient to determine divisibility.
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the length of the input array, as we iterate through each element once - Space Complexity:
O(1)
for the variables (not counting the output array which isO(n)
)
This approach elegantly avoids potential integer overflow issues that would arise from calculating the full decimal values, especially for large binary arrays.
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 nums = [1,1,0,1,1]
:
Initial state: x = 0
(remainder), ans = []
Step 1: Process nums[0] = 1
- Calculate:
x = (0 << 1 | 1) % 5 = (0 * 2 + 1) % 5 = 1
- Binary so far: "1" = decimal 1
- Is 1 divisible by 5? No (remainder = 1)
ans = [False]
Step 2: Process nums[1] = 1
- Calculate:
x = (1 << 1 | 1) % 5 = (2 + 1) % 5 = 3
- Binary so far: "11" = decimal 3
- Is 3 divisible by 5? No (remainder = 3)
ans = [False, False]
Step 3: Process nums[2] = 0
- Calculate:
x = (3 << 1 | 0) % 5 = (6 + 0) % 5 = 1
- Binary so far: "110" = decimal 6
- Is 6 divisible by 5? No (remainder = 1)
ans = [False, False, False]
Step 4: Process nums[3] = 1
- Calculate:
x = (1 << 1 | 1) % 5 = (2 + 1) % 5 = 3
- Binary so far: "1101" = decimal 13
- Is 13 divisible by 5? No (remainder = 3)
ans = [False, False, False, False]
Step 5: Process nums[4] = 1
- Calculate:
x = (3 << 1 | 1) % 5 = (6 + 1) % 5 = 2
- Binary so far: "11011" = decimal 27
- Is 27 divisible by 5? No (remainder = 2)
ans = [False, False, False, False, False]
Final Result: [False, False, False, False, False]
Note how we never store the actual decimal values (1, 3, 6, 13, 27) but only track remainders (1, 3, 1, 3, 2), preventing overflow while still determining divisibility by 5.
Solution Implementation
1class Solution:
2 def prefixesDivBy5(self, nums: List[int]) -> List[bool]:
3 """
4 Check if each prefix of binary array forms a number divisible by 5.
5
6 Args:
7 nums: List of binary digits (0 or 1)
8
9 Returns:
10 List of booleans where result[i] indicates if prefix nums[0:i+1]
11 forms a binary number divisible by 5
12 """
13 result = []
14 current_value = 0
15
16 for bit in nums:
17 # Build the binary number incrementally:
18 # - Left shift current value by 1 position (multiply by 2)
19 # - Add the new bit using bitwise OR
20 # - Take modulo 5 to prevent overflow and keep only remainder
21 current_value = (current_value << 1 | bit) % 5
22
23 # Check if current prefix forms a number divisible by 5
24 # (remainder equals 0 means divisible by 5)
25 result.append(current_value == 0)
26
27 return result
28
1class Solution {
2 public List<Boolean> prefixesDivBy5(int[] nums) {
3 // Initialize result list to store divisibility results
4 List<Boolean> result = new ArrayList<>();
5
6 // Track the running remainder when dividing by 5
7 int remainder = 0;
8
9 // Iterate through each binary digit in the array
10 for (int binaryDigit : nums) {
11 // Left shift the current remainder (multiply by 2) and add the new binary digit
12 // Then take modulo 5 to keep the remainder manageable and avoid overflow
13 remainder = (remainder << 1 | binaryDigit) % 5;
14
15 // Check if current binary number formed is divisible by 5
16 // (remainder equals 0 means divisible by 5)
17 result.add(remainder == 0);
18 }
19
20 return result;
21 }
22}
23
1class Solution {
2public:
3 vector<bool> prefixesDivBy5(vector<int>& nums) {
4 vector<bool> result;
5 int currentRemainder = 0;
6
7 // Process each binary digit in the array
8 for (int binaryDigit : nums) {
9 // Left shift the current value (multiply by 2) and add the new binary digit
10 // Then take modulo 5 to keep the remainder manageable
11 currentRemainder = ((currentRemainder << 1) | binaryDigit) % 5;
12
13 // Check if the current prefix forms a number divisible by 5
14 // (remainder equals 0 means divisible by 5)
15 result.push_back(currentRemainder == 0);
16 }
17
18 return result;
19 }
20};
21
1/**
2 * Determines if binary number prefixes are divisible by 5
3 * @param nums - Array of binary digits (0s and 1s)
4 * @returns Array of booleans indicating if each prefix forms a number divisible by 5
5 */
6function prefixesDivBy5(nums: number[]): boolean[] {
7 // Array to store divisibility results for each prefix
8 const result: boolean[] = [];
9
10 // Running value of the binary number formed by the prefix
11 let currentValue = 0;
12
13 // Process each binary digit
14 for (const binaryDigit of nums) {
15 // Shift left (multiply by 2) and add the current binary digit
16 // Use modulo 5 to keep the value manageable and avoid overflow
17 currentValue = ((currentValue << 1) | binaryDigit) % 5;
18
19 // Check if current prefix value is divisible by 5
20 result.push(currentValue === 0);
21 }
22
23 return result;
24}
25
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the input array nums
. The algorithm iterates through each element in the array exactly once, performing constant-time operations (bit shift, bitwise OR, modulo, and comparison) for each element.
Space Complexity: O(1)
if we exclude the space required for the output array ans
. The only additional variable used is x
, which stores the running remainder and uses constant space regardless of the input size. If we include the output array, the space complexity would be O(n)
since the ans
array stores n
boolean values.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow Without Modular Arithmetic
The Pitfall: A common mistake is trying to compute the full decimal value at each step without using modular arithmetic:
# WRONG APPROACH - Will cause overflow for large arrays
def prefixesDivBy5(self, nums: List[int]) -> List[bool]:
result = []
current_value = 0
for bit in nums:
current_value = current_value * 2 + bit # No modulo operation!
result.append(current_value % 5 == 0)
return result
Why it fails: For large binary arrays (e.g., length > 64), the decimal value quickly exceeds integer limits, causing overflow errors or incorrect results.
The Solution: Always apply modulo 5 at each step to keep the value bounded:
current_value = (current_value * 2 + bit) % 5
2. Incorrect Binary Construction
The Pitfall: Some might incorrectly build the binary number by treating it as a string or using wrong bit positioning:
# WRONG - Treating as string concatenation
current_value = int(str(current_value) + str(bit), 2)
# WRONG - Adding bit in wrong position
current_value = current_value + bit * (2 ** position)
The Solution: Use the correct formula that shifts left and adds the new bit:
current_value = (current_value << 1 | bit) % 5 # Or equivalently: current_value = (current_value * 2 + bit) % 5
3. Forgetting to Handle the Modulo Property
The Pitfall: Only checking divisibility at the end instead of maintaining the modulo throughout:
# WRONG - Only checking modulo at append time for bit in nums: current_value = current_value * 2 + bit result.append(current_value % 5 == 0) # Value grows unbounded
Why it matters: The key insight is that (a * 2 + b) % 5 = ((a % 5) * 2 + b) % 5
, which allows us to work with remainders only.
The Solution: Apply modulo 5 immediately after computing the new value to maintain only the remainder throughout the iteration.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!