🧠
Bottom-Up DP with Binary Search Optimization
💡 Using binary search to find the next uncovered day reduces the inner loop from O(n) to O(log n), improving time complexity significantly.
Intuition
Instead of scanning linearly to find the next uncovered day, use binary search on the sorted days array.
Algorithm
- Precompute dp array with dp[n] = 0.
- For each day from last to first, use binary search to find next uncovered day for each ticket.
- Calculate minimum cost among all ticket options plus dp of next uncovered day.
- Store result in dp[i].
💡 Binary search reduces the time to find next uncovered day, making the solution scalable.
Recurrence:dp[i] = min_{j in {1,7,30}} (costs[j] + dp[nextIndex(i, j)]) with nextIndex found by binary search
import bisect
def mincostTickets(days, costs):
n = len(days)
durations = [1,7,30]
dp = [0] * (n + 1)
for i in range(n - 1, -1, -1):
ans = float('inf')
for j, d in enumerate(durations):
cost = costs[j]
next_day = days[i] + d
next_i = bisect.bisect_left(days, next_day, i, n)
ans = min(ans, cost + dp[next_i])
dp[i] = ans
return dp[0]
# Example usage
if __name__ == '__main__':
print(mincostTickets([1,4,6,7,8,20], [2,7,15])) # Output: 11
Line Notes
import bisectImport binary search module
next_i = bisect.bisect_left(days, next_day, i, n)Find next uncovered day index efficiently
dp = [0] * (n + 1)Initialize dp array with base case
for i in range(n - 1, -1, -1):Fill dp backward
dp[i] = ansStore minimum cost for current index
import java.util.*;
public class Solution {
public int mincostTickets(int[] days, int[] costs) {
int n = days.length;
int[] durations = {1,7,30};
int[] dp = new int[n + 1];
for (int i = n - 1; i >= 0; i--) {
int ans = Integer.MAX_VALUE;
for (int j = 0; j < durations.length; j++) {
int cost = costs[j];
int d = durations[j];
int next_i = binarySearch(days, days[i] + d, i, n);
ans = Math.min(ans, cost + dp[next_i]);
}
dp[i] = ans;
}
return dp[0];
}
private int binarySearch(int[] days, int target, int left, int right) {
int l = left, r = right;
while (l < r) {
int mid = l + (r - l) / 2;
if (days[mid] >= target) r = mid;
else l = mid + 1;
}
return l;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.mincostTickets(new int[]{1,4,6,7,8,20}, new int[]{2,7,15})); // 11
}
}
Line Notes
private int binarySearch(int[] days, int target, int left, int right)Binary search to find next uncovered day index
int l = left, r = right;Initialize binary search boundaries
if (days[mid] >= target) r = mid; else l = mid + 1;Adjust search space based on comparison
dp[i] = ans;Store minimum cost for current index
return dp[0];Return minimum cost starting from first travel day
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class Solution {
public:
int binarySearch(const vector<int>& days, int target, int left, int right) {
int l = left, r = right;
while (l < r) {
int mid = l + (r - l) / 2;
if (days[mid] >= target) r = mid;
else l = mid + 1;
}
return l;
}
int mincostTickets(vector<int>& days, vector<int>& costs) {
int n = days.size();
vector<int> durations = {1,7,30};
vector<int> dp(n + 1, 0);
for (int i = n - 1; i >= 0; i--) {
int ans = INT_MAX;
for (int j = 0; j < 3; j++) {
int cost = costs[j];
int d = durations[j];
int next_i = binarySearch(days, days[i] + d, i, n);
ans = min(ans, cost + dp[next_i]);
}
dp[i] = ans;
}
return dp[0];
}
};
int main() {
Solution sol;
vector<int> days = {1,4,6,7,8,20};
vector<int> costs = {2,7,15};
cout << sol.mincostTickets(days, costs) << endl; // 11
return 0;
}
Line Notes
int binarySearch(const vector<int>& days, int target, int left, int right)Binary search to find next uncovered day index
if (days[mid] >= target) r = mid; else l = mid + 1;Adjust search space based on comparison
dp[i] = ans;Store minimum cost for current index
return dp[0];Return minimum cost starting from first travel day
vector<int> dp(n + 1, 0);Initialize dp array with base case
var mincostTickets = function(days, costs) {
const durations = [1,7,30];
const n = days.length;
const dp = new Array(n + 1).fill(0);
function binarySearch(target, left, right) {
let l = left, r = right;
while (l < r) {
let mid = Math.floor((l + r) / 2);
if (days[mid] >= target) r = mid;
else l = mid + 1;
}
return l;
}
for (let i = n - 1; i >= 0; i--) {
let ans = Infinity;
for (let j = 0; j < durations.length; j++) {
let cost = costs[j];
let d = durations[j];
let next_i = binarySearch(days[i] + d, i, n);
ans = Math.min(ans, cost + dp[next_i]);
}
dp[i] = ans;
}
return dp[0];
};
// Example usage
console.log(mincostTickets([1,4,6,7,8,20], [2,7,15])); // 11
Line Notes
function binarySearch(target, left, right)Binary search to find next uncovered day index
if (days[mid] >= target) r = mid; else l = mid + 1;Adjust search space based on comparison
dp[i] = ans;Store minimum cost for current index
return dp[0];Return minimum cost starting from first travel day
const dp = new Array(n + 1).fill(0);Initialize dp array with base case
TimeO(n * 3 * log n) = O(n log n)
SpaceO(n) for dp array
Binary search reduces the inner loop from O(n) to O(log n), making the solution efficient for large inputs.
💡 For n=10^5, this approach is practical and efficient.
Interview Verdict: Accepted and optimal for large inputs
This is the recommended approach in interviews for this problem.