152 lines
7.8 KiB
YAML
152 lines
7.8 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
|
|
|
|
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
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- If we complete the loop without returning `false`, all `1`s are sufficiently spaced
|
|
- Return `true`
|
|
|
|
|
|
|
|
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.
|