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.