177 lines
7.8 KiB
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
|
|
|
|
|
|
|
|
**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`
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- Return the `result` list containing booleans for each prefix
|
|
|
|
|
|
|
|
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.
|