如何优化n≤20的按钮翻转问题回溯算法运行时长?
Great job getting your backtracking solution up and running for the button flip problem! Let's dive into some targeted optimizations that can slash that runtime for n=20 from a minute down to just seconds.
Key Observations to Build Optimizations On
First, let's recap your solid existing pruning step: you stop exploring a branch if the row above the current position has mismatched states, since you can't go back to fix it later. We can build on this with smarter constraints and faster operations.
1. Fix First Row Enumeration (Game-Changer for Runtime)
Instead of enumerating every single button's press/no-press state across the entire grid, only enumerate all possible states of the first row (2^n possibilities, which for n=20 is ~1 million—way better than 2^40!). Here's why this works:
- Once the first row's press state is fixed, every button in the second row is completely determined: if a button in the first row is still pressed (
O), you must press the button directly below it to flip it (since you can't go back to the first row anymore). - Repeat this logic for every subsequent row: each row's press state is determined entirely by the state of the row above it.
- After deriving all rows, just check if the final row is all un-pressed (
#). If yes, calculate the total number of presses and keep track of the minimum.
This cuts the search space from exponential in n² to exponential in n—an enormous reduction for n=20.
2. Replace Array Operations with Bitwise Calculations
Since n ≤ 20, we can represent each row's state as a 32-bit integer (where each bit represents a button: 0 = #, 1 = O). Pressing a button translates to XOR-ing with a precomputed bitmask, which is way faster than modifying a 2D array.
For example:
- Precompute a mask for each column
colin a row: this mask includes bits forcol,col-1(if valid), andcol+1(if valid) (since pressing a button flips itself and left/right neighbors). - Precompute another mask for the row below: pressing column
colflips the same column in the row below, so this mask is just1 << col.
With bitwise operations, state updates and checks happen in nanoseconds instead of microseconds, which adds up quickly for large n.
3. Early Termination with Minimum Press Tracking
Keep track of the smallest number of presses you've found so far that solves the problem. As you derive each row:
- If the total number of presses used already exceeds this minimum, immediately abandon this branch—there's no need to keep calculating, since it can't produce a better solution.
- Initialize this minimum to a high value (like n²) and update it whenever you find a valid solution with fewer presses.
4. Precompute All Press Masks Upfront
Avoid recalculating the bitmasks for each press every time. Precompute two arrays once at the start:
row_masks[col]: The bitmask to XOR with the current row when pressing columncol.below_masks[col]: The bitmask to XOR with the row below when pressing columncol.
This saves redundant calculations during the core search loop.
5. Parallelize the First Row Enumeration
Since each first-row state is independent of the others, you can split the 2^n first-row possibilities into chunks and process them in parallel using multiple threads. Most modern CPUs have 4+ cores, so this can cut runtime roughly in half (or more, depending on your CPU).
Putting It All Together
Here's a quick outline of how to adjust your existing backtracking approach:
- Precompute
row_masksandbelow_masksfor all columns. - Iterate over every possible first-row press state (from 0 to 2^n - 1).
- For each first-row state:
a. Calculate the initial grid state (starting from the input array) after applying the first-row presses.
b. Derive each subsequent row's press state based on the row above's current state.
c. Count the total presses as you go—if it exceeds the current minimum, break early.
d. After processing all rows, check if the final row is all#. If yes, update the minimum press count and save the solution. - Once all first-row states are processed, output the solution with the minimum presses.
This approach retains the core backtracking logic but narrows the search space drastically and speeds up each iteration with bitwise operations.
内容的提问来源于stack exchange,提问作者mille

