Reducing a new problem to a solved one

One-dimensional Dynamic Programming

Reducing a new problem to a solved one

When a constraint wraps around, split into cases where it does not, and reuse what you have.

Key idea

The wrap-around

Arranging the elements in a circle rather than a line adds one constraint: the first and last are now adjacent. That single addition breaks a linear recurrence, because the decision at the end now depends on the decision at the start.

Adding the wrap-around into the state is possible and unpleasant. There is a much better move.

Why it works

Split on the awkward element

Either the first element is taken or it is not. Those two cases are exhaustive, and in each one the circular constraint disappears.

If the first is taken, the last cannot be, so the problem is the linear one over everything except the last. If the first is not taken, the problem is the linear one over everything except the first.

Run the solved linear version twice and take the better answer. No new recurrence, no new state.

The linear version is the one you just wrote, in its rolling two-variable form. The only new work is deciding what to hand it.

Each slice removes one end, so neither run can ever take both ends at once.

Gotcha

The single element is the case that breaks it

With one element, both slices are empty and the maximum of two zeros is zero, when the answer is that element. It needs its own line.

Boundary cases created by slicing are a recurring hazard: the slice is fine for every larger input and degenerate for the smallest. Whenever a solution slices the input, check the smallest case explicitly rather than assuming.

Tip

The two runs overlap and that is fine

Both slices contain the middle elements, so some work is repeated. It is a constant factor of two on a linear algorithm, which is nothing.

Resist optimizing it. The clarity of running a known-correct function twice is worth far more than the saving, and combining them into one pass is where the bugs live.

Key idea

The general move

This is worth generalizing. When one element makes a problem awkward, split into the cases for that element, and check whether the awkwardness disappears in each.

The same move handles problems with a mandatory first choice, a forbidden combination, or an at-most-one constraint. Two clean runs of a simpler algorithm beats one complicated one.

Why does splitting work?

In the circular version, why is running the linear algorithm on the two slices sufficient?

House Robber Ii

Read the constraints first and let them tell you what complexity is expected. Derive the approach, implement it, run the tests, and submit when it passes.

Loading the workspace…
← Previous