questions M-R

This commit is contained in:
2025-05-25 12:43:25 +01:00
parent 917c371529
commit 68699f35ec
62 changed files with 12841 additions and 0 deletions

View File

@@ -0,0 +1,213 @@
title: Maximum Product Subarray
slug: maximum-product-subarray
difficulty: medium
leetcode_id: 152
leetcode_url: https://leetcode.com/problems/maximum-product-subarray/
categories:
- arrays
- dynamic-programming
patterns:
- dynamic-programming
description: |
Given an integer array `nums`, find a subarray that has the largest product, and return *the product*.
The test cases are generated so that the answer will fit in a **32-bit** integer.
**Note** that the product of an array with a single element is the value of that element.
constraints: |
- `1 <= nums.length <= 2 * 10^4`
- `-10 <= nums[i] <= 10`
- The product of any subarray of `nums` is **guaranteed** to fit in a **32-bit** integer.
examples:
- input: "nums = [2,3,-2,4]"
output: "6"
explanation: "The subarray [2,3] has the largest product 6."
- input: "nums = [-2,0,-1]"
output: "0"
explanation: "The result cannot be 2, because [-2,-1] is not a subarray (elements must be contiguous)."
explanation:
intuition: |
This problem is a twist on the classic **Maximum Subarray Sum** (Kadane's algorithm). However, products behave differently from sums in one crucial way: **multiplying by a negative number flips the sign**.
Imagine you're tracking the largest product ending at each position. If you encounter a negative number, your current large positive product suddenly becomes a large *negative* product. But here's the twist: if you encounter *another* negative number later, that large negative product could flip back to become the largest positive product!
Think of it like this: negative numbers are "wild cards" that can transform your worst result into your best result. A very negative product multiplied by another negative number becomes a very positive product.
The key insight is that we need to track **both** the maximum and minimum products ending at each position:
- The **maximum** product could come from extending the previous maximum (if current is positive) or from the previous minimum flipping sign
- The **minimum** product matters because it might become the maximum after hitting a negative number
This dual-tracking approach lets us handle the sign-flipping nature of multiplication in a single pass.
approach: |
We solve this using a **Dynamic Programming** approach that tracks both maximum and minimum products:
**Step 1: Initialise variables**
- `max_product`: Set to `nums[0]` — the global maximum product found
- `current_max`: Set to `nums[0]` — the maximum product ending at the current position
- `current_min`: Set to `nums[0]` — the minimum product ending at the current position (needed for negative flips)
&nbsp;
**Step 2: Iterate through the array starting from index 1**
For each element `num`:
- If `num` is negative, swap `current_max` and `current_min` — this prepares for the sign flip
- Update `current_max`: the larger of `num` alone (start fresh) or `current_max * num` (extend)
- Update `current_min`: the smaller of `num` alone (start fresh) or `current_min * num` (extend)
- Update `max_product` if `current_max` is greater
&nbsp;
**Step 3: Return the result**
- Return `max_product` — the largest product subarray found
&nbsp;
The swap trick before updating handles the sign flip elegantly: when we multiply by a negative, what was maximum becomes minimum and vice versa.
common_pitfalls:
- title: Ignoring Negative Numbers
description: |
A common mistake is to apply Kadane's algorithm directly without modification:
```python
current_max = max(num, current_max * num)
```
This fails because it discards negative products that could become positive later.
For example, with `nums = [2, 3, -2, 4, -1]`:
- Without tracking minimum: After `-2`, you'd start fresh with `4`, missing that `2 * 3 * -2 * 4 * -1 = 48`
- The two negatives cancel out, giving a larger product than any positive-only subarray
wrong_approach: "Only track maximum product (standard Kadane's)"
correct_approach: "Track both maximum AND minimum products"
- title: Forgetting Zeros Reset Everything
description: |
When you encounter `0`, any product including it becomes `0`. The subarray must either:
- End before the zero
- Start after the zero
- Be just `[0]` if everything else is negative
The algorithm handles this naturally: `max(num, current_max * num)` will choose `0` (starting fresh) when `current_max * 0 = 0`.
wrong_approach: "Special-case zeros with complex logic"
correct_approach: "Let the max/min comparison handle zeros naturally"
- title: Wrong Swap Timing
description: |
Some implementations swap `current_max` and `current_min` *after* the multiplication, which is incorrect.
The swap must happen **before** computing the new values because we're preparing for how the negative will affect the *previous* max and min.
```python
# Wrong: swap after
current_max = max(num, current_max * num)
current_min = min(num, current_min * num)
if num < 0:
current_max, current_min = current_min, current_max # Too late!
```
wrong_approach: "Swap after computing new max/min"
correct_approach: "Swap before computing when num is negative"
key_takeaways:
- "**Track extremes in both directions**: When operations can flip signs, track both maximum and minimum values"
- "**Negative numbers need special handling**: In products, a very negative value can become the maximum after another negative"
- "**Extension of Kadane's algorithm**: The `max(current, current * num)` pattern extends to products with the min-tracking addition"
- "**Foundation for similar problems**: This dual-tracking technique applies to other problems where values can flip between positive and negative"
time_complexity: "O(n). We traverse the array exactly once, performing constant-time operations at each element."
space_complexity: "O(1). We only use three variables (`max_product`, `current_max`, `current_min`) regardless of input size."
solutions:
- approach_name: Dynamic Programming with Min/Max Tracking
is_optimal: true
code: |
def max_product(nums: list[int]) -> int:
if not nums:
return 0
# Initialise with first element
max_product = nums[0]
current_max = nums[0]
current_min = nums[0]
for i in range(1, len(nums)):
num = nums[i]
# If negative, max becomes min and min becomes max after multiplication
if num < 0:
current_max, current_min = current_min, current_max
# Either start fresh with num, or extend the previous subarray
current_max = max(num, current_max * num)
current_min = min(num, current_min * num)
# Update global maximum
max_product = max(max_product, current_max)
return max_product
explanation: |
**Time Complexity:** O(n) — Single pass through the array.
**Space Complexity:** O(1) — Only three variables used.
The key insight is swapping max and min when we encounter a negative number. This handles the sign-flip elegantly: multiplying a large negative (previous min) by a negative gives a large positive (new max).
- approach_name: Dynamic Programming (Explicit Candidates)
is_optimal: true
code: |
def max_product(nums: list[int]) -> int:
if not nums:
return 0
max_product = nums[0]
current_max = nums[0]
current_min = nums[0]
for i in range(1, len(nums)):
num = nums[i]
# Consider all three candidates for new max and min
candidates = (num, current_max * num, current_min * num)
current_max = max(candidates)
current_min = min(candidates)
max_product = max(max_product, current_max)
return max_product
explanation: |
**Time Complexity:** O(n) — Single pass through the array.
**Space Complexity:** O(1) — Only three variables used.
This version makes the logic more explicit by considering all three candidates at once: the number itself (starting fresh), extending with previous max, or extending with previous min. The swap is implicit in the candidate selection.
- approach_name: Brute Force
is_optimal: false
code: |
def max_product(nums: list[int]) -> int:
n = len(nums)
max_product = nums[0]
# Try every possible subarray
for i in range(n):
product = 1
for j in range(i, n):
product *= nums[j]
max_product = max(max_product, product)
return max_product
explanation: |
**Time Complexity:** O(n^2) — Nested loops checking all subarrays.
**Space Complexity:** O(1) — Only tracking the current product and maximum.
This approach checks every contiguous subarray by fixing a start index and extending to all possible end indices. While correct, it's too slow for large inputs. Included to show why the DP approach is necessary.