Files
codetutor/backend/data/questions/check-if-all-1s-are-at-least-length-k-places-away.yaml

172 lines
8.3 KiB
YAML

title: Check If All 1's Are at Least Length K Places Away
slug: check-if-all-1s-are-at-least-length-k-places-away
difficulty: easy
leetcode_id: 1437
leetcode_url: https://leetcode.com/problems/check-if-all-1s-are-at-least-length-k-places-away/
categories:
- arrays
patterns:
- two-pointers
function_signature: "def k_length_apart(nums: list[int], k: int) -> bool:"
test_cases:
visible:
- input: { nums: [1, 0, 0, 0, 1, 0, 0, 1], k: 2 }
expected: true
- input: { nums: [1, 0, 0, 1, 0, 1], k: 2 }
expected: false
hidden:
- input: { nums: [1], k: 0 }
expected: true
- input: { nums: [0, 0, 0], k: 5 }
expected: true
- input: { nums: [1, 1], k: 0 }
expected: false
- input: { nums: [1, 0, 1], k: 1 }
expected: true
- input: { nums: [1, 0, 0, 0, 0, 1], k: 4 }
expected: true
description: |
Given a binary array `nums` and an integer `k`, return `true` *if all* `1`*'s are at least* `k` *places away from each other, otherwise return* `false`.
In other words, for every pair of `1`s in the array, there must be at least `k` elements (zeros) between them.
constraints: |
- `1 <= nums.length <= 10^5`
- `0 <= k <= nums.length`
- `nums[i]` is `0` or `1`
examples:
- input: "nums = [1,0,0,0,1,0,0,1], k = 2"
output: "true"
explanation: "Each of the 1s are at least 2 places away from each other. The first and second 1s have 3 zeros between them, and the second and third 1s have 2 zeros between them."
- input: "nums = [1,0,0,1,0,1], k = 2"
output: "false"
explanation: "The second 1 (index 3) and third 1 (index 5) are only one position apart from each other (distance = 2 - 1 = 1 < k)."
explanation:
intuition: |
Think of this problem as checking distances between consecutive markers on a number line.
Imagine the array as a row of positions, where some positions have a flag (`1`) and others are empty (`0`). Your task is to verify that every pair of *adjacent* flags has at least `k` empty positions between them.
The key insight is that you only need to check **consecutive** pairs of `1`s. If every consecutive pair has sufficient distance, then all pairs automatically satisfy the condition. This is because if `1` at position A and `1` at position B are far enough apart, and `1` at position B and `1` at position C are far enough apart, then A and C are definitely far enough apart (distance(A,C) = distance(A,B) + distance(B,C)).
As you iterate through the array, whenever you encounter a `1`, you check how far it is from the *previous* `1`. If this distance is less than or equal to `k`, the condition is violated.
approach: |
We solve this using a **Single Pass with Position Tracking** approach:
**Step 1: Initialise tracking variable**
- `prev_one_index`: Set to `-k - 1` initially (or a very negative value), so that the first `1` we encounter won't falsely trigger a violation
&nbsp;
**Step 2: Iterate through the array**
- For each index `i`, check if `nums[i] == 1`
- If it's a `1`, calculate the distance from the previous `1`: `i - prev_one_index`
- If this distance is less than or equal to `k`, return `false` immediately
- Otherwise, update `prev_one_index = i` to track this `1` as the new reference point
&nbsp;
**Step 3: Return the result**
- If we complete the loop without returning `false`, all `1`s are sufficiently spaced
- Return `true`
&nbsp;
The trick of initialising `prev_one_index` to `-k - 1` ensures the first `1` automatically passes the distance check (since `i - (-k-1) = i + k + 1 > k` for any non-negative `i`).
common_pitfalls:
- title: Off-by-One in Distance Calculation
description: |
A common mistake is confusing "k places away" with "distance of k".
If two `1`s are at indices 2 and 5, the distance is `5 - 2 = 3`, meaning there are **2 zeros** between them (indices 3 and 4).
Being "k places away" means the **distance** (difference in indices) must be **greater than k**, not greater than or equal to. With `k = 2`, a distance of exactly 2 means only 1 zero between them, which violates the condition.
The check should be `distance <= k` returns `false`, or equivalently, we need `distance > k` to pass.
wrong_approach: "Checking if distance >= k"
correct_approach: "Checking if distance > k (or distance <= k returns false)"
- title: Forgetting to Handle the First 1
description: |
If you initialise `prev_one_index` to `0` or don't handle it specially, you might incorrectly flag the first `1` as violating the condition.
For example, with `nums = [0,0,1,...]`, if `prev_one_index = 0`, then when we reach index 2, we'd compute distance = 2, which could falsely fail for `k >= 2`.
Initialising to `-k - 1` (or any value ≤ `-k - 1`) ensures the first `1` always passes since its "distance from the previous" will always be greater than `k`.
wrong_approach: "Initialising prev_one_index to 0"
correct_approach: "Initialising prev_one_index to -k - 1 or using a flag for first occurrence"
- title: Checking All Pairs Instead of Consecutive
description: |
A brute force approach might store all indices of `1`s and check every pair combination, resulting in O(n^2) worst case if many `1`s exist.
This is unnecessary because checking only consecutive pairs is sufficient. If A-B and B-C satisfy the distance requirement, A-C automatically does too.
wrong_approach: "O(n^2) checking all pairs of 1s"
correct_approach: "O(n) checking only consecutive pairs"
key_takeaways:
- "**Single-pass pattern**: Many array problems can be solved by tracking key information (like the last occurrence) as you iterate"
- "**Initialisation trick**: Setting the initial state to a \"safe\" value (like `-k - 1`) eliminates the need for special-case handling"
- "**Consecutive suffices**: When checking pairwise properties with transitivity, you only need to verify consecutive pairs"
- "**Early termination**: Return `false` as soon as a violation is found — no need to continue checking"
time_complexity: "O(n). We traverse the array exactly once, performing constant-time operations at each element."
space_complexity: "O(1). We only use a single integer variable (`prev_one_index`) regardless of the input size."
solutions:
- approach_name: Single Pass with Position Tracking
is_optimal: true
code: |
def k_length_apart(nums: list[int], k: int) -> bool:
# Initialise to a value that ensures the first 1 passes
# Any index minus this will be > k
prev_one_index = -k - 1
for i, num in enumerate(nums):
if num == 1:
# Check distance from previous 1
if i - prev_one_index <= k:
# Distance is too small, condition violated
return False
# Update the position of the last seen 1
prev_one_index = i
# All consecutive 1s are sufficiently spaced
return True
explanation: |
**Time Complexity:** O(n) — Single pass through the array.
**Space Complexity:** O(1) — Only one variable used.
We iterate through the array once, tracking the index of the most recent `1`. When we encounter a new `1`, we check if the distance from the previous `1` exceeds `k`. The clever initialisation of `prev_one_index = -k - 1` handles the edge case of the first `1` elegantly.
- approach_name: Collect Indices Then Check
is_optimal: false
code: |
def k_length_apart(nums: list[int], k: int) -> bool:
# Collect all indices where nums[i] == 1
one_indices = [i for i, num in enumerate(nums) if num == 1]
# Check consecutive pairs
for i in range(1, len(one_indices)):
if one_indices[i] - one_indices[i - 1] <= k:
return False
return True
explanation: |
**Time Complexity:** O(n) — Two passes: one to collect indices, one to check pairs.
**Space Complexity:** O(m) — Where m is the number of 1s in the array (could be O(n) in worst case).
This approach first collects all positions of `1`s into a list, then checks consecutive pairs. While still O(n) time, it uses extra space and requires two passes. The single-pass approach is preferred for both space efficiency and early termination capability.