206 lines
8.8 KiB
YAML
206 lines
8.8 KiB
YAML
title: Subarray Product Less Than K
|
|
slug: subarray-product-less-than-k
|
|
difficulty: medium
|
|
leetcode_id: 713
|
|
leetcode_url: https://leetcode.com/problems/subarray-product-less-than-k/
|
|
categories:
|
|
- arrays
|
|
- two-pointers
|
|
patterns:
|
|
- slug: sliding-window
|
|
is_optimal: true
|
|
|
|
function_signature: "def num_subarray_product_less_than_k(nums: list[int], k: int) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { nums: [10, 5, 2, 6], k: 100 }
|
|
expected: 8
|
|
- input: { nums: [1, 2, 3], k: 0 }
|
|
expected: 0
|
|
hidden:
|
|
- input: { nums: [1, 1, 1], k: 2 }
|
|
expected: 6
|
|
- input: { nums: [10, 9, 10, 4, 3, 8, 3, 3, 6, 2, 10, 10, 9, 3], k: 19 }
|
|
expected: 18
|
|
- input: { nums: [1, 2, 3, 4, 5], k: 1 }
|
|
expected: 0
|
|
- input: { nums: [100], k: 101 }
|
|
expected: 1
|
|
- input: { nums: [100], k: 100 }
|
|
expected: 0
|
|
- input: { nums: [1, 1, 1, 1, 1], k: 2 }
|
|
expected: 15
|
|
|
|
description: |
|
|
Given an array of integers `nums` and an integer `k`, return *the number of contiguous subarrays where the product of all the elements in the subarray is strictly less than* `k`.
|
|
|
|
constraints: |
|
|
- `1 <= nums.length <= 3 * 10^4`
|
|
- `1 <= nums[i] <= 1000`
|
|
- `0 <= k <= 10^6`
|
|
|
|
examples:
|
|
- input: "nums = [10, 5, 2, 6], k = 100"
|
|
output: "8"
|
|
explanation: "The 8 subarrays that have product less than 100 are: [10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [5, 2, 6]. Note that [10, 5, 2] is not included as the product of 100 is not strictly less than k."
|
|
- input: "nums = [1, 2, 3], k = 0"
|
|
output: "0"
|
|
explanation: "Since k = 0, no product of positive integers can be less than 0, so the answer is 0."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you're sliding a flexible window across the array, and inside this window you're multiplying all the numbers together. The window can expand by including more elements from the right, but if the product becomes too large (>= k), you need to shrink the window from the left until the product is valid again.
|
|
|
|
The key insight is this: **when the window is valid (product < k), every subarray ending at the current right pointer and starting anywhere within the window is also valid**. If your window spans from index `left` to `right`, then the subarrays ending at `right` are: `[right]`, `[right-1, right]`, `[right-2, right-1, right]`, ... all the way to `[left, ..., right]`. That's exactly `right - left + 1` new valid subarrays.
|
|
|
|
Think of it like this: each time you extend the window to include a new element, you're asking "how many new valid subarrays does this create?" The answer is the current window size, because each starting position within the window gives you a unique subarray ending at the current position.
|
|
|
|
approach: |
|
|
We solve this using the **Sliding Window** technique:
|
|
|
|
**Step 1: Handle edge cases**
|
|
|
|
- If `k <= 1`, return `0` immediately since all elements are >= 1, so no product can be < 1 (or < 0)
|
|
|
|
|
|
|
|
**Step 2: Initialise variables**
|
|
|
|
- `left`: Left boundary of window, starts at `0`
|
|
- `product`: Running product of elements in current window, starts at `1`
|
|
- `count`: Total count of valid subarrays, starts at `0`
|
|
|
|
|
|
|
|
**Step 3: Expand the window**
|
|
|
|
- Iterate `right` from `0` to `n-1`
|
|
- Multiply `product` by `nums[right]` to include the new element
|
|
|
|
|
|
|
|
**Step 4: Shrink if needed**
|
|
|
|
- While `product >= k` and `left <= right`:
|
|
- Divide `product` by `nums[left]` to remove the leftmost element
|
|
- Increment `left` to shrink the window
|
|
|
|
|
|
|
|
**Step 5: Count valid subarrays**
|
|
|
|
- After shrinking, the window from `left` to `right` is valid
|
|
- Add `right - left + 1` to `count` (this counts all subarrays ending at `right`)
|
|
|
|
|
|
|
|
**Step 6: Return result**
|
|
|
|
- Return `count` after processing all elements
|
|
|
|
common_pitfalls:
|
|
- title: Missing the Edge Case k <= 1
|
|
description: |
|
|
Since all elements in `nums` are >= 1, the product of any non-empty subarray is at least 1. If `k <= 1`, no subarray can have a product strictly less than `k`.
|
|
|
|
Forgetting this check can lead to incorrect results or infinite loops when the window never becomes valid.
|
|
wrong_approach: "Not handling k <= 1 specially"
|
|
correct_approach: "Return 0 immediately if k <= 1"
|
|
|
|
- title: Counting Subarrays Incorrectly
|
|
description: |
|
|
A common mistake is to count all subarrays within the window at each step, leading to overcounting. Or counting only 1 subarray per valid window position.
|
|
|
|
The correct insight is: **at each position `right`, we add exactly `right - left + 1` new subarrays** — these are all the subarrays that *end* at `right` and start anywhere from `left` to `right`.
|
|
wrong_approach: "Counting window_size * (window_size + 1) / 2 or just 1"
|
|
correct_approach: "Add right - left + 1 at each step"
|
|
|
|
- title: The Brute Force Trap
|
|
description: |
|
|
The naive approach checks every possible subarray and computes its product:
|
|
- Outer loop for start index
|
|
- Inner loop for end index
|
|
- Multiply all elements in each subarray
|
|
|
|
This is O(n^2) or O(n^3) depending on implementation. With `nums.length <= 3 * 10^4`, this will cause **Time Limit Exceeded**.
|
|
wrong_approach: "Nested loops checking all subarrays"
|
|
correct_approach: "Sliding window with O(n) time"
|
|
|
|
- title: Integer Overflow Concerns
|
|
description: |
|
|
With `nums[i] <= 1000` and `nums.length <= 3 * 10^4`, the product can become extremely large. However, since we shrink the window whenever `product >= k` and `k <= 10^6`, the product stays bounded.
|
|
|
|
In languages without arbitrary precision integers, be aware of potential overflow, though Python handles this naturally.
|
|
|
|
key_takeaways:
|
|
- "**Sliding window for counting**: When counting valid subarrays with a monotonic property, sliding window often gives O(n) time"
|
|
- "**Count subarrays ending at each position**: Adding `right - left + 1` at each step counts all new subarrays introduced by expanding to `right`"
|
|
- "**Product vs sum windows**: For products, divide to shrink (vs subtract for sums). This works because all elements are positive"
|
|
- "**Related problems**: This pattern applies to subarray sum problems, longest substring problems, and other contiguous sequence counting tasks"
|
|
|
|
time_complexity: "O(n). Each element is added to the window once (when `right` advances) and removed at most once (when `left` advances)."
|
|
space_complexity: "O(1). We only use a fixed number of variables (`left`, `product`, `count`) regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Sliding Window
|
|
is_optimal: true
|
|
code: |
|
|
def numSubarrayProductLessThanK(nums: list[int], k: int) -> int:
|
|
# Edge case: no product of positive integers can be < 1
|
|
if k <= 1:
|
|
return 0
|
|
|
|
left = 0
|
|
product = 1
|
|
count = 0
|
|
|
|
for right in range(len(nums)):
|
|
# Expand window by including nums[right]
|
|
product *= nums[right]
|
|
|
|
# Shrink window from left while product is too large
|
|
while product >= k:
|
|
product //= nums[left]
|
|
left += 1
|
|
|
|
# All subarrays ending at 'right' and starting from 'left' to 'right' are valid
|
|
# That's (right - left + 1) subarrays
|
|
count += right - left + 1
|
|
|
|
return count
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each element enters and leaves the window at most once.
|
|
|
|
**Space Complexity:** O(1) — Only three variables used.
|
|
|
|
The sliding window maintains the invariant that the product of elements from `left` to `right` is always less than `k`. At each position, we count all valid subarrays ending at that position.
|
|
|
|
- approach_name: Brute Force
|
|
is_optimal: false
|
|
code: |
|
|
def numSubarrayProductLessThanK(nums: list[int], k: int) -> int:
|
|
n = len(nums)
|
|
count = 0
|
|
|
|
# Try every starting position
|
|
for i in range(n):
|
|
product = 1
|
|
# Extend to every ending position
|
|
for j in range(i, n):
|
|
product *= nums[j]
|
|
# If product is still valid, count this subarray
|
|
if product < k:
|
|
count += 1
|
|
else:
|
|
# Product can only grow, so no point continuing
|
|
break
|
|
|
|
return count
|
|
explanation: |
|
|
**Time Complexity:** O(n^2) — Nested loops, though we break early when product exceeds k.
|
|
|
|
**Space Complexity:** O(1) — Only tracking product and count.
|
|
|
|
This approach tries every starting index and extends until the product becomes too large. While the early break helps in practice, worst case is still O(n^2). Too slow for large inputs but demonstrates the problem structure.
|