Greedy
Phase 3 14 solutionsGreedy
mindmap
root((Greedy))
Kadane's Subarray
Maximum Subarray LC53
Maximum Sum Circular Subarray LC918
Longest Turbulent Subarray LC978
Jump Games
Jump Game LC55
Jump Game II LC45
Jump Game VII LC1871
Scheduling
Gas Station LC134
Hand of Straights LC846
Dota2 Senate LC649
Candy LC135
Partitioning
Merge Triplets LC1899
Partition Labels LC763
Valid Parenthesis String LC678
Simulation
Lemonade Change LC860
Problem List
| # | Problem | LC # | Difficulty | Key Technique |
|---|---|---|---|---|
| 1 | Lemonade Change | 860 | Easy | Greedy simulation |
| 2 | Maximum Subarray | 53 | Medium | Kadane's algorithm |
| 3 | Maximum Sum Circular Subarray | 918 | Medium | Kadane's + total - min subarray |
| 4 | Longest Turbulent Subarray | 978 | Medium | Kadane's variant |
| 5 | Jump Game | 55 | Medium | Greedy farthest reach |
| 6 | Jump Game II | 45 | Medium | Greedy BFS levels |
| 7 | Jump Game VII | 1871 | Medium | Sliding window + greedy |
| 8 | Gas Station | 134 | Medium | Single-pass greedy |
| 9 | Hand of Straights | 846 | Medium | Greedy + sorted map |
| 10 | Dota2 Senate | 649 | Medium | Greedy queue |
| 11 | Merge Triplets | 1899 | Medium | Greedy filtering |
| 12 | Partition Labels | 763 | Medium | Last occurrence greedy |
| 13 | Valid Parenthesis String | 678 | Medium | Greedy range tracking |
| 14 | Candy | 135 | Hard | Two-pass greedy |
Greedy Problem Categories
graph TD
G[Greedy Problems] --> SA[Subarray]
G --> JMP[Jump Games]
G --> SCH["Scheduling / Simulation"]
G --> PART[Partitioning]
SA --> S1[Maximum Subarray #53]
SA --> S2[Max Sum Circular #918]
SA --> S3[Longest Turbulent #978]
JMP --> J1[Jump Game #55]
JMP --> J2[Jump Game II #45]
JMP --> J3[Jump Game VII #1871]
SCH --> SC1[Lemonade Change #860]
SCH --> SC2[Gas Station #134]
SCH --> SC3[Hand of Straights #846]
SCH --> SC4[Dota2 Senate #649]
SCH --> SC5[Candy #135]
PART --> P1[Merge Triplets #1899]
PART --> P2[Partition Labels #763]
PART --> P3[Valid Parenthesis String #678]
Kadane's Algorithm Flow
flowchart TD
A["Start: maxSum = nums[0], cur = nums[0]"] --> B[For each num in nums from index 1]
B --> C{"cur < 0?"}
C -- Yes --> D[cur = num]
C -- No --> E[cur = cur + num]
D --> F[maxSum = max of maxSum and cur]
E --> F
F --> G{More elements?}
G -- Yes --> B
G -- No --> H[Return maxSum]
style A fill:#2d6a4f,color:#fff
style H fill:#1b4332,color:#fff
Circular variant (LC 918): answer = max(maxSubarray, totalSum - minSubarray)
Jump Game Greedy Flow
flowchart TD
A[Start: farthest = 0] --> B[For i = 0 to n-1]
B --> C{"i > farthest?"}
C -- Yes --> D[Return False — can't reach i]
C -- No --> E[farthest = max of farthest and i + nums_i]
E --> F{i == n-1?}
F -- Yes --> G[Return True]
F -- No --> B
style D fill:#9b2226,color:#fff
style G fill:#2d6a4f,color:#fff
Jump Game II (LC 45): Track current window end; when i == windowEnd, increment jumps and extend window to farthest.
Gas Station Single-Pass Logic
flowchart TD
A[total = 0, tank = 0, start = 0] --> B[For i = 0 to n-1]
B --> C[gain = gas_i - cost_i]
C --> D[total += gain, tank += gain]
D --> E{"tank < 0?"}
E -- Yes --> F[start = i + 1, tank = 0]
E -- No --> G{More stations?}
F --> G
G -- Yes --> B
G -- No --> H{"total >= 0?"}
H -- Yes --> I[Return start]
H -- No --> J[Return -1 — impossible]
style I fill:#2d6a4f,color:#fff
style J fill:#9b2226,color:#fff
Partition Labels Algorithm Flow
flowchart TD
A[Build last occurrence map for each char] --> B[size = 0, end = 0, result = empty]
B --> C[For i, char in enumerate s]
C --> D[end = max of end and last_char]
D --> E[size += 1]
E --> F{i == end?}
F -- Yes --> G[Append size to result, size = 0]
F -- No --> H{More chars?}
G --> H
H -- Yes --> C
H -- No --> I[Return result]
style A fill:#2d6a4f,color:#fff
style I fill:#1b4332,color:#fff
Complexity Summary
| Problem | Time | Space |
|---|---|---|
| Lemonade Change | O(n) | O(1) |
| Maximum Subarray | O(n) | O(1) |
| Max Sum Circular | O(n) | O(1) |
| Longest Turbulent | O(n) | O(1) |
| Jump Game | O(n) | O(1) |
| Jump Game II | O(n) | O(1) |
| Jump Game VII | O(n) | O(n) |
| Gas Station | O(n) | O(1) |
| Hand of Straights | O(n log n) | O(n) |
| Dota2 Senate | O(n) | O(n) |
| Merge Triplets | O(n) | O(1) |
| Partition Labels | O(n) | O(1) |
| Valid Parenthesis String | O(n) | O(1) |
| Candy | O(n) | O(n) |
Study Order
- Lemonade Change — warm-up greedy simulation
- Maximum Subarray — master Kadane's
- Max Sum Circular Subarray — Kadane's variant
- Longest Turbulent Subarray — Kadane's with conditions
- Jump Game — greedy farthest reach
- Jump Game II — greedy BFS levels
- Jump Game VII — sliding window + greedy
- Gas Station — single-pass invariant
- Partition Labels — last occurrence greedy
- Valid Parenthesis String — greedy range
- Merge Triplets — greedy filtering
- Hand of Straights — sorted map greedy
- Dota2 Senate — queue-based greedy
- Candy — two-pass hard greedy
Greedy
Core Intuition
Make the locally optimal choice at each step, trusting it leads to a global optimum. Greedy works when the problem has optimal substructure and a greedy choice property — you never need to reconsider a past decision.
- Kadane's: Extend or restart subarray based on current sum
- Jump games: Track the farthest reachable index
- Scheduling: Sort by end time / deadline
- Partitioning: Greedily extend current partition as far as possible
Decision Flowchart
flowchart TD
A[Greedy Problem] --> B{What are we optimizing?}
B -->|Max/min contiguous subarray| C["Kadane's variant<br/>Max Subarray, Max Product, Turbulent"]
B -->|Reachability / coverage| D["Jump Game pattern<br/>track max reach"]
B -->|Resource allocation| E{"Circular / global constraint?"}
E -->|Yes| F["Two-pass or total check<br/>Gas Station"]
E -->|No| G["Local greedy<br/>Lemonade Change"]
B -->|Interval / partition| H{Minimize intervals?}
H -->|Yes| I["Sort by end, count overlaps<br/>Non-Overlapping"]
H -->|No| J["Extend partition greedily<br/>Partition Labels"]
B -->|Sequence / game| K["Simulation<br/>Dota2 Senate, Hand of Straights"]
B -->|Distribute items| L["Sort + assign<br/>Candy"]
Problems
Lemonade Change — LC 860 | Easy
- Learn: Greedy: prefer giving $10+$5 change over three $5s (preserve $5 bills)
- Mistake: Not prioritizing $10 bill for $20 change
- Complexity: O(n) time, O(1) space
- Edge: First customer pays $10 or $20 → false immediately
Maximum Subarray — LC 53 | Medium
- Learn: Kadane's: curr = max(num, curr+num); update global max each step
- Mistake: Initializing curr/max to 0 (fails all-negative arrays)
- Complexity: O(n) time, O(1) space
- Edge: All negative → return max element; single element
Maximum Sum Circular Subarray — LC 918 | Medium
- Learn: Answer is max(normal Kadane, total_sum - min_subarray); handle all-negative case
- Mistake: Forgetting the all-negative edge case (circular answer would be 0, but must pick at least one)
- Complexity: O(n) time, O(1) space
- Edge: All negative → return max element (not circular)
Longest Turbulent Subarray — LC 978 | Medium
- Learn: Track current run length; reset when pattern breaks (inc→inc or dec→dec)
- Mistake: Not handling equal adjacent elements (turbulent length resets to 1)
- Complexity: O(n) time, O(1) space
- Edge: All equal elements → 1; length 1 → 1
Jump Game — LC 55 | Medium
- Learn: Track
maxReach; if current index > maxReach, return false - Mistake: Simulating all jumps instead of just tracking max reach
- Complexity: O(n) time, O(1) space
- Edge: Single element → true; last element is 0 but already at end
Jump Game II — LC 45 | Medium
- Learn: BFS-like: track current level's farthest reach; increment jumps when exhausting current level
- Mistake: Greedy "always jump to farthest" without tracking level boundaries
- Complexity: O(n) time, O(1) space
- Edge: Already at last index → 0 jumps; guaranteed reachable
Jump Game VII — LC 1871 | Medium
- Learn: Use a sliding window / BFS with a pointer to track reachable positions; avoid re-processing
- Mistake: O(n²) BFS without the "last processed" pointer optimization
- Complexity: O(n) time, O(n) space
- Edge: Start or end is '1' → false; minJump > maxJump
Gas Station — LC 134 | Medium
- Learn: If total gas ≥ total cost, a solution exists; find start by resetting when tank < 0
- Mistake: Not using the total sum check (guarantees unique solution exists)
- Complexity: O(n) time, O(1) space
- Edge: Single station; all costs equal all gas
Hand of Straights — LC 846 | Medium
- Learn: Sort + use ordered map; greedily form groups starting from smallest card
- Mistake: Not checking if total cards divisible by groupSize first
- Complexity: O(n log n) time, O(n) space
- Edge: groupSize=1 → always true; cards not divisible by groupSize → false
Dota2 Senate — LC 649 | Medium
- Learn: Two queues (Radiant, Dire); smaller index bans the other, re-queues with index+n
- Mistake: Using a single array simulation instead of queues (O(n²))
- Complexity: O(n) time, O(n) space
- Edge: All same party → that party wins immediately
Merge Triplets to Form Target — LC 1899 | Medium
- Learn: Filter triplets where any component exceeds target; then check if max of remaining equals target
- Mistake: Including triplets that exceed target in any dimension
- Complexity: O(n) time, O(1) space
- Edge: No valid triplets after filtering → false
Partition Labels — LC 763 | Medium
- Learn: Last occurrence of each char; greedily extend partition end; cut when i == end
- Mistake: Not updating the partition end as you scan
- Complexity: O(n) time, O(1) space (26-char map)
- Edge: All same character → single partition; all unique → n partitions of size 1
Valid Parenthesis String — LC 678 | Medium
- Learn: Track range [lo, hi] of possible open counts; '*' expands range; prune lo < 0
- Mistake: Only tracking one count; not clamping lo to 0
- Complexity: O(n) time, O(1) space
- Edge: All '*' → true; empty string → true
Candy — LC 135 | Hard
- Learn: Two passes: left→right (higher rating than left neighbor gets +1), right→left (same for right); take max at each position
- Mistake: Single pass; not taking max of both passes at each index
- Complexity: O(n) time, O(n) space
- Edge: All equal ratings → n candies; strictly increasing → 1,2,...,n
General Edge Cases
- All-negative arrays (Kadane's variants)
- Single element input
- Circular arrays (Gas Station, Max Sum Circular)
- Ties in greedy ordering (break ties consistently)
- Empty input → 0 or true depending on problem
- Integer overflow for sum-based problems
| # | Problem | LC # | Difficulty | Key Technique |
|---|---|---|---|---|
| 1 | Lemonade Change | 860 | Easy | Greedy simulation |
| 2 | Maximum Subarray | 53 | Medium | Kadane's algorithm |
| 3 | Maximum Sum Circular Subarray | 918 | Medium | Kadane's + total - min subarray |
| 4 | Longest Turbulent Subarray | 978 | Medium | Kadane's variant |
| 5 | Jump Game | 55 | Medium | Greedy farthest reach |
| 6 | Jump Game II | 45 | Medium | Greedy BFS levels |
| 7 | Jump Game VII | 1871 | Medium | Sliding window + greedy |
| 8 | Gas Station | 134 | Medium | Single-pass greedy |
| 9 | Hand of Straights | 846 | Medium | Greedy + sorted map |
| 10 | Dota2 Senate | 649 | Medium | Greedy queue |
| 11 | Merge Triplets | 1899 | Medium | Greedy filtering |
| 12 | Partition Labels | 763 | Medium | Last occurrence greedy |
| 13 | Valid Parenthesis String | 678 | Medium | Greedy range tracking |
| 14 | Candy | 135 | Hard | Two-pass greedy |
Candy ▼ expand
from typing import List
class Solution:
"""
## 135. Candy
There are n children standing in a line. Each child is assigned a rating value.
You must give each child at least one candy. Children with a higher rating than
their adjacent neighbor must receive more candies. Return the minimum total candies.
### Example
```text
Input: ratings = [1, 0, 2]
Output: 5 (give [2, 1, 2] candies)
Input: ratings = [1, 2, 2]
Output: 4 (give [1, 2, 1] candies)
```
### Constraints
- 1 <= ratings.length <= 2 * 10^4
- 0 <= ratings[i] <= 2 * 10^4
"""
def candy(self, ratings: List[int]) -> int:
# Two passes: left-to-right ensures higher-rated child gets more than left neighbor
# Right-to-left ensures higher-rated child gets more than right neighbor
# Take max of both passes at each position
# O(n) time, O(n) space — two-pass left-right then right-left
n = len(ratings)
candies = [1] * n
# Left pass: ensure right neighbor with higher rating gets more
for i in range(1, n):
if ratings[i] > ratings[i - 1]:
candies[i] = candies[i - 1] + 1
# Right pass: ensure left neighbor with higher rating gets more
for i in range(n - 2, -1, -1):
if ratings[i] > ratings[i + 1]:
candies[i] = max(candies[i], candies[i + 1] + 1)
return sum(candies)
if __name__ == '__main__':
s = Solution()
assert s.candy([1, 0, 2]) == 5
assert s.candy([1, 2, 2]) == 4
assert s.candy([1]) == 1
assert s.candy([1, 3, 2, 2, 1]) == 7
print("All tests passed.") Dota2 Senate ▼ expand
from collections import deque
class Solution:
"""
## 649. Dota2 Senate
In the Dota2 world, the senate consists of senators from two parties: Radiant (R) and Dire (D).
Each senator can either ban another senator's right or announce victory if all remaining senators
are from their party. Both sides act optimally. Return the winning party.
### Example
```text
Input: senate = "RD"
Output: "Radiant"
Input: senate = "RDD"
Output: "Dire"
```
### Constraints
- 1 <= senate.length <= 10^4
- senate[i] is either 'R' or 'D'
"""
def predictPartyVictory(self, senate: str) -> str:
# Two queues of indices; smaller index bans the other party's next senator
# Re-enqueue the winner with index += n to maintain relative order
# O(n) time, O(n) space — queue simulation
radiant = deque()
dire = deque()
n = len(senate)
for i, c in enumerate(senate):
if c == 'R':
radiant.append(i)
else:
dire.append(i)
while radiant and dire:
r, d = radiant.popleft(), dire.popleft()
# Whoever has the lower index acts first and bans the other
if r < d:
radiant.append(r + n)
else:
dire.append(d + n)
return "Radiant" if radiant else "Dire"
if __name__ == '__main__':
s = Solution()
assert s.predictPartyVictory("RD") == "Radiant"
assert s.predictPartyVictory("RDD") == "Dire"
assert s.predictPartyVictory("R") == "Radiant"
assert s.predictPartyVictory("DDRRR") == "Dire"
print("All tests passed.") Gas Station ▼ expand
from typing import List
class Solution:
"""
## 134. Gas Station
There are n gas stations in a circle. You are given two integer arrays gas and cost.
Return the starting station's index if you can travel around the circuit once in the
clockwise direction, otherwise return -1. If a solution exists, it is guaranteed to be unique.
### Example
```text
Input: gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2]
Output: 3
Input: gas = [2, 3, 4], cost = [3, 4, 3]
Output: -1
```
### Constraints
- n == gas.length == cost.length
- 1 <= n <= 10^5
- 0 <= gas[i], cost[i] <= 10^4
"""
def canCompleteCircuit_brute(self, gas: List[int], cost: List[int]) -> int:
# O(n^2) time, O(1) space
n = len(gas)
for start in range(n):
tank = 0
for i in range(n):
idx = (start + i) % n
tank += gas[idx] - cost[idx]
if tank < 0:
break
else:
return start
return -1
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
# If total gas >= total cost, a solution exists
# Start from the index after any point where running sum goes negative
# O(n) time, O(1) space — single pass
total = tank = start = 0
for i, (g, c) in enumerate(zip(gas, cost)):
diff = g - c
total += diff
tank += diff
if tank < 0:
start = i + 1
tank = 0
return start if total >= 0 else -1
if __name__ == '__main__':
s = Solution()
assert s.canCompleteCircuit([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]) == 3
assert s.canCompleteCircuit([2, 3, 4], [3, 4, 3]) == -1
assert s.canCompleteCircuit_brute([1, 2, 3, 4, 5], [3, 4, 5, 1, 2]) == 3
print("All tests passed.") Hand Of Straights ▼ expand
from typing import List
from collections import Counter
class Solution:
"""
## 846. Hand of Straights
Given an integer array hand and an integer groupSize, return true if the cards can be
rearranged into groups of groupSize consecutive cards.
### Example
```text
Input: hand = [1, 2, 3, 6, 2, 3, 4, 7, 8], groupSize = 3
Output: true (groups: [1,2,3], [2,3,4], [6,7,8])
Input: hand = [1, 2, 3, 4, 5], groupSize = 4
Output: false
```
### Constraints
- 1 <= hand.length <= 10^4
- 0 <= hand[i] <= 10^9
- 1 <= groupSize <= hand.length
"""
def isNStraightHand(self, hand: List[int], groupSize: int) -> bool:
# Sort cards; greedily form groups starting from the smallest card
# Use a counter; for each group start, consume groupSize consecutive values
# O(n log n) time, O(n) space — sort + greedy with counter
if len(hand) % groupSize:
return False
count = Counter(hand)
for card in sorted(count):
if count[card] > 0:
need = count[card]
for i in range(groupSize):
if count[card + i] < need:
return False
count[card + i] -= need
return True
if __name__ == '__main__':
s = Solution()
assert s.isNStraightHand([1, 2, 3, 6, 2, 3, 4, 7, 8], 3) == True
assert s.isNStraightHand([1, 2, 3, 4, 5], 4) == False
assert s.isNStraightHand([1, 2, 3], 3) == True
assert s.isNStraightHand([1], 1) == True
print("All tests passed.") Jump Game ▼ expand
from typing import List
class Solution:
"""
## 55. Jump Game
Given an integer array nums where nums[i] is the maximum jump length from index i,
return true if you can reach the last index starting from index 0.
### Example
```text
Input: nums = [2, 3, 1, 1, 4]
Output: true
Input: nums = [3, 2, 1, 0, 4]
Output: false
```
### Constraints
- 1 <= nums.length <= 10^4
- 0 <= nums[i] <= 10^5
"""
def canJump_dp(self, nums: List[int]) -> bool:
# O(n^2) time, O(n) space — DP
n = len(nums)
dp = [False] * n
dp[0] = True
for i in range(1, n):
for j in range(i):
if dp[j] and j + nums[j] >= i:
dp[i] = True
break
return dp[-1]
def canJump(self, nums: List[int]) -> bool:
# Track the farthest index reachable; if current index exceeds it, can't proceed
# Update farthest at each step; return True if we reach or pass last index
# O(n) time, O(1) space — track farthest reachable index
farthest = 0
for i, jump in enumerate(nums):
if i > farthest:
return False
farthest = max(farthest, i + jump)
return True
if __name__ == '__main__':
s = Solution()
assert s.canJump([2, 3, 1, 1, 4]) == True
assert s.canJump([3, 2, 1, 0, 4]) == False
assert s.canJump([0]) == True
assert s.canJump_dp([2, 3, 1, 1, 4]) == True
assert s.canJump_dp([3, 2, 1, 0, 4]) == False
print("All tests passed.") Jump Game Ii ▼ expand
from typing import List
class Solution:
"""
## 45. Jump Game II
Given a 0-indexed integer array nums, return the minimum number of jumps to reach
the last index. You can always reach the last index.
### Example
```text
Input: nums = [2, 3, 1, 1, 4]
Output: 2 (jump 1 step from index 0 to 1, then 3 steps to last index)
Input: nums = [2, 3, 0, 1, 4]
Output: 2
```
### Constraints
- 1 <= nums.length <= 10^4
- 0 <= nums[i] <= 1000
- The answer is guaranteed to exist.
"""
def jump_dp(self, nums: List[int]) -> int:
# O(n^2) time, O(n) space — DP
n = len(nums)
dp = [float('inf')] * n
dp[0] = 0
for i in range(n):
for j in range(1, nums[i] + 1):
if i + j < n:
dp[i + j] = min(dp[i + j], dp[i] + 1)
return dp[-1]
def jump(self, nums: List[int]) -> int:
# Greedy BFS levels: at each jump, find the farthest reachable position
# Increment jumps when we exhaust the current level's range
# O(n) time, O(1) space — BFS-like level tracking
jumps = cur_end = farthest = 0
for i in range(len(nums) - 1):
farthest = max(farthest, i + nums[i])
if i == cur_end:
jumps += 1
cur_end = farthest
return jumps
if __name__ == '__main__':
s = Solution()
assert s.jump([2, 3, 1, 1, 4]) == 2
assert s.jump([2, 3, 0, 1, 4]) == 2
assert s.jump([1]) == 0
assert s.jump_dp([2, 3, 1, 1, 4]) == 2
print("All tests passed.") Jump Game Vii ▼ expand
class Solution:
"""
## 1871. Jump Game VII
Given a binary string s and integers minJump and maxJump, you start at index 0
(which is '0'). Return true if you can reach the last index, where you can jump
from index i to any index j such that i + minJump <= j <= i + maxJump and s[j] == '0'.
### Example
```text
Input: s = "011010", minJump = 2, maxJump = 3
Output: true (0 -> 3 -> 5)
Input: s = "01101110", minJump = 2, maxJump = 3
Output: false
```
### Constraints
- 2 <= s.length <= 10^5
- s[i] is either '0' or '1'
- s[0] == '0'
- 1 <= minJump <= maxJump < s.length
"""
def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
# BFS/sliding window: track reachable positions; from each, jump [minJump, maxJump]
# Use a pointer to avoid re-processing already-covered positions
# O(n) time, O(n) space — DP with prefix sum sliding window
n = len(s)
reach = [False] * n
reach[0] = True
prefix = 0 # count of reachable positions in current window
for i in range(1, n):
if i >= minJump:
prefix += reach[i - minJump]
if i > maxJump:
prefix -= reach[i - maxJump - 1]
reach[i] = prefix > 0 and s[i] == '0'
return reach[-1]
if __name__ == '__main__':
s = Solution()
assert s.canReach("011010", 2, 3) == True
assert s.canReach("01101110", 2, 3) == False
assert s.canReach("00", 1, 1) == True
print("All tests passed.") Lemonade Change ▼ expand
from typing import List
class Solution:
"""
## 860. Lemonade Change
At a lemonade stand, each lemonade costs $5. Customers pay with $5, $10, or $20 bills.
You start with no change. Return true if you can provide every customer with correct change.
### Example
```text
Input: bills = [5, 5, 5, 10, 20]
Output: true
Input: bills = [5, 5, 10, 10, 20]
Output: false
```
### Constraints
- 1 <= bills.length <= 10^5
- bills[i] is either 5, 10, or 20
"""
def lemonadeChange(self, bills: List[int]) -> bool:
# Track $5 and $10 bills; greedily use $10 before $5 when making $15 change
# O(n) time, O(1) space
five = ten = 0
for bill in bills:
if bill == 5:
five += 1
elif bill == 10:
if not five:
return False
five -= 1
ten += 1
else: # bill == 20
if ten and five:
ten -= 1
five -= 1
elif five >= 3:
five -= 3
else:
return False
return True
if __name__ == '__main__':
s = Solution()
assert s.lemonadeChange([5, 5, 5, 10, 20]) == True
assert s.lemonadeChange([5, 5, 10, 10, 20]) == False
assert s.lemonadeChange([5]) == True
assert s.lemonadeChange([10]) == False
print("All tests passed.") Longest Turbulent Subarray ▼ expand
from typing import List
class Solution:
"""
## 978. Longest Turbulent Subarray
A subarray is turbulent if the comparison sign alternates between each adjacent pair.
Return the length of the longest turbulent subarray.
### Example
```text
Input: arr = [9, 4, 2, 10, 7, 8, 8, 1, 9]
Output: 5 (subarray [4, 2, 10, 7, 8])
Input: arr = [4, 8, 12, 16]
Output: 2
Input: arr = [100]
Output: 1
```
### Constraints
- 1 <= arr.length <= 4 * 10^4
- 0 <= arr[i] <= 10^9
"""
def maxTurbulenceSize_window(self, arr: List[int]) -> int:
# O(n) time, O(1) space — sliding window, reset on equal or non-alternating
n = len(arr)
if n == 1:
return 1
best = cur = 1
for i in range(1, n):
cmp = (arr[i] > arr[i-1]) - (arr[i] < arr[i-1]) # -1, 0, or 1
if cmp == 0:
cur = 1
elif i == 1 or cmp == ((arr[i-1] > arr[i-2]) - (arr[i-1] < arr[i-2])):
cur = 2
else:
cur += 1
best = max(best, cur)
return best
def maxTurbulenceSize(self, arr: List[int]) -> int:
# Sliding window: extend if alternating comparison sign, else reset to 2 or 1
# Track current turbulent length and update global max
# O(n) time, O(n) space — DP with inc/dec arrays
n = len(arr)
if n == 1:
return 1
inc = [1] * n # length of turbulent subarray ending here with arr[i-1] < arr[i]
dec = [1] * n # length of turbulent subarray ending here with arr[i-1] > arr[i]
best = 1
for i in range(1, n):
if arr[i] > arr[i-1]:
inc[i] = dec[i-1] + 1
elif arr[i] < arr[i-1]:
dec[i] = inc[i-1] + 1
best = max(best, inc[i], dec[i])
return best
if __name__ == '__main__':
s = Solution()
assert s.maxTurbulenceSize([9, 4, 2, 10, 7, 8, 8, 1, 9]) == 5
assert s.maxTurbulenceSize([4, 8, 12, 16]) == 2
assert s.maxTurbulenceSize([100]) == 1
assert s.maxTurbulenceSize([9, 9]) == 1
assert s.maxTurbulenceSize_window([9, 4, 2, 10, 7, 8, 8, 1, 9]) == 5
assert s.maxTurbulenceSize_window([4, 8, 12, 16]) == 2
assert s.maxTurbulenceSize_window([100]) == 1
print("All tests passed.") Maximum Subarray ▼ expand
from typing import List
class Solution:
"""
## 53. Maximum Subarray
Given an integer array nums, find the subarray with the largest sum and return its sum.
### Example
```text
Input: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Output: 6 (subarray [4, -1, 2, 1])
Input: nums = [1]
Output: 1
Input: nums = [5, 4, -1, 7, 8]
Output: 23
```
### Constraints
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
"""
def maxSubArray_brute(self, nums: List[int]) -> int:
# O(n^2) time, O(1) space
n, best = len(nums), nums[0]
for i in range(n):
curr = 0
for j in range(i, n):
curr += nums[j]
best = max(best, curr)
return best
def maxSubArray(self, nums: List[int]) -> int:
# Kadane's: extend current subarray or start fresh if current sum goes negative
# Track global max throughout
# O(n) time, O(1) space — Kadane's algorithm
best = curr = nums[0]
for x in nums[1:]:
curr = max(x, curr + x)
best = max(best, curr)
return best
if __name__ == '__main__':
s = Solution()
assert s.maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]) == 6
assert s.maxSubArray([1]) == 1
assert s.maxSubArray([5, 4, -1, 7, 8]) == 23
assert s.maxSubArray_brute([-2, 1, -3, 4, -1, 2, 1, -5, 4]) == 6
print("All tests passed.") Maximum Sum Circular Subarray ▼ expand
from typing import List
class Solution:
"""
## 918. Maximum Sum Circular Subarray
Given a circular integer array nums, return the maximum possible sum of a non-empty subarray.
A circular array means the end of the array connects to the beginning.
### Example
```text
Input: nums = [1, -2, 3, -2]
Output: 3 (subarray [3])
Input: nums = [5, -3, 5]
Output: 10 (subarray [5, 5] wrapping around)
Input: nums = [-3, -2, -3]
Output: -2
```
### Constraints
- 1 <= nums.length <= 3 * 10^4
- -3 * 10^4 <= nums[i] <= 3 * 10^4
"""
def maxSubarraySumCircular(self, nums: List[int]) -> int:
# Case 1: max subarray is non-wrapping — standard Kadane's
# Case 2: max subarray wraps — equals total_sum - min_subarray_sum
# Edge case: if all elements negative, return max (min subarray = whole array)
# O(n) time, O(1) space — Kadane's for both max and min subarray
total = sum(nums)
max_sum = cur_max = nums[0]
min_sum = cur_min = nums[0]
for x in nums[1:]:
cur_max = max(x, cur_max + x)
max_sum = max(max_sum, cur_max)
cur_min = min(x, cur_min + x)
min_sum = min(min_sum, cur_min)
# If all negative, max_sum is the answer (total - min_sum would be 0)
return max(max_sum, total - min_sum) if max_sum > 0 else max_sum
if __name__ == '__main__':
s = Solution()
assert s.maxSubarraySumCircular([1, -2, 3, -2]) == 3
assert s.maxSubarraySumCircular([5, -3, 5]) == 10
assert s.maxSubarraySumCircular([-3, -2, -3]) == -2
print("All tests passed.") Merge Triplets To Form Target Triplet ▼ expand
from typing import List
class Solution:
"""
## 1899. Merge Triplets to Form Target Triplet
A triplet is an array of three integers. You are given a 2D array triplets and a target triplet.
You may choose any two triplets and update one of them to [max(a,x), max(b,y), max(c,z)].
Return true if it is possible to obtain the target triplet as an element of triplets.
### Example
```text
Input: triplets = [[2,5,3],[1,8,4],[1,7,5]], target = [2,7,5]
Output: true (merge [2,5,3] and [1,7,5] -> [2,7,5])
Input: triplets = [[3,4,5],[4,5,6]], target = [3,2,5]
Output: false
```
### Constraints
- 1 <= triplets.length <= 10^5
- triplets[i].length == target.length == 3
- 1 <= triplets[i][j], target[j] <= 1000
"""
def mergeTriplets(self, triplets: List[List[int]], target: List[int]) -> bool:
# Only consider triplets where no element exceeds the target
# Check if the union (element-wise max) of valid triplets equals target
# O(n) time, O(1) space — filter valid triplets and accumulate max
result = [0, 0, 0]
for t in triplets:
# Skip triplets that exceed any target component
if t[0] <= target[0] and t[1] <= target[1] and t[2] <= target[2]:
result = [max(result[i], t[i]) for i in range(3)]
return result == target
if __name__ == '__main__':
s = Solution()
assert s.mergeTriplets([[2, 5, 3], [1, 8, 4], [1, 7, 5]], [2, 7, 5]) == True
assert s.mergeTriplets([[3, 4, 5], [4, 5, 6]], [3, 2, 5]) == False
assert s.mergeTriplets([[2, 5, 3], [2, 3, 4], [1, 2, 5]], [2, 5, 5]) == True
print("All tests passed.") Partition Labels ▼ expand
from typing import List
class Solution:
"""
## 763. Partition Labels
You are given a string s. Partition it into as many parts as possible so that each letter
appears in at most one part. Return a list of integers representing the size of each part.
### Example
```text
Input: s = "ababcbacadefegdehijhklij"
Output: [9, 7, 8]
(parts: "ababcbaca", "defegde", "hijhklij")
Input: s = "eccbbbbdec"
Output: [10]
```
### Constraints
- 1 <= s.length <= 500
- s consists of lowercase English letters
"""
def partitionLabels(self, s: str) -> List[int]:
# Record last occurrence of each character
# Extend current partition end to the last occurrence of each char seen
# When current index reaches partition end, record partition size and start new one
# O(n) time, O(1) space — record last occurrence of each character
last = {c: i for i, c in enumerate(s)}
result = []
start = end = 0
for i, c in enumerate(s):
end = max(end, last[c])
if i == end:
result.append(end - start + 1)
start = i + 1
return result
if __name__ == '__main__':
s = Solution()
assert s.partitionLabels("ababcbacadefegdehijhklij") == [9, 7, 8]
assert s.partitionLabels("eccbbbbdec") == [10]
assert s.partitionLabels("a") == [1]
print("All tests passed.") Valid Parenthesis String ▼ expand
class Solution:
"""
## 678. Valid Parenthesis String
Given a string s containing '(', ')', and '*', return true if s is valid.
'*' can be treated as '(', ')', or an empty string.
### Example
```text
Input: s = "(*)"
Output: true
Input: s = "(*))"
Output: true
Input: s = "(*())"
Output: true
```
### Constraints
- 1 <= s.length <= 100
- s[i] is '(', ')' or '*'
"""
def checkValidString_two_pass(self, s: str) -> bool:
# O(n) time, O(1) space — two-pass left-to-right and right-to-left
# Left pass: treat '*' as '('
opens = 0
for c in s:
opens += 1 if c != ')' else -1
if opens < 0:
return False
# Right pass: treat '*' as ')'
closes = 0
for c in reversed(s):
closes += 1 if c != '(' else -1
if closes < 0:
return False
return True
def checkValidString(self, s: str) -> bool:
# Track range [lo, hi] of possible open-paren counts
# '(' increases both, ')' decreases both, '*' widens range by ±1
# Valid if hi >= 0 throughout and lo == 0 at end
# O(n) time, O(1) space — track range [lo, hi] of possible open counts
lo = hi = 0
for c in s:
if c == '(':
lo += 1; hi += 1
elif c == ')':
lo -= 1; hi -= 1
else: # '*'
lo -= 1; hi += 1
if hi < 0:
return False
lo = max(lo, 0)
return lo == 0
if __name__ == '__main__':
s = Solution()
assert s.checkValidString("(*)") == True
assert s.checkValidString("(*))") == True
assert s.checkValidString("(*()") == True
assert s.checkValidString(")(") == False
assert s.checkValidString_two_pass("(*)") == True
assert s.checkValidString_two_pass("(*))") == True
print("All tests passed.") Greedy
Track farthest reachable index greedily
2
[0]
3
[1]
1
[2]
1
[3]
4
[4]
Jump Game: can you reach the last index? Press "Start".