Files
codetutor/backend/data/questions/binary-prefix-divisible-by-5.yaml

177 lines
7.8 KiB
YAML

title: Binary Prefix Divisible By 5
slug: binary-prefix-divisible-by-5
difficulty: easy
leetcode_id: 1018
leetcode_url: https://leetcode.com/problems/binary-prefix-divisible-by-5/
categories:
- arrays
- math
patterns:
- slug: prefix-sum
is_optimal: true
function_signature: "def prefixes_div_by5(nums: list[int]) -> list[bool]:"
test_cases:
visible:
- input: { nums: [0, 1, 1] }
expected: [true, false, false]
- input: { nums: [1, 1, 1] }
expected: [false, false, false]
- input: { nums: [1, 0, 1, 0] }
expected: [false, false, true, false]
hidden:
- input: { nums: [0] }
expected: [true]
- input: { nums: [1] }
expected: [false]
- input: { nums: [1, 0, 1] }
expected: [false, false, true]
- input: { nums: [0, 0, 0, 0, 0] }
expected: [true, true, true, true, true]
- input: { nums: [1, 1, 0, 0, 1] }
expected: [false, false, false, false, false]
- input: { nums: [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1] }
expected: [false, false, false, false, true, true, true, false, false, true, true, true, true, true, true, false, false, false, false, false]
description: |
You are given a binary array `nums` (**0-indexed**).
We define `x_i` as the number whose binary representation is the subarray `nums[0..i]` (from most-significant-bit to least-significant-bit).
For example, if `nums = [1,0,1]`, then `x_0 = 1`, `x_1 = 2`, and `x_2 = 5`.
Return *an array of booleans* `answer` *where* `answer[i]` *is* `true` *if* `x_i` *is divisible by* `5`.
constraints: |
- `1 <= nums.length <= 10^5`
- `nums[i]` is either `0` or `1`
examples:
- input: "nums = [0,1,1]"
output: "[true, false, false]"
explanation: "The input numbers in binary are 0, 01, 011; which are 0, 1, and 3 in base-10. Only the first number is divisible by 5, so answer[0] is true."
- input: "nums = [1,1,1]"
output: "[false, false, false]"
explanation: "The binary numbers are 1, 11, 111; which are 1, 3, and 7 in base-10. None are divisible by 5."
- input: "nums = [1,0,1,0]"
output: "[false, false, true, false]"
explanation: "The binary numbers are 1, 10, 101, 1010; which are 1, 2, 5, and 10 in base-10. The values 5 and 10 are divisible by 5, but 10 appears at index 3, so answer = [false, false, true, false]."
explanation:
intuition: |
Imagine you're reading a binary number digit by digit, from left to right. Each time you read a new digit, you're essentially building up a larger number.
Think of it like this: if you have the number `5` (binary `101`) and you append a `0`, you get `1010` which is `10` in decimal. Appending a digit doubles the previous number and adds the new bit. Mathematically: `new_number = old_number * 2 + new_bit`.
The key insight is that we don't need to track the actual number — it could grow astronomically large (up to 2<sup>100000</sup>). Instead, we only care about **divisibility by 5**, which means we only need to track the **remainder when divided by 5**.
This works because of **modular arithmetic**: if we know the remainder of `old_number % 5`, we can compute `new_number % 5` directly as `(old_number * 2 + new_bit) % 5`. The intermediate large numbers never need to be stored.
approach: |
We solve this using a **Running Remainder** approach:
**Step 1: Initialise variables**
- `current`: Set to `0` to represent the running number mod 5
- `result`: An empty list to collect our boolean answers
&nbsp;
**Step 2: Iterate through the binary array**
- For each bit in `nums`, update `current` using the formula: `current = (current * 2 + bit) % 5`
- This simulates "shifting left and adding the new bit" while keeping only the remainder
- Append `True` to `result` if `current == 0`, otherwise append `False`
&nbsp;
**Step 3: Return the result**
- Return the `result` list containing booleans for each prefix
&nbsp;
This approach works because `(a * 2 + b) % 5 = ((a % 5) * 2 + b) % 5`. We maintain the invariant that `current` always holds `x_i % 5`.
common_pitfalls:
- title: Computing the Actual Binary Number
description: |
A naive approach might try to build the actual integer value at each step:
```python
num = int(''.join(map(str, nums[:i+1])), 2)
```
With `nums.length <= 10^5`, the binary number can have 100,000 digits. This number is astronomically large (approximately 2<sup>100000</sup>) and will cause **memory issues** and **timeout errors**.
Instead, use modular arithmetic to track only the remainder.
wrong_approach: "Converting binary subarray to integer"
correct_approach: "Track running remainder mod 5"
- title: Forgetting to Apply Modulo After Each Step
description: |
If you compute `current = current * 2 + bit` without taking `% 5`, the value will still overflow (in languages with fixed-size integers) or grow unnecessarily large.
Always apply `% 5` at each step: `current = (current * 2 + bit) % 5`.
wrong_approach: "Accumulating without modulo"
correct_approach: "Apply % 5 after each update"
- title: Off-by-One in Understanding the Prefix
description: |
The prefix `x_i` includes `nums[0]` through `nums[i]` inclusive. Make sure you're checking divisibility at the right moment — after incorporating the i<sup>th</sup> bit, not before.
key_takeaways:
- "**Modular arithmetic** allows you to track divisibility without storing huge numbers"
- "**Building numbers bit by bit**: `new = old * 2 + bit` is the fundamental operation for constructing binary numbers left-to-right"
- "**Property preservation**: If you only care about a property (like divisibility), you may not need the full value"
- "**Pattern recognition**: This technique applies to any divisibility check on incrementally built numbers"
time_complexity: "O(n). We traverse the array once, performing constant-time arithmetic at each step."
space_complexity: "O(n). We store the result array of length `n`. The running remainder uses O(1) extra space."
solutions:
- approach_name: Running Remainder
is_optimal: true
code: |
def prefixes_div_by5(nums: list[int]) -> list[bool]:
# Track the current number mod 5
current = 0
result = []
for bit in nums:
# Shift left (multiply by 2) and add new bit, keep only remainder
current = (current * 2 + bit) % 5
# Check if current prefix is divisible by 5
result.append(current == 0)
return result
explanation: |
**Time Complexity:** O(n) — Single pass through the array.
**Space Complexity:** O(n) — Output array of size n; O(1) auxiliary space.
We use modular arithmetic to track divisibility without computing the actual (potentially huge) binary numbers. At each step, `current` holds the value of the prefix modulo 5.
- approach_name: Brute Force (Convert to Integer)
is_optimal: false
code: |
def prefixes_div_by5(nums: list[int]) -> list[bool]:
result = []
for i in range(len(nums)):
# Build binary string from prefix
binary_str = ''.join(str(b) for b in nums[:i+1])
# Convert to integer
num = int(binary_str, 2)
# Check divisibility
result.append(num % 5 == 0)
return result
explanation: |
**Time Complexity:** O(n^2) — For each of n prefixes, we build a string of length up to n.
**Space Complexity:** O(n) — The binary string can grow up to length n.
This approach works for small inputs but fails on large arrays due to the enormous numbers involved. With `n = 10^5`, the final number has 100,000 binary digits — far too large to handle efficiently. Included to illustrate why modular arithmetic is essential.