← back

Dynamic Programming

Linear 1D DP

Simple 1D recurrence where each state depends on a few previous states. Covers coin change, climbing stairs, decode ways, house robber, and similar problems where you build up the answer incrementally.

You are standing at the bottom of a staircase with n steps. You can climb one step or two steps at a time. How many distinct ways can you reach the top?

If you have never seen dynamic programming before, your first instinct might be to try every possible combination of 1-steps and 2-steps. That works, but it is painfully slow: the number of paths grows exponentially. There is a much better way, and understanding it will unlock an entire family of problems. The key observation is that to reach step i, you must have come from either step i-1 (taking a single step) or step i-2 (taking a double step). So the number of ways to reach step i is the number of ways to reach step i-1 plus the number of ways to reach step i-2. That's it. That is the entire algorithm.

This is linear 1D dynamic programming, probably the most common DP pattern you will encounter in interviews. "Linear" because you sweep through a sequence from left to right. "1D" because your state is a single index. Once you can see this pattern, you will recognize it everywhere.

The core idea

Every linear 1D DP problem has the same skeleton. You define an array dp where dp[i] represents the answer to the problem considering only the first i elements. Then you fill it in from left to right using a recurrence relation, a formula that computes dp[i] from earlier entries, usually dp[i-1] and dp[i-2].

What varies from problem to problem is three things: what dp[i] means, what the recurrence is, and what operator you use to combine subproblems. For counting problems (like climbing stairs), you add. For optimization problems (like house robber), you take a max or min. Getting the operator wrong is a surprisingly common bug: the code compiles and runs, but the answer is silently wrong.

Let's make this concrete with two classic examples.

Climbing stairs

LeetCode 70 captures this directly. The state definition is: dp[i] = the number of distinct ways to reach step i.

The recurrence is: dp[i] = dp[i-1] + dp[i-2]. You can arrive at step i from one step below or two steps below, and these paths are disjoint, so you add.

The base cases are: dp[0] = 1 (one way to be at the ground, do nothing) and dp[1] = 1 (one way to reach step 1, take one step).

1
2
3
4
5
6
7
8
9
def climbStairs(n):
    if n <= 1:
        return 1
    dp = [0] * (n + 1)
    dp[0] = 1
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

If you recognize this recurrence, it is the Fibonacci sequence. That is not a coincidence: Fibonacci is the simplest possible linear DP.

House robber

Consider LeetCode 198: you are a robber planning to rob houses along a street. Each house has a certain amount of money, but you cannot rob two adjacent houses (the alarm system will go off). What is the maximum money you can rob?

The state definition is: dp[i] = the maximum money you can rob from houses 0 through i.

The recurrence requires you to make a choice at each house: rob it, or skip it. If you rob house i, you gain nums[i] but must skip house i-1, so the best you can do from the earlier houses is dp[i-2]. If you skip house i, your total is just dp[i-1]. You want the better option: dp[i] = max(dp[i-1], dp[i-2] + nums[i]).

Notice the operator is now max instead of +. This is an optimization problem, not a counting problem.

1
2
3
4
5
6
7
8
9
10
11
12
def rob(nums):
    if not nums:
        return 0
    n = len(nums)
    if n == 1:
        return nums[0]
    dp = [0] * n
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    for i in range(2, n):
        dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
    return dp[-1]

The space optimization

Look at the two solutions above. In both cases, dp[i] only depends on dp[i-1] and dp[i-2]. We are maintaining an entire array, but at any point we only ever look back two positions. This means we can throw away the array entirely and just keep two variables.

1
2
3
4
5
6
7
8
9
def rob(nums):
    if not nums:
        return 0
    prev2, prev1 = 0, 0
    for num in nums:
        curr = max(prev1, prev2 + num)
        prev2 = prev1
        prev1 = curr
    return prev1

This takes O(1)O(1) space instead of O(n)O(n). In an interview, you should almost always present the space-optimized version. It shows you understand what the DP is actually doing, not just that you can fill in a table.

The variable names prev2 and prev1 are a convention: prev1 is the answer for "everything up to the previous element," and prev2 is the answer for "everything up to two elements ago." After computing the current value, you shift the window forward. The order matters. If you update prev1 before computing curr, you lose the old value of prev1 that you needed.

Decode Ways

LeetCode 91. Decode Ways asks: given a string of digits like "226", how many ways can you decode it into letters, where 'A' = 1, 'B' = 2, ..., 'Z' = 26? For "226", the answer is 3: "BZ" (2, 26), "VF" (22, 6), or "BBF" (2, 2, 6).

The state definition is: dp[i] = the number of ways to decode the first i characters.

The recurrence has two cases. If the single digit s[i-1] is between '1' and '9', it is a valid one-character decoding, so add dp[i-1]. If the two-digit number formed by s[i-2:i] is between 10 and 26, it is a valid two-character decoding, so add dp[i-2]. The key difference from Climbing Stairs is that not every transition is valid. A '0' cannot be decoded on its own, and "37" is not a valid two-digit code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def numDecodings(s):
    if not s or s[0] == '0':
        return 0
    n = len(s)
    prev2, prev1 = 1, 1  # dp[0] = 1, dp[1] = 1
    for i in range(2, n + 1):
        curr = 0
        if s[i - 1] != '0':
            curr += prev1
        two_digit = int(s[i - 2:i])
        if 10 <= two_digit <= 26:
            curr += prev2
        prev2 = prev1
        prev1 = curr
    return prev1

This is a great example of how linear 1D DP adapts to constraints. The skeleton is the same as Climbing Stairs, looking back one and two steps, but each transition is conditionally valid.

Min Cost Climbing Stairs

LeetCode 746. Min Cost Climbing Stairs gives you an array cost where cost[i] is the cost of stepping on stair i. You can start from step 0 or step 1 and climb one or two steps at a time. Find the minimum cost to reach the top (past the last step).

The state definition is: dp[i] = the minimum cost to reach step i. The recurrence is: dp[i] = cost[i] + min(dp[i-1], dp[i-2]). You pay the cost of the current step plus the cheaper of the two ways to get here. The answer is min(dp[n-1], dp[n-2]) since you can jump past the last step from either of the final two positions.

1
2
3
4
5
6
7
def minCostClimbingStairs(cost):
    prev2, prev1 = cost[0], cost[1]
    for i in range(2, len(cost)):
        curr = cost[i] + min(prev1, prev2)
        prev2 = prev1
        prev1 = curr
    return min(prev2, prev1)

Notice how this is an optimization problem (using min) rather than a counting problem (using +). The skeleton is identical to House Robber. The only difference is the recurrence formula.

How to recognize this pattern

When you see a new problem, ask yourself these questions:

Am I making a sequential decision at each position in an array or sequence? Does the decision at position i depend only on a fixed, small number of previous positions, not on all of them, and not on future positions? Am I looking for an optimal value (max, min) or a count of ways?

If the answer to all three is yes, you are almost certainly looking at linear 1D DP. Here are some of the classic problems in this family: Climbing Stairs, House Robber, Min Cost Climbing Stairs, Decode Ways, Maximum Alternating Subsequence Sum, Delete and Earn, and Paint House.

A subtlety: what linear 1D DP is not

Some problems look like they belong here but actually have a different structure. Coin Change asks for the minimum number of coins to make a target amount, and it tempts you into writing a simple left-to-right recurrence. But the coins can be reused, and the state dimension is the target amount rather than the position in an array. It is an unbounded knapsack problem, not linear DP. The tell is that the "previous positions" you depend on are not a fixed window but depend on the coin values.

Similarly, Word Break looks like it should be dp[i] = dp[i-1] or dp[i-2], but you actually need to check all possible word endings at each position, making it O(n⋅m)O(n \cdot m) where m depends on the dictionary. If the set of "previous positions" is variable or data-dependent rather than a fixed constant like 1 or 2, you have left the linear DP pattern and entered something more complex.

Complexity

Time is always O(n)O(n): you visit each element once and do constant work per element. Space is O(1)O(1) with the rolling-variable optimization, or O(n)O(n) if you keep the full table. You might keep the full table during debugging (it is easier to print and inspect), then optimize to O(1)O(1) once the logic is correct.

Putting it all together

The recipe for any linear 1D DP problem is mechanical once you have practiced it a few times:

  1. Define the state: what does dp[i] represent?
  2. Find the recurrence: how does dp[i] relate to dp[i-1], dp[i-2], etc.?
  3. Set the base cases: what are dp[0] and dp[1]?
  4. Choose the operator: are you adding (counting) or taking max/min (optimizing)?
  5. Iterate left to right, filling in the table.
  6. Optimize space by replacing the array with rolling variables.

The hard part is step 1. Once you have the right state definition, everything else follows. If you are stuck, try writing out a few examples by hand. Draw the table. See which earlier entries each cell depends on. The pattern will reveal itself.

Recognizing this pattern

You're probably looking at linear 1D DP when:

  • The input is a single sequence (array or string) and the answer accumulates as you sweep left to right.
  • The decision at index i depends on a fixed, constant number of earlier indices, usually i-1 and i-2, occasionally i-k for small constant k.
  • The problem asks for a count of ways, a maximum/minimum total, or a yes/no reachability over the prefix.
  • Constraints are large enough (n≤105n \le 10^5 or higher) that an O(n2)O(n^2) scan-everything-behind approach would time out.

Common templates:

  • Fibonacci-style counting: dp[i] = dp[i-1] + dp[i-2] for paths/ways. Example: Climbing Stairs.
  • Take-or-skip optimization: dp[i] = max(dp[i-1], dp[i-2] + nums[i]) when adjacent picks conflict. Example: House Robber.
  • Conditional transition: same skeleton as Fibonacci but each lookback only contributes if a validity check passes. Example: Decode Ways.
  • Min-cost prefix: dp[i] = cost[i] + min(dp[i-1], dp[i-2]) for cheapest way to land at i. Example: Min Cost Climbing Stairs.
  • Multi-state per index: track a small tuple (e.g. paint-house color, last-action flag) at each i instead of a scalar. Example: Paint House.

Practice Problems (199)

⊣ 70 Climbing Stairs
⊣ 91 Decode Ways
⊣ 96 Unique Binary Search Trees
⊣ 139 Word Break
⊣ 198 House Robber
⊣ 213 House Robber II
⊣ 322 Coin Change
⊣ 343 Integer Break
⊣ 377 Combination Sum IV
⊣ 403 Frog Jump
⊣ 413 Arithmetic Slices
⊣ 446 Arithmetic Slices II - Subsequence
⊣ 467 Unique Substrings in Wraparound String
⊣ 509 Fibonacci Number
⊣ 518 Coin Change II
⊣ 552 Student Attendance Record II
⊣ 629 K Inverse Pairs Array
⊣ 639 Decode Ways II
⊣ 650 2 Keys Keyboard
⊣ 689 Maximum Sum of 3 Non-Overlapping Subarrays
⊣ 740 Delete and Earn
⊣ 746 Min Cost Climbing Stairs
⊣ 790 Domino and Tromino Tiling
⊣ 798 Smallest Rotation with Highest Score
⊣ 801 Minimum Swaps To Make Sequences Increasing
⊣ 818 Race Car
⊣ 823 Binary Trees With Factors
⊣ 873 Length of Longest Fibonacci Subsequence
⊣ 898 Bitwise ORs of Subarrays
⊣ 920 Number of Music Playlists
⊣ 926 Flip String to Monotone Increasing
⊣ 940 Distinct Subsequences II
⊣ 956 Tallest Billboard
⊣ 960 Delete Columns to Make Sorted III
⊣ 964 Least Operators to Express Number
⊣ 983 Minimum Cost For Tickets
⊣ 1014 Best Sightseeing Pair
⊣ 1035 Uncrossed Lines
⊣ 1043 Partition Array for Maximum Sum
⊣ 1048 Longest String Chain
⊣ 1092 Shortest Common Supersequence
⊣ 1105 Filling Bookcase Shelves
⊣ 1155 Number of Dice Rolls With Target Sum
⊣ 1187 Make Array Strictly Increasing
⊣ 1218 Longest Arithmetic Subsequence of Given Difference
⊣ 1220 Count Vowels Permutation
⊣ 1223 Dice Roll Simulation
⊣ 1235 Maximum Profit in Job Scheduling
⊣ 1262 Greatest Sum Divisible by Three
⊣ 1269 Number of Ways to Stay in the Same Place After Some Steps
⊣ 1335 Minimum Difficulty of a Job Schedule
⊣ 1340 Jump Game V
⊣ 1387 Sort Integers by The Power Value
⊣ 1388 Pizza With 3n Slices
⊣ 1395 Count Number of Teams
⊣ 1402 Reducing Dishes
⊣ 1411 Number of Ways to Paint N × 3 Grid
⊣ 1416 Restore The Array
⊣ 1420 Build Array Where You Can Find The Maximum Exactly K Comparisons
⊣ 1449 Form Largest Integer With Digits That Add up to Target
⊣ 1458 Max Dot Product of Two Subsequences
⊣ 1473 Paint House III
⊣ 1531 String Compression II
⊣ 1537 Get the Maximum Score
⊣ 1547 Minimum Cost to Cut a Stick
⊣ 1553 Minimum Number of Days to Eat N Oranges
⊣ 1575 Count All Possible Routes
⊣ 1578 Minimum Time to Make Rope Colorful
⊣ 1639 Number of Ways to Form a Target String Given a Dictionary
⊣ 1653 Minimum Deletions to Make String Balanced
⊣ 1687 Delivering Boxes from Storage to Ports
⊣ 1749 Maximum Absolute Sum of Any Subarray
⊣ 1751 Maximum Number of Events That Can Be Attended II
⊣ 1770 Maximum Score from Performing Multiplication Operations
⊣ 1824 Minimum Sideway Jumps
⊣ 1871 Jump Game VII
⊣ 1872 Stone Game VIII
⊣ 1883 Minimum Skips to Arrive at Meeting On Time
⊣ 1884 Egg Drop With 2 Eggs and N Floors
⊣ 1911 Maximum Alternating Subsequence Sum
⊣ 1955 Count Number of Special Subsequences
⊣ 1987 Number of Unique Good Subsequences
⊣ 1997 First Day Where You Have Been in All the Rooms
⊣ 2008 Maximum Earnings From Taxi
⊣ 2063 Vowels of All Substrings
⊣ 2110 Number of Smooth Descent Periods of a Stock
⊣ 2140 Solving Questions With Brainpower
⊣ 2147 Number of Ways to Divide a Long Corridor
⊣ 2167 Minimum Time to Remove All Cars Containing Illegal Goods
⊣ 2188 Minimum Time to Finish the Race
⊣ 2209 Minimum White Tiles After Covering With Carpets
⊣ 2218 Maximum Value of K Coins From Piles
⊣ 2222 Number of Ways to Select Buildings
⊣ 2262 Total Appeal of A String
⊣ 2266 Count Number of Texts
⊣ 2310 Sum of Numbers With Units Digit K
⊣ 2311 Longest Binary Subsequence Less Than or Equal to K
⊣ 2318 Number of Distinct Roll Sequences
⊣ 2320 Count Number of Ways to Place Houses
⊣ 2327 Number of People Aware of a Secret
⊣ 2348 Number of Zero-Filled Subarrays
⊣ 2369 Check if There is a Valid Partition For The Array
⊣ 2370 Longest Ideal Subsequence
⊣ 2380 Time Needed to Rearrange a Binary String
⊣ 2430 Maximum Deletions on a String
⊣ 2463 Minimum Total Distance Traveled
⊣ 2466 Count Ways To Build Good Strings
⊣ 2478 Number of Beautiful Partitions
⊣ 2501 Longest Square Streak in an Array
⊣ 2522 Partition String Into Substrings With Values at Most K
⊣ 2547 Minimum Cost to Split an Array
⊣ 2552 Count Increasing Quadruplets
⊣ 2573 Find the String with LCP
⊣ 2585 Number of Ways to Earn Points
⊣ 2645 Minimum Additions to Make Valid String
⊣ 2663 Lexicographically Smallest Beautiful String
⊣ 2681 Power of Heroes
⊣ 2707 Extra Characters in a String
⊣ 2708 Maximum Strength of a Group
⊣ 2712 Minimum Cost to Make All Characters Equal
⊣ 2713 Maximum Strictly Increasing Cells in a Matrix
⊣ 2734 Lexicographically Smallest String After Substring Operation
⊣ 2742 Painting the Walls
⊣ 2745 Construct the Longest New String
⊣ 2746 Decremental String Concatenation
⊣ 2750 Ways to Split Array Into Good Subarrays
⊣ 2767 Partition String Into Minimum Beautiful Substrings
⊣ 2770 Maximum Number of Jumps to Reach the Last Index
⊣ 2771 Longest Non-decreasing Subarray From Two Arrays
⊣ 2786 Visit Array Positions to Maximize Score
⊣ 2809 Minimum Time to Make Array Sum At Most x
⊣ 2811 Check if it is Possible to Split Array
⊣ 2830 Maximize the Profit as the Salesman
⊣ 2836 Maximize Value of Function in a Ball Passing Game
⊣ 2844 Minimum Operations to Make a Special Number
⊣ 2896 Apply Operations to Make Two Strings Equal
⊣ 2900 Longest Unequal Adjacent Groups Subsequence I
⊣ 2901 Longest Unequal Adjacent Groups Subsequence II
⊣ 2919 Minimum Increment Operations to Make Array Beautiful
⊣ 2944 Minimum Number of Coins for Fruits
⊣ 2945 Find Maximum Non-decreasing Array Length
⊣ 2957 Remove Adjacent Almost-Equal Characters
⊣ 3041 Maximize Consecutive Elements in an Array After Modification
⊣ 3077 Maximum Strength of K Disjoint Subarrays
⊣ 3098 Find the Sum of Subsequence Powers
⊣ 3122 Minimum Number of Operations to Satisfy Conditions
⊣ 3129 Find All Possible Stable Binary Arrays I
⊣ 3130 Find All Possible Stable Binary Arrays II
⊣ 3144 Minimum Substring Partition of Equal Character Frequency
⊣ 3147 Taking Maximum Energy From the Mystic Dungeon
⊣ 3154 Find Number of Ways to Reach the K-th Stair
⊣ 3176 Find the Maximum Length of a Good Subsequence I
⊣ 3177 Find the Maximum Length of a Good Subsequence II
⊣ 3186 Maximum Total Damage With Spell Casting
⊣ 3193 Count the Number of Inversions
⊣ 3196 Maximize Total Cost of Alternating Subarrays
⊣ 3201 Find the Maximum Length of Valid Subsequence I
⊣ 3202 Find the Maximum Length of Valid Subsequence II
⊣ 3250 Find the Count of Monotonic Pairs I
⊣ 3251 Find the Count of Monotonic Pairs II
⊣ 3259 Maximum Energy Boost From Two Drinks
⊣ 3290 Maximum Multiplication Score
⊣ 3333 Find the Original Typed String II
⊣ 3335 Total Characters in String After Transformations I
⊣ 3336 Find the Number of Subsequences With Equal GCD
⊣ 3351 Sum of Good Subsequences
⊣ 3366 Minimum Array Sum
⊣ 3389 Minimum Operations to Make Character Frequencies Equal
⊣ 3409 Longest Subsequence With Decreasing Adjacent Difference
⊣ 3429 Paint House IV
⊣ 3469 Find Minimum Cost to Remove Array Elements
⊣ 3473 Sum of K Subarrays With Length at Least M
⊣ 3500 Minimum Cost to Divide Array Into Subarrays
⊣ 3505 Minimum Operations to Make Elements Within K Subarrays Equal
⊣ 3524 Find X Value of Array I
⊣ 3538 Merge Operations for Minimum Travel Time
⊣ 3543 Maximum Weighted K-Edge Path
⊣ 3578 Count Partitions With Max-Min Difference at Most K
⊣ 3592 Inverse Coin Change
⊣ 3599 Partition Array to Minimize XOR
⊣ 3640 Trionic Array II
⊣ 3660 Jump Game IX
⊣ 3661 Maximum Walls Destroyed by Robots
⊣ 3685 Subsequence Sum After Capping Elements
⊣ 3686 Number of Stable Subsequences
⊣ 3693 Climbing Stairs II
⊣ 3699 Number of ZigZag Arrays I
⊣ 3700 Number of ZigZag Arrays II
⊣ 3704 Count No-Zero Pairs That Sum to N
⊣ 3738 Longest Non-Decreasing Subarray After Replacing at Most One Element
⊣ 3743 Maximize Cyclic Partition Score
⊣ 3801 Minimum Cost to Merge Sorted Lists
⊣ 3811 Number of Alternating XOR Partitions
⊣ 3826 Minimum Partition Score
⊣ 3830 Longest Alternating Subarray After Removing At Most One Element
⊣ 3836 Maximum Score Using Exactly K Pairs
⊣ 3840 House Robber V
⊣ 3850 Count Sequences to K
⊣ 3857 Minimum Cost to Split into Ones