394. Coins in a Line
Tag:
Dynamic Problem
Main Idea:
DP-Game Problem. This problem can have multiple solutions.
Solution 1:
Assuming player A is the first one to take the coins. Let d p [ i ] \ dp[i] dp[i] represent player A win/lose. If player A has i \ i i coins to to take, it means the other player B may have i − 1 / i − 2 \ i-1/i-2 i−1/i−2 coins to take. If player B chooses i − 1 \ i-1 i−1, then A need to consider whether i − 3 / i − 2 \ i-3/i-2 i−3/i−2; If player B chooses i − 2 \ i-2 i−2, then A need to consider whether i − 3 / i − 4 \ i-3/i-4 i−3/i−4. Thus, the update equation would be: KaTeX parse error: Expected 'EOF', got '&' at position 20: …[i] = (dp[i-2] &̲& dp[i-3]) || d…. For initialization, d p [ 0 ] = d p [ 3 ] = f a l s e , d p [ 1 ] = d p [ 2 ] = t r u e \ dp[0] = dp[3] = false, dp[1] = dp[2] =true dp[0]=dp[3]=false,dp[1]=dp[2]=true. The answer is d p [ n ] \ dp[n] dp[n].
Solution 2:
Let d p [ i ] \ dp[i] dp[i] represent win/lose when there has i coins left. As long as d p [ i − 1 ] = f a l s e ∣ ∣ d p [ i − 2 ] = f a l s e \ dp[i-1]=false || dp[i-2]=false dp[i−1]=false∣∣dp[i−2]=false, then d p [ i ] \ dp[i] dp[i] wins. Thus, the update equation would be: d p [ i ] = t r u e \ dp[i] = true dp[i]=true, if d p [ i − 1 ] = f a l s e ∣ ∣ d p [ i − 2 ] = f a l s e \ dp[i-1]=false || dp[i-2]=false dp[i−1]=false∣∣dp[i−2]=false. For initialization, d p [ 0 ] = f a l s e , d p [ 1 ] = d p [ 2 ] = t r u e \ dp[0] = false, dp[1] = dp[2] =true dp[0]=false,dp[1]=dp[2]=true. The answer is d p [ n ] \ dp[n] dp[n].
Tips/Notes:
For optimization, this problem can apply rolling array to reduce space cost to
O
(
1
)
\ O(1)
O(1)
Time/Space Cost:
Time Cost:
O
(
n
)
\ O(n)
O(n)
Space Cost:
O
(
n
)
\ O(n)
O(n)
Code:
The code here implement Solution1 without optimization.
class Solution {
public:
/**
* @param n: An integer
* @return: A boolean which equals to true if the first player will win
*/
bool firstWillWin(int n) {
// write your code here
vector<bool> dp(n + 1, false);
dp[0] = false;
dp[1] = true;
dp[2] = true;
dp[3] = false;
for(int i = 4; i <= n; i++){
dp[i] = (dp[i-2] && dp[i-3]) || (dp[i-4] && dp[i-3]);
}
return dp[n];
}
};
Follow-up Problem: 395. Coins in a Line II
Main Idea:
Main idea:
Rolling Array DP Problem. Let dp[i] represents the maximum value player can get, sum[i] represent the sum value of coin from i to n-1. In this case, the maximum value player B can get is sum[i]-dp[i] .
Now, let’s discuss the update equation of dp[i] . There will be two cases: the first is player A takes coin[i] ,then the A can get values[i] + sum[i+1] - dp[i+1] ; The other case is that A takes coin[i] + coin[i+1] ,then the A can get values[i] + values[i+1] + sum[i+2] - dp[i+2] . Thus, the update equation would be: dp[i] = max(values[i] + sum[i+1] - dp[i+1], values[i] + values[i+1] + sum[i+2] - dp[i+2]) .
As for initialization, this DP problem is from back to end. Thus, IF n<3 , return true. ELSE, dp[n-1] = sum[n-1] = values[n-1], dp[n-2] = sum[n-2] = values[n-2] + values[n-1] .
The answer is dp[0] > sum[0] - dp[0] .
For optimization, we only need ***sum[i+1]/sum[i+2], dp[i+1]/dp[i+2]***. Thus, we can implement rolling array to save space cost.
Tips/Notes:
Time/Space Cost:
Time Cost:
O
(
n
)
\ O(n)
O(n)
Space Cost:
O
(
2
n
)
\ O(2n)
O(2n),
O
(
1
)
\ O(1)
O(1) if optimize.
Code:
class Solution {
public:
/**
* @param values: a vector of integers
* @return: a boolean which equals to true if the first player will win
*/
bool firstWillWin(vector<int> &values) {
// write your code here
int n = values.size();
if(n < 3){
return true;
}
vector<int> dp(n,0), sum(n, 0);
dp[n-1] = sum[n-1] = values[n-1];
dp[n-2] = sum[n-2] = values[n-2] + values[n-1];
for(int i = n-3; i >= 0; i--){
sum[i] = sum[i+1] + values[i];
dp[i] = max(values[i] + sum[i+1] - dp[i+1],
values[i] + values[i+1] + sum[i+2] - dp[i+2]);
}
return dp[0] > sum[0] - dp[0];
}
};
Follow-up Problem3: 396. Coins in a Line III
Main Idea:
-
State:
dp[i][j] represents the first player A can get the maximum values from i to j. -
Update Function:
There will be two cases: A takes the most left one or the most right one.
-1. A takes the most left one (i), then left (i+1, j) to player B: Left = min(dp[i+2][j], dp[i+1][j-1]) + values[i] .
-2. A takes the most right one (j), then left (i, j-1) to player B: Left = min(dp[i+1][j-1], dp[i][j-2]) + values[j] .
-3. dp[i][j] = max(left, right) -
Initialization:
-1. if x > y, dp[x][y] = 0;
-2. if x = y, dp[x][y] = values[x];
-3. if x + 1 = y, dp[x][y] = max(values[x], values[y]); -
Answer:
Check if the dp[0][n-1] larger than the half of the sum.
Tips/Notes:
- Note the parameter in helper function. Vector should pass pointer.
Time/Space Cost:
Time Cost:
O
(
n
2
)
\ O(n^2)
O(n2)
Space Cost:
O
(
n
2
)
\ O(n^2)
O(n2)
Code:
class Solution {
public:
/**
* @param values: a vector of integers
* @return: a boolean which equals to true if the first player will win
*/
bool firstWillWin(vector<int> &values) {
// write your code here
int n = values.size();
vector<vector<int>> dp(n+1, vector<int>(n+1, -1));
int sum = 0;
for(auto now : values){
sum += now;
}
return sum < 2 * search(values, dp, 0, n-1);
}
int search(vector<int> &values, vector<vector<int>> &dp, int x, int y){
if(dp[x][y] >= 0){
return dp[x][y];
}
if(x > y){
dp[x][y] = 0;
}
else if(x == y){
dp[x][y] = values[x];
}
else if(x + 1 == y){
dp[x][y] = max(values[x], values[y]);
}
else{
int left = values[x] + min(search(values, dp, x+2, y), search(values, dp, x+1, y-1));
int right = values[y] + min(search(values, dp, x+1, y-1), search(values, dp, x, y-2));
dp[x][y] = max(left, right);
}
return dp[x][y];
}
};