questions S-W
This commit is contained in:
170
backend/data/questions/subarray-sum-equals-k.yaml
Normal file
170
backend/data/questions/subarray-sum-equals-k.yaml
Normal file
@@ -0,0 +1,170 @@
|
||||
title: Subarray Sum Equals K
|
||||
slug: subarray-sum-equals-k
|
||||
difficulty: medium
|
||||
leetcode_id: 560
|
||||
leetcode_url: https://leetcode.com/problems/subarray-sum-equals-k/
|
||||
categories:
|
||||
- arrays
|
||||
- hash-tables
|
||||
patterns:
|
||||
- prefix-sum
|
||||
|
||||
description: |
|
||||
Given an array of integers `nums` and an integer `k`, return *the total number of subarrays whose sum equals to* `k`.
|
||||
|
||||
A subarray is a contiguous **non-empty** sequence of elements within an array.
|
||||
|
||||
constraints: |
|
||||
- `1 <= nums.length <= 2 * 10^4`
|
||||
- `-1000 <= nums[i] <= 1000`
|
||||
- `-10^7 <= k <= 10^7`
|
||||
|
||||
examples:
|
||||
- input: "nums = [1,1,1], k = 2"
|
||||
output: "2"
|
||||
explanation: "The subarrays [1,1] (indices 0-1) and [1,1] (indices 1-2) both sum to 2."
|
||||
- input: "nums = [1,2,3], k = 3"
|
||||
output: "2"
|
||||
explanation: "The subarrays [1,2] and [3] both sum to 3."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine you're walking along a number line, keeping a running total of all the numbers you've seen so far. This running total is called a **prefix sum**.
|
||||
|
||||
Here's the key insight: if you've reached a certain prefix sum `current_sum`, and you want to find a subarray ending at this position that sums to `k`, you need to find a *previous* prefix sum that equals `current_sum - k`. Why? Because the difference between two prefix sums gives you the sum of the subarray between them.
|
||||
|
||||
Think of it like this: if your cumulative total at position `j` is 10, and your cumulative total at position `i` was 3, then the sum of elements from index `i+1` to `j` is `10 - 3 = 7`. So if you want a subarray sum of `k = 7`, you look for any previous prefix sum of `current_sum - k = 10 - 7 = 3`.
|
||||
|
||||
The trick is to use a **hash map** to count how many times each prefix sum has occurred. As you iterate through the array, you can instantly look up how many times `current_sum - k` has appeared before — each occurrence represents a valid subarray ending at the current index.
|
||||
|
||||
approach: |
|
||||
We solve this using the **Prefix Sum + Hash Map** technique:
|
||||
|
||||
**Step 1: Initialise tracking variables**
|
||||
|
||||
- `count`: Set to `0` to track the total number of valid subarrays
|
||||
- `current_sum`: Set to `0` to track the running prefix sum
|
||||
- `prefix_counts`: A hash map initialised with `{0: 1}` — this accounts for subarrays that start from index 0
|
||||
|
||||
|
||||
|
||||
**Step 2: Iterate through the array**
|
||||
|
||||
- Add the current element to `current_sum`
|
||||
- Calculate `complement = current_sum - k`
|
||||
- If `complement` exists in `prefix_counts`, add its count to our result — each occurrence represents a valid subarray ending here
|
||||
- Add `current_sum` to `prefix_counts` (increment its count if it already exists)
|
||||
|
||||
|
||||
|
||||
**Step 3: Return the result**
|
||||
|
||||
- Return `count` after processing all elements
|
||||
|
||||
|
||||
|
||||
The hash map initialisation `{0: 1}` is crucial: it handles the case where a prefix sum *exactly* equals `k`, meaning the subarray starts from index 0.
|
||||
|
||||
common_pitfalls:
|
||||
- title: The Brute Force Trap
|
||||
description: |
|
||||
A natural first approach is to check every possible subarray with nested loops:
|
||||
- Outer loop for start index
|
||||
- Inner loop for end index
|
||||
- Calculate sum for each subarray
|
||||
|
||||
This results in **O(n^2) time complexity** (or O(n^3) if you recalculate sums each time). With `n = 2 * 10^4`, this means up to 400 million operations — too slow!
|
||||
wrong_approach: "Nested loops checking every subarray"
|
||||
correct_approach: "Prefix sum with hash map for O(n) lookup"
|
||||
|
||||
- title: Forgetting to Initialise the Hash Map with Zero
|
||||
description: |
|
||||
If you start with an empty hash map, you'll miss subarrays that begin at index 0.
|
||||
|
||||
For example, with `nums = [3]` and `k = 3`: the prefix sum after the first element is 3, and we need `current_sum - k = 3 - 3 = 0` to exist in our map. Without `{0: 1}` initialisation, we'd return 0 instead of 1.
|
||||
wrong_approach: "Starting with an empty hash map"
|
||||
correct_approach: "Initialise with {0: 1} to handle subarrays starting at index 0"
|
||||
|
||||
- title: Using Sliding Window Instead
|
||||
description: |
|
||||
Sliding window doesn't work here because the array can contain **negative numbers**. With negative numbers, expanding the window doesn't guarantee the sum increases, and shrinking doesn't guarantee it decreases.
|
||||
|
||||
Sliding window is only valid when all elements are positive (or all negative), where the sum is monotonic with window size.
|
||||
wrong_approach: "Sliding window technique"
|
||||
correct_approach: "Prefix sum with hash map (handles negative numbers)"
|
||||
|
||||
- title: Updating the Hash Map Before Checking
|
||||
description: |
|
||||
The order of operations matters. You must check for `current_sum - k` in the hash map *before* adding `current_sum` to the map.
|
||||
|
||||
If you add first, you might count a subarray of length 0 (from an index to itself) when `k = 0`.
|
||||
wrong_approach: "Adding current_sum to map before checking complement"
|
||||
correct_approach: "Check complement first, then add current_sum to map"
|
||||
|
||||
key_takeaways:
|
||||
- "**Prefix sum pattern**: The difference between two prefix sums gives the sum of elements in between — a powerful technique for range sum queries"
|
||||
- "**Hash map for O(1) lookups**: Instead of searching for complements with nested loops, store counts in a hash map"
|
||||
- "**Initialisation matters**: Starting with `{0: 1}` handles edge cases where the subarray starts at index 0"
|
||||
- "**When sliding window fails**: This problem demonstrates why sliding window requires monotonic relationships — negative numbers break that assumption"
|
||||
|
||||
time_complexity: "O(n). We traverse the array once, with O(1) hash map operations at each step."
|
||||
space_complexity: "O(n). In the worst case, all prefix sums are unique, requiring O(n) space in the hash map."
|
||||
|
||||
solutions:
|
||||
- approach_name: Prefix Sum with Hash Map
|
||||
is_optimal: true
|
||||
code: |
|
||||
def subarray_sum(nums: list[int], k: int) -> int:
|
||||
# Count of valid subarrays found
|
||||
count = 0
|
||||
# Running prefix sum
|
||||
current_sum = 0
|
||||
# Map of prefix_sum -> count of occurrences
|
||||
# Initialise with 0:1 to handle subarrays starting at index 0
|
||||
prefix_counts = {0: 1}
|
||||
|
||||
for num in nums:
|
||||
# Update running sum
|
||||
current_sum += num
|
||||
|
||||
# Check if (current_sum - k) exists in our map
|
||||
# If so, there are that many subarrays ending here with sum k
|
||||
complement = current_sum - k
|
||||
if complement in prefix_counts:
|
||||
count += prefix_counts[complement]
|
||||
|
||||
# Add current prefix sum to the map
|
||||
prefix_counts[current_sum] = prefix_counts.get(current_sum, 0) + 1
|
||||
|
||||
return count
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Single pass through the array with O(1) hash map operations.
|
||||
|
||||
**Space Complexity:** O(n) — Hash map stores up to n unique prefix sums.
|
||||
|
||||
We use the mathematical insight that `sum(i, j) = prefix_sum(j) - prefix_sum(i-1)`. By storing prefix sum counts in a hash map, we can instantly find how many previous positions would create a valid subarray ending at the current position.
|
||||
|
||||
- approach_name: Brute Force
|
||||
is_optimal: false
|
||||
code: |
|
||||
def subarray_sum(nums: list[int], k: int) -> int:
|
||||
count = 0
|
||||
n = len(nums)
|
||||
|
||||
# Try every possible starting index
|
||||
for i in range(n):
|
||||
current_sum = 0
|
||||
# Try every possible ending index
|
||||
for j in range(i, n):
|
||||
current_sum += nums[j]
|
||||
# Check if this subarray sums to k
|
||||
if current_sum == k:
|
||||
count += 1
|
||||
|
||||
return count
|
||||
explanation: |
|
||||
**Time Complexity:** O(n^2) — Nested loops check all possible subarrays.
|
||||
|
||||
**Space Complexity:** O(1) — Only tracking count and current_sum.
|
||||
|
||||
This approach checks every contiguous subarray by trying all start/end combinations. While correct, it's too slow for large inputs. Included to show why the prefix sum optimisation is necessary.
|
||||
Reference in New Issue
Block a user