2585. Number of Ways to Earn Points
Problem Description
You are given a test that contains n
different types of questions. Each question type is described by two values:
count[i]
: the number of questions available of typei
marks[i]
: the points earned for solving each question of typei
Your goal is to find the number of ways to earn exactly target
points by solving questions from the test.
The input is provided as:
- An integer
target
representing the exact score you need to achieve - A 2D array
types
wheretypes[i] = [count[i], marks[i]]
Important notes:
- Questions of the same type are indistinguishable. For example, if there are 3 questions of type 1, solving questions #1 and #2 is considered the same as solving questions #1 and #3.
- You can choose to solve any number of questions from each type (from 0 up to the available count)
- The answer should be returned modulo
10^9 + 7
since it can be very large
For example, if you have types = [[2, 5], [3, 2]]
and target = 10
:
- Type 1 has 2 questions worth 5 points each
- Type 2 has 3 questions worth 2 points each
- You could achieve exactly 10 points by:
- Solving 2 questions of type 1: 2 × 5 = 10 points
- Solving 0 questions of type 1 and 5 questions of type 2: 0 × 5 + 5 × 2 = 10 points (but this is invalid since type 2 only has 3 questions)
- Other valid combinations...
The problem asks you to count all valid ways to select questions that sum to exactly the target score.
Intuition
This problem asks us to count the number of ways to achieve an exact score, which immediately suggests a counting problem that can be solved with dynamic programming. The key insight is that we need to make decisions about how many questions to solve from each type, and these decisions build upon each other.
Think of it as a knapsack-like problem where instead of maximizing value, we're counting the number of ways to reach an exact "weight" (the target score). Each question type represents a group of identical items, and we can take anywhere from 0 to count[i]
items from each group.
The natural approach is to process one question type at a time. For each type, we decide how many questions to solve from it (0, 1, 2, ... up to the available count). Each choice affects our remaining target score and builds upon the ways we could achieve scores using previous question types.
This leads us to define our state as f[i][j]
= number of ways to get exactly j
points using only the first i
types of questions.
Why does this work? Because:
- We can break down the problem into smaller subproblems (using fewer question types)
- The solution to a larger problem depends on solutions to smaller problems
- We can systematically build up from "0 types of questions" to "all n types"
For each new question type we consider, we examine all possible numbers of questions we could solve from that type. If we solve k
questions of type i
(each worth marks[i]
points), we get k × marks[i]
points from this type. The remaining points j - k × marks[i]
must come from the previous types, which we've already computed.
The transition naturally becomes: for each possible score j
and each valid number of questions k
from the current type, add the number of ways to achieve score j - k × marks[i]
using previous types to our current count for score j
.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the dynamic programming solution using a 2D array f
where f[i][j]
represents the number of ways to achieve exactly j
points using the first i
types of questions.
Initialization:
- Create a 2D array
f
of size(n+1) × (target+1)
initialized with zeros - Set
f[0][0] = 1
as there's exactly one way to get 0 points with 0 question types (solve nothing) - All other values start at 0
State Transition:
For each question type i
from 1 to n
:
- Extract
count
andmarks
fromtypes[i-1]
(using 0-indexed array) - For each possible score
j
from 0 totarget
:- For each possible number of questions
k
from 0 tocount
:- Check if we can afford
k
questions:j >= k * marks
- If yes, add the ways to achieve score
j - k * marks
using the previousi-1
types:f[i][j] = (f[i][j] + f[i-1][j - k * marks]) % mod
- Check if we can afford
- For each possible number of questions
The formula captures the idea that to get j
points using i
question types, we can:
- Solve 0 questions of type
i
and getj
points from types 1 toi-1
- Solve 1 question of type
i
(gettingmarks
points) and getj - marks
points from types 1 toi-1
- Solve 2 questions of type
i
(getting2 * marks
points) and getj - 2 * marks
points from types 1 toi-1
- ... and so on up to
count
questions
Implementation Details:
- We use modulo
10^9 + 7
at each addition to prevent integer overflow - The outer loop iterates through question types (1 to
n
) - The middle loop iterates through all possible scores (0 to
target
) - The inner loop iterates through all valid numbers of questions for the current type (0 to
count
)
Time Complexity: O(n × target × max_count)
where max_count
is the maximum number of questions of any single type
Space Complexity: O(n × target)
for the DP table
The final answer is f[n][target]
, representing the number of ways to achieve exactly target
points using all n
question types.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with types = [[2, 3], [1, 4]]
and target = 6
.
This means:
- Type 1: 2 questions worth 3 points each
- Type 2: 1 question worth 4 points
We'll build our DP table f[i][j]
where i
represents using the first i
question types and j
represents the score.
Step 1: Initialize
Create a table f
of size 3×7 (since we have 2 question types and target=6):
f[0][0] = 1 (one way to get 0 points with 0 types) All other entries = 0
Initial state:
j: 0 1 2 3 4 5 6 i=0: 1 0 0 0 0 0 0 i=1: 0 0 0 0 0 0 0 i=2: 0 0 0 0 0 0 0
Step 2: Process Type 1 (count=2, marks=3) For each score j from 0 to 6, consider taking k questions (k can be 0, 1, or 2):
- j=0: Only k=0 works (0×3=0). f[1][0] = f[0][0] = 1
- j=1: No valid k (can't make 1 with multiples of 3)
- j=2: No valid k
- j=3: k=1 works (1×3=3). f[1][3] = f[0][3-3] = f[0][0] = 1
- j=4: No valid k
- j=5: No valid k
- j=6: k=2 works (2×3=6). f[1][6] = f[0][6-6] = f[0][0] = 1
After processing Type 1:
j: 0 1 2 3 4 5 6 i=0: 1 0 0 0 0 0 0 i=1: 1 0 0 1 0 0 1 i=2: 0 0 0 0 0 0 0
Step 3: Process Type 2 (count=1, marks=4) For each score j from 0 to 6, consider taking k questions (k can be 0 or 1):
- j=0: k=0 works. f[2][0] = f[1][0] = 1
- j=1: No valid k
- j=2: No valid k
- j=3: k=0 works. f[2][3] = f[1][3] = 1
- j=4: k=1 works (1×4=4). f[2][4] = f[1][4-4] = f[1][0] = 1
- j=5: No valid k
- j=6: k=0 works. f[2][6] = f[1][6] = 1
But wait! For j=6, we should also check k=1:
- k=1 would need j-4 = 2 points from Type 1, but f[1][2] = 0
So f[2][6] = f[1][6] = 1
Final table:
j: 0 1 2 3 4 5 6 i=0: 1 0 0 0 0 0 0 i=1: 1 0 0 1 0 0 1 i=2: 1 0 0 1 1 0 1
Answer: f[2][6] = 1
There's exactly 1 way to achieve 6 points: solve 2 questions of Type 1 (2×3=6 points) and 0 questions of Type 2.
Let's verify: Could we do 0 questions of Type 1 and some Type 2? We'd need 6 points from Type 2, but 1×4=4, which isn't enough. Could we do 1 question of Type 1 and some Type 2? We'd have 3 points from Type 1 and need 3 more, but 1×4=4 is too much. So indeed, the only way is 2 questions of Type 1.
Solution Implementation
1class Solution:
2 def waysToReachTarget(self, target: int, types: List[List[int]]) -> int:
3 """
4 Calculate the number of ways to reach exactly the target score.
5
6 Args:
7 target: The target score to achieve
8 types: List of [count, marks] where count is max questions of this type
9 and marks is the score per question
10
11 Returns:
12 Number of ways to reach the target score modulo 10^9 + 7
13 """
14 num_types = len(types)
15 MOD = 10**9 + 7
16
17 # dp[i][j] represents number of ways to achieve score j using first i types
18 dp = [[0] * (target + 1) for _ in range(num_types + 1)]
19
20 # Base case: one way to achieve score 0 with 0 types
21 dp[0][0] = 1
22
23 # Process each question type
24 for type_idx in range(1, num_types + 1):
25 max_questions, points_per_question = types[type_idx - 1]
26
27 # For each possible score
28 for score in range(target + 1):
29 # Try using 0 to max_questions of current type
30 for questions_used in range(max_questions + 1):
31 points_from_current = questions_used * points_per_question
32
33 # Check if we can achieve this score
34 if score >= points_from_current:
35 remaining_score = score - points_from_current
36 dp[type_idx][score] = (dp[type_idx][score] +
37 dp[type_idx - 1][remaining_score]) % MOD
38
39 return dp[num_types][target]
40
1class Solution {
2 public int waysToReachTarget(int target, int[][] types) {
3 // Number of question types
4 int numTypes = types.length;
5 // Modulo value for large numbers
6 final int MOD = (int) 1e9 + 7;
7
8 // dp[i][j] represents the number of ways to achieve exactly j marks
9 // using the first i types of questions
10 int[][] dp = new int[numTypes + 1][target + 1];
11
12 // Base case: 0 marks with 0 question types has exactly 1 way (choosing nothing)
13 dp[0][0] = 1;
14
15 // Iterate through each question type
16 for (int i = 1; i <= numTypes; i++) {
17 // Extract count and marks for current question type
18 int questionCount = types[i - 1][0]; // Maximum number of questions of this type
19 int marksPerQuestion = types[i - 1][1]; // Marks for each question of this type
20
21 // Iterate through all possible target scores
22 for (int currentTarget = 0; currentTarget <= target; currentTarget++) {
23 // Try using k questions of the current type (from 0 to questionCount)
24 for (int numQuestions = 0; numQuestions <= questionCount; numQuestions++) {
25 int totalMarks = numQuestions * marksPerQuestion;
26
27 // Check if we can achieve currentTarget by using numQuestions of current type
28 if (currentTarget >= totalMarks) {
29 // Add the number of ways from previous state
30 dp[i][currentTarget] = (dp[i][currentTarget] +
31 dp[i - 1][currentTarget - totalMarks]) % MOD;
32 }
33 }
34 }
35 }
36
37 // Return the number of ways to achieve the target using all question types
38 return dp[numTypes][target];
39 }
40}
41
1class Solution {
2public:
3 int waysToReachTarget(int target, vector<vector<int>>& types) {
4 int numTypes = types.size();
5 const int MOD = 1e9 + 7;
6
7 // dp[i][j] represents the number of ways to achieve exactly j marks
8 // using the first i types of questions
9 int dp[numTypes + 1][target + 1];
10 memset(dp, 0, sizeof(dp));
11
12 // Base case: one way to achieve 0 marks with 0 types of questions
13 dp[0][0] = 1;
14
15 // Iterate through each type of question
16 for (int i = 1; i <= numTypes; ++i) {
17 int maxCount = types[i - 1][0]; // Maximum number of questions of this type
18 int marksPerQuestion = types[i - 1][1]; // Marks for each question of this type
19
20 // Calculate ways to achieve each possible score
21 for (int currentScore = 0; currentScore <= target; ++currentScore) {
22 // Try using k questions of the current type
23 for (int questionsUsed = 0; questionsUsed <= maxCount; ++questionsUsed) {
24 int marksFromThisType = questionsUsed * marksPerQuestion;
25
26 // Check if we can achieve currentScore by using questionsUsed of this type
27 if (currentScore >= marksFromThisType) {
28 // Add the ways from previous state
29 dp[i][currentScore] = (dp[i][currentScore] +
30 dp[i - 1][currentScore - marksFromThisType]) % MOD;
31 }
32 }
33 }
34 }
35
36 // Return the number of ways to achieve exactly the target score
37 return dp[numTypes][target];
38 }
39};
40
1/**
2 * Calculates the number of ways to reach a target score using different types of questions
3 * @param target - The target score to achieve
4 * @param types - Array of question types, where each type is [count, marks]
5 * count: number of questions of this type
6 * marks: marks/points for each question of this type
7 * @returns Number of ways to reach the target score modulo 10^9 + 7
8 */
9function waysToReachTarget(target: number, types: number[][]): number {
10 const numberOfTypes: number = types.length;
11 const MOD: number = 10 ** 9 + 7;
12
13 // dp[i][j] represents the number of ways to achieve score j using first i types of questions
14 const dp: number[][] = Array.from(
15 { length: numberOfTypes + 1 },
16 () => Array(target + 1).fill(0)
17 );
18
19 // Base case: one way to achieve score 0 with 0 types (select nothing)
20 dp[0][0] = 1;
21
22 // Iterate through each question type
23 for (let typeIndex = 1; typeIndex <= numberOfTypes; ++typeIndex) {
24 // Extract count and marks for current question type
25 const [questionCount, marksPerQuestion] = types[typeIndex - 1];
26
27 // Calculate ways to achieve each possible score
28 for (let score = 0; score <= target; ++score) {
29 // Try using 0 to questionCount questions of current type
30 for (let questionsUsed = 0; questionsUsed <= questionCount; ++questionsUsed) {
31 const marksFromCurrentType: number = questionsUsed * marksPerQuestion;
32
33 // Check if current score can accommodate the marks from selected questions
34 if (score >= marksFromCurrentType) {
35 // Add ways from previous state to current state
36 dp[typeIndex][score] = (
37 dp[typeIndex][score] +
38 dp[typeIndex - 1][score - marksFromCurrentType]
39 ) % MOD;
40 }
41 }
42 }
43 }
44
45 // Return the number of ways to achieve target score using all types
46 return dp[numberOfTypes][target];
47}
48
Time and Space Complexity
The time complexity is O(n × target × count)
where:
n
is the number of question types (length of thetypes
array)target
is the target score we want to achievecount
represents the maximum number of questions we can solve for each type
This is because we have three nested loops:
- The outer loop iterates through all
n
question types - The middle loop iterates through all possible scores from
0
totarget
- The inner loop iterates through the number of questions we can take from the current type (from
0
tocount
)
The space complexity is O(n × target)
because we create a 2D DP table f
with dimensions (n + 1) × (target + 1)
to store the number of ways to achieve each score using the first i
types of questions.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow Without Proper Modulo Application
The Pitfall: A common mistake is forgetting to apply the modulo operation during intermediate calculations, or applying it only at the very end. This can cause integer overflow even in languages that handle large integers well, and definitely causes issues in languages with fixed integer sizes.
Incorrect approach:
# Wrong - accumulating without modulo
for questions_used in range(max_questions + 1):
if score >= questions_used * points_per_question:
dp[type_idx][score] += dp[type_idx - 1][score - questions_used * points_per_question]
# Applying modulo only at the end
dp[type_idx][score] %= MOD
Why it's problematic:
- The intermediate sum can grow extremely large before the modulo is applied
- In some test cases with large counts and many types, the number of ways can exceed standard integer limits
The Solution: Apply modulo operation immediately after each addition:
dp[type_idx][score] = (dp[type_idx][score] + dp[type_idx - 1][remaining_score]) % MOD
2. Breaking Early in the Inner Loop
The Pitfall:
Some might try to optimize by breaking the inner loop when questions_used * points_per_question > score
:
# Wrong optimization
for questions_used in range(max_questions + 1):
points_from_current = questions_used * points_per_question
if points_from_current > score:
break # This seems logical but is unnecessary
dp[type_idx][score] = (dp[type_idx][score] +
dp[type_idx - 1][score - points_from_current]) % MOD
Why it can be problematic:
- While not incorrect, it adds complexity without significant performance gain
- The condition check
if score >= points_from_current
already handles invalid cases - Breaking early might make the code harder to understand and maintain
The Solution: Keep the simple condition check without early termination. The clean approach in the original solution is preferred for clarity and correctness.
3. Space Optimization Pitfall
The Pitfall: Attempting to optimize space by using a 1D array instead of 2D, but updating it incorrectly:
# Wrong space optimization
dp = [0] * (target + 1)
dp[0] = 1
for type_idx in range(len(types)):
max_questions, points_per_question = types[type_idx]
for score in range(target + 1): # Wrong direction!
for questions_used in range(1, max_questions + 1):
if score >= questions_used * points_per_question:
dp[score] = (dp[score] + dp[score - questions_used * points_per_question]) % MOD
Why it's problematic:
- When iterating forward through scores, we might use already-updated values from the current iteration
- This leads to counting the same question type multiple times
The Solution: If space optimization is needed, iterate through scores in reverse order:
dp = [0] * (target + 1)
dp[0] = 1
for type_idx in range(len(types)):
max_questions, points_per_question = types[type_idx]
# Iterate in reverse to avoid using updated values
for score in range(target, -1, -1):
for questions_used in range(1, max_questions + 1):
if score >= questions_used * points_per_question:
dp[score] = (dp[score] + dp[score - questions_used * points_per_question]) % MOD
This ensures we only use values from the previous iteration, not the current one.
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!