Opening the reading…
Opening the reading…
RECURSION & BACKTRACKING › RECURSION PROBLEMS
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →At any turn, only the numbers between indices i and j remain. Instead of storing both players' future totals, define the value of this interval as the largest score difference the player whose turn it is can achieve over the other player. A non-negative value means the player to move eventually ties or wins; a negative value means that player eventually loses.
If the player takes nums[i], the opponent becomes the player to move on i + 1 to j. That opponent can secure dfs(i + 1, j) more than the next player, so the current player's net result is nums[i] - dfs(i + 1, j). Taking the right end similarly gives nums[j] - dfs(i, j - 1). The current player chooses the larger result.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n^2) | There are n(n + 1) / 2 non-empty intervals, and each memoized interval evaluates exactly two endpoint choices once. Every later request returns in constant time, so no interval is recomputed. |
| Space | O(n^2) | The memo and seen tables reserve O(n^2) working memory, while the recursion stack adds O(n) and is dominated by the tables. The returned boolean is required output and is excluded. This bound is already the worst shape for the input because all endpoint pairs may be queried. |
#include <algorithm>
#include <functional>
#include <vector>
using namespace std;
class Solution {
public:
bool predictTheWinner(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> memo(n, vector<int>(n, 0));
vector<vector<bool>> seen(n, vector<bool>(n, false));
function<int(int, int)> dfs = [&](int i, int j) -> int {
if (i > j) return 0;
if (seen[i][j]) return memo[i][j];
seen[i][j] = true;
int takeLeft = nums[i] - dfs(i + 1, j);
int takeRight = nums[j] - dfs(i, j - 1);
memo[i][j] = max(takeLeft, takeRight);
return memo[i][j];
};
return dfs(0, n - 1) >= 0;
}
};The subtraction in both choices is the central line of the solution. After the current player takes an endpoint, the opponent becomes the player to move on the smaller interval. Because dfs measures the advantage of whoever moves next, that future advantage belongs on the other side of the current player's score.
You can fill the intervals from short to long instead of asking recursion to discover them. An interval of length one has difference nums[i]. For a longer interval, both intervals on the right side of the recurrence are shorter by one, so their values are already available. This is a different arrangement, not an asymptotic optimisation: it keeps O(n^2) time and uses O(n^2) table space while removing the call stack.
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
bool predictTheWinner(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i) {
dp[i][i] = nums[i];
}
for (int length = 2; length <= n; ++length) {
for (int i = 0; i + length <= n; ++i) {
int j = i + length - 1;
int takeLeft = nums[i] - dp[i + 1][j];
int takeRight = nums[j] - dp[i][j - 1];
dp[i][j] = max(takeLeft, takeRight);
}
}
return dp[0][n - 1] >= 0;
}
};