AlgoAtlas

Greedy

Phase 3 14 solutions

Greedy

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

  1. Lemonade Change — warm-up greedy simulation
  2. Maximum Subarray — master Kadane's
  3. Max Sum Circular Subarray — Kadane's variant
  4. Longest Turbulent Subarray — Kadane's with conditions
  5. Jump Game — greedy farthest reach
  6. Jump Game II — greedy BFS levels
  7. Jump Game VII — sliding window + greedy
  8. Gas Station — single-pass invariant
  9. Partition Labels — last occurrence greedy
  10. Valid Parenthesis String — greedy range
  11. Merge Triplets — greedy filtering
  12. Hand of Straights — sorted map greedy
  13. Dota2 Senate — queue-based greedy
  14. 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".