Consider a game where players can score 3, 5, or 10 points in a move. Given a total score of n, the task is to find the number of ways to reach the given score.
Examples:
Input: n = 20
Output: 4
Explanation: There are following 4 ways to reach 20: (10, 10), (5, 5, 10), (5, 5, 5, 5), (3, 3, 3, 3, 3, 5)Input: n = 13
Output: 2
Explanation: There are following 2 ways to reach 13: (3, 5, 5), (3, 10)
The given problem is a variation of the coin change problem.
Table of Content
Using Recursion - O(2^n) Time and O(n) Space
The idea is to use a recursive function that explores all possible combinations of scoring points by making choices at each step. For each point value (3, 5, or 10), we have two options: either take that point value or skip it. The function will recursively explore these choices. When the total score reaches exactly 0, we count it as a valid combination and if it goes below 0, return 0.
Recurrence Relation: The final result will be the sum of both cases:
- countWays(n, i, points) = countWays(n-points[i], i, points) + countWays(n, i+1, points)
Base Cases:
- countWays(n) = 1, if n = 0.
- countWays(n) = 0, if n < 0 or i == 3.
// C++ program to find number of ways
// to reach a given score in a game.
#include <bits/stdc++.h>
using namespace std;
int countRecur(int n, int i, vector<int> &points) {
// Base cases
if (n==0) return 1;
if (n<0 || i==3) return 0;
// take points[i] point
int take = countRecur(n-points[i], i, points);
// skip point
int noTake = countRecur(n, i+1, points);
return take + noTake;
}
int countWays(int n) {
vector<int> points = {3, 5, 10};
return countRecur(n, 0, points);
}
int main() {
int n = 20;
cout << countWays(n);
return 0;
}
// Java program to find number of ways
// to reach a given score in a game.
import java.util.*;
class GfG {
static int countRecur(int n, int i, int[] points) {
// Base cases
if (n == 0) return 1;
if (n < 0 || i == 3) return 0;
// take points[i] point
int take = countRecur(n - points[i], i, points);
// skip point
int noTake = countRecur(n, i + 1, points);
return take + noTake;
}
static int countWays(int n) {
int[] points = {3, 5, 10};
return countRecur(n, 0, points);
}
public static void main(String[] args) {
int n = 20;
System.out.println(countWays(n));
}
}
# Python program to find number of ways
# to reach a given score in a game.
def countRecur(n, i, points):
# Base cases
if n == 0:
return 1
if n < 0 or i == 3:
return 0
# take points[i] point
take = countRecur(n - points[i], i, points)
# skip point
noTake = countRecur(n, i + 1, points)
return take + noTake
def countWays(n):
points = [3, 5, 10]
return countRecur(n, 0, points)
if __name__ == "__main__":
n = 20
print(countWays(n))
// C# program to find number of ways
// to reach a given score in a game.
using System;
class GfG {
static int countRecur(int n, int i, int[] points) {
// Base cases
if (n == 0) return 1;
if (n < 0 || i == 3) return 0;
// take points[i] point
int take = countRecur(n - points[i], i, points);
// skip point
int noTake = countRecur(n, i + 1, points);
return take + noTake;
}
static int countWays(int n) {
int[] points = {3, 5, 10};
return countRecur(n, 0, points);
}
static void Main(string[] args) {
int n = 20;
Console.WriteLine(countWays(n));
}
}
// JavaScript program to find number of ways
// to reach a given score in a game.
function countRecur(n, i, points) {
// Base cases
if (n === 0) return 1;
if (n < 0 || i === 3) return 0;
// take points[i] point
let take = countRecur(n - points[i], i, points);
// skip point
let noTake = countRecur(n, i + 1, points);
return take + noTake;
}
function countWays(n) {
let points = [3, 5, 10];
return countRecur(n, 0, points);
}
let n = 20;
console.log(countWays(n));
Output
4
Using Top-Down DP (Memoization) - O(n) Time and O(n) Space
If we notice carefully, we can observe that the above recursive solution holds the following two properties of Dynamic Programming:
1. Optimal Substructure: Number of ways to make score n at index i, i.e., countWays(n, i, points), depends on the optimal solutions of the subproblems countWays(n-points[i], i, points) and countWays(n, i+1, points). By combining these optimal substructures, we can efficiently calculate the number of ways to make target score at index i.
2. Overlapping Subproblems: While applying a recursive approach in this problem, we notice that certain subproblems are computed multiple times.
- There are two parameters: n and i that changes in the recursive solution. So we create a 2D matrix of size (n+1)*3 for memoization.
- We initialize this matrix as -1 to indicate nothing is computed initially.
- Now we modify our recursive solution to first check if the value is -1, then only make recursive calls. This way, we avoid re-computations of the same subproblems.
// C++ program to find number of ways
// to reach a given score in a game using memmoization
#include <bits/stdc++.h>
using namespace std;
int countRecur(int n, int i, vector<int> &points,
vector<vector<int>> &memo) {
// Base cases
if (n==0) return 1;
if (n<0 || i==3) return 0;
// If value is memoized
if (memo[n][i]!=-1) {
return memo[n][i];
}
// take points[i] point
int take = countRecur(n-points[i], i, points, memo);
// skip point
int noTake = countRecur(n, i+1, points, memo);
return memo[n][i] = take + noTake;
}
int countWays(int n) {
vector<int> points = {3, 5, 10};
vector<vector<int>> memo(n+1, vector<int>(3, -1));
return countRecur(n, 0, points, memo);
}
int main() {
int n = 20;
cout << countWays(n);
return 0;
}
// Java program to find number of ways
// to reach a given score in a game using memmoization
import java.util.Arrays;
class GfG {
static int countRecur(int n, int i, int[] points, int[][] memo) {
// Base cases
if (n == 0) return 1;
if (n < 0 || i == 3) return 0;
// If value is memoized
if (memo[n][i] != -1) {
return memo[n][i];
}
// take points[i] point
int take = countRecur(n - points[i], i, points, memo);
// skip point
int noTake = countRecur(n, i + 1, points, memo);
return memo[n][i] = take + noTake;
}
static int countWays(int n) {
int[] points = {3, 5, 10};
int[][] memo = new int[n + 1][3];
for (int[] row : memo) {
Arrays.fill(row, -1);
}
return countRecur(n, 0, points, memo);
}
public static void main(String[] args) {
int n = 20;
System.out.println(countWays(n));
}
}
# Python program to find number of ways
# to reach a given score in a game using memmoization
def countRecur(n, i, points, memo):
# Base cases
if n == 0:
return 1
if n < 0 or i == 3:
return 0
# If value is memoized
if memo[n][i] != -1:
return memo[n][i]
# take points[i] point
take = countRecur(n - points[i], i, points, memo)
# skip point
noTake = countRecur(n, i + 1, points, memo)
memo[n][i] = take + noTake
return memo[n][i]
def countWays(n):
points = [3, 5, 10]
memo = [[-1] * 3 for _ in range(n + 1)]
return countRecur(n, 0, points, memo)
if __name__ == "__main__":
n = 20
print(countWays(n))
// C# program to find number of ways
// to reach a given score in a game using memmoization
using System;
class GfG {
static int countRecur(int n, int i, int[] points, int[,] memo) {
// Base cases
if (n == 0) return 1;
if (n < 0 || i == 3) return 0;
// If value is memoized
if (memo[n, i] != -1) {
return memo[n, i];
}
// take points[i] point
int take = countRecur(n - points[i], i, points, memo);
// skip point
int noTake = countRecur(n, i + 1, points, memo);
memo[n, i] = take + noTake;
return memo[n, i];
}
static int countWays(int n) {
int[] points = {3, 5, 10};
int[,] memo = new int[n + 1, 3];
for (int x = 0; x <= n; x++) {
for (int y = 0; y < 3; y++) {
memo[x, y] = -1;
}
}
return countRecur(n, 0, points, memo);
}
static void Main(string[] args) {
int n = 20;
Console.WriteLine(countWays(n));
}
}
// JavaScript program to find number of ways
// to reach a given score in a game using memmoization
function countRecur(n, i, points, memo) {
// Base cases
if (n === 0) return 1;
if (n < 0 || i === 3) return 0;
// If value is memoized
if (memo[n][i] !== -1) {
return memo[n][i];
}
// take points[i] point
let take = countRecur(n - points[i], i, points, memo);
// skip point
let noTake = countRecur(n, i + 1, points, memo);
memo[n][i] = take + noTake;
return memo[n][i];
}
function countWays(n) {
let points = [3, 5, 10];
let memo = Array.from({ length: n + 1 }, () => Array(3).fill(-1));
return countRecur(n, 0, points, memo);
}
let n = 20;
console.log(countWays(n));
Output
4
Using Bottom-Up DP (Tabulation) - O(n) Time and O(n) Space
The idea is to fill the DP table based on previous values. For each point, we either include it or exclude it to compute the number of ways needed for each score. The table is filled in an iterative manner from score i = 1 to i = n and for each point points[j] from j=2 to j=0.
The dynamic programming relation is as follows:
- if (i-points[j]) is greater than 0, then dp[i][j] = dp[i-points[j]][j] + dp[i][j+1]
- else dp[i][j] = dp[i][j+1].
// C++ program to find number of ways
// to reach a given score in a game using tabulation
#include <bits/stdc++.h>
using namespace std;
int countWays(int n) {
vector<int> points = {3, 5, 10};
vector<vector<int>> dp(n + 1, vector<int>(4, 0));
// Set 1 for score 0 for all points.
for (int j = 0; j < 4; j++) {
dp[0][j] = 1;
}
// Check for each score
for (int i = 1; i <= n; i++) {
// For each point.
for (int j = 2; j >= 0; j--) {
if (i - points[j] >= 0) {
dp[i][j] = dp[i - points[j]][j] + dp[i][j + 1];
}
else {
dp[i][j] = dp[i][j + 1];
}
}
}
return dp[n][0];
}
int main() {
int n = 20;
cout << countWays(n);
return 0;
}
// Java program to find number of ways
// to reach a given score in a game using tabulation
class GfG {
static int countWays(int n) {
int[] points = {3, 5, 10};
int[][] dp = new int[n + 1][4];
// Set 1 for score 0 for all points.
for (int j = 0; j < 4; j++) {
dp[0][j] = 1;
}
// Check for each score
for (int i = 1; i <= n; i++) {
// For each point.
for (int j = 2; j >= 0; j--) {
if (i - points[j] >= 0) {
dp[i][j] = dp[i - points[j]][j] + dp[i][j + 1];
} else {
dp[i][j] = dp[i][j + 1];
}
}
}
return dp[n][0];
}
public static void main(String[] args) {
int n = 20;
System.out.println(countWays(n));
}
}
# Python program to find number of ways
# to reach a given score in a game using tabulation
def countWays(n):
points = [3, 5, 10]
dp = [[0] * 4 for _ in range(n + 1)]
# Set 1 for score 0 for all points.
for j in range(4):
dp[0][j] = 1
# Check for each score
for i in range(1, n + 1):
# For each point.
for j in range(2, -1, -1):
if i - points[j] >= 0:
dp[i][j] = dp[i - points[j]][j] + dp[i][j + 1]
else:
dp[i][j] = dp[i][j + 1]
return dp[n][0]
if __name__ == "__main__":
n = 20
print(countWays(n))
// C# program to find number of ways
// to reach a given score in a game using tabulation
using System;
class GfG {
static int countWays(int n) {
int[] points = {3, 5, 10};
int[,] dp = new int[n + 1, 4];
// Set 1 for score 0 for all points.
for (int j = 0; j < 4; j++) {
dp[0, j] = 1;
}
// Check for each score
for (int i = 1; i <= n; i++) {
// For each point.
for (int j = 2; j >= 0; j--) {
if (i - points[j] >= 0) {
dp[i, j] = dp[i - points[j], j] + dp[i, j + 1];
} else {
dp[i, j] = dp[i, j + 1];
}
}
}
return dp[n, 0];
}
static void Main(string[] args) {
int n = 20;
Console.WriteLine(countWays(n));
}
}
// JavaScript program to find number of ways
// to reach a given score in a game using tabulation
function countWays(n) {
let points = [3, 5, 10];
let dp = Array.from({ length: n + 1 }, () => Array(4).fill(0));
// Set 1 for score 0 for all points.
for (let j = 0; j < 4; j++) {
dp[0][j] = 1;
}
// Check for each score
for (let i = 1; i <= n; i++) {
// For each point.
for (let j = 2; j >= 0; j--) {
if (i - points[j] >= 0) {
dp[i][j] = dp[i - points[j]][j] + dp[i][j + 1];
} else {
dp[i][j] = dp[i][j + 1];
}
}
}
return dp[n][0];
}
let n = 20;
console.log(countWays(n));
Output
4
Using Space Optimized DP - O(n) Time and O(1) Space
In previous approach of dynamic programming we have derive the relation between states as given below:
- if (i-points[j]) is greater than 0, then dp[i][j] = dp[i-points[j]][j] + dp[i][j+1]
- else dp[i][j] = dp[i][j+1].
If we observe that for calculating current dp[i][j] state we only need to store the last 10 scores. There is no need to store all the previous states to compute result.
// C++ program to find number of ways
// to reach a given score in a game using space optimised dp
#include <bits/stdc++.h>
using namespace std;
int countWays(int n) {
vector<int> points = {3, 5, 10};
// dp to store last 10 scores [-9, 0].
vector<vector<int>> dp(10, vector<int>(4, 0));
// Set ways=1 for score=0.
for (int j = 0; j < 4; j++)
dp[9][j] = 1;
// Check for each score
for (int i = 1; i <= n; i++) {
vector<int> curr(4, 0);
// For each point.
for (int j = 2; j >= 0; j--) {
curr[j] = dp[10 - points[j]][j] + curr[j + 1];
}
// Update dp states.
for (int k = 0; k < 9; k++) {
for (int j = 0; j < 4; j++) {
dp[k][j] = dp[k + 1][j];
}
}
for (int j = 0; j < 4; j++) {
dp[9][j] = curr[j];
}
}
return dp[9][0];
}
int main() {
int n = 20;
cout << countWays(n);
return 0;
}
// Java program to find number of ways
// to reach a given score in a game using space optimised dp
class GfG {
static int countWays(int n) {
int[] points = {3, 5, 10};
// dp to store last 10 scores [-9, 0].
int[][] dp = new int[10][4];
// Set ways=1 for score=0.
for (int j = 0; j < 4; j++) dp[9][j] = 1;
// Check for each score
for (int i = 1; i <= n; i++) {
int[] curr = new int[4];
// For each point.
for (int j = 2; j >= 0; j--) {
curr[j] = dp[10 - points[j]][j] + curr[j + 1];
}
// Update dp states.
for (int k = 0; k < 9; k++) {
for (int j = 0; j < 4; j++) {
dp[k][j] = dp[k + 1][j];
}
}
for (int j = 0; j < 4; j++) {
dp[9][j] = curr[j];
}
}
return dp[9][0];
}
public static void main(String[] args) {
int n = 20;
System.out.println(countWays(n));
}
}
# Python program to find number of ways
# to reach a given score in a game using space optimised dp
def countWays(n):
points = [3, 5, 10]
# dp to store last 10 scores [-9, 0].
dp = [[0] * 4 for _ in range(10)]
# Set ways=1 for score=0.
for j in range(4):
dp[9][j] = 1
# Check for each score
for i in range(1, n + 1):
curr = [0] * 4
# For each point.
for j in range(2, -1, -1):
curr[j] = dp[10 - points[j]][j] + curr[j + 1]
# Update dp states.
for k in range(9):
for j in range(4):
dp[k][j] = dp[k + 1][j]
for j in range(4):
dp[9][j] = curr[j]
return dp[9][0]
if __name__ == "__main__":
n = 20
print(countWays(n))
// C# program to find number of ways
// to reach a given score in a game using space optimised dp
using System;
class GfG {
static int countWays(int n) {
int[] points = {3, 5, 10};
// dp to store last 10 scores [-9, 0].
int[,] dp = new int[10, 4];
// Set ways=1 for score=0.
for (int j = 0; j < 4; j++) dp[9, j] = 1;
// Check for each score
for (int i = 1; i <= n; i++) {
int[] curr = new int[4];
// For each point.
for (int j = 2; j >= 0; j--) {
curr[j] = dp[10 - points[j], j] + curr[j + 1];
}
// Update dp states.
for (int k = 0; k < 9; k++) {
for (int j = 0; j < 4; j++) {
dp[k, j] = dp[k + 1, j];
}
}
for (int j = 0; j < 4; j++) {
dp[9, j] = curr[j];
}
}
return dp[9, 0];
}
static void Main(string[] args) {
int n = 20;
Console.WriteLine(countWays(n));
}
}
// JavaScript program to find number of ways
// to reach a given score in a game using space optimised dp
function countWays(n) {
let points = [3, 5, 10];
// dp to store last 10 scores [-9, 0].
let dp = Array.from({ length: 10 }, () => Array(4).fill(0));
// Set ways=1 for score=0.
for (let j = 0; j < 4; j++) dp[9][j] = 1;
// Check for each score
for (let i = 1; i <= n; i++) {
let curr = Array(4).fill(0);
// For each point.
for (let j = 2; j >= 0; j--) {
curr[j] = dp[10 - points[j]][j] + curr[j + 1];
}
// Update dp states.
for (let k = 0; k < 9; k++) {
for (let j = 0; j < 4; j++) {
dp[k][j] = dp[k + 1][j];
}
}
for (let j = 0; j < 4; j++) {
dp[9][j] = curr[j];
}
}
return dp[9][0];
}
let n = 20;
console.log(countWays(n));
Output
4