LeetCode 983 - Minimum Cost For Tickets


https://leetcode.com/problems/minimum-cost-for-tickets/
In a country popular for train travel, you have planned some train travelling one year in advance.  The days of the year that you will travel is given as an array days.  Each day is an integer from 1 to 365.
Train tickets are sold in 3 different ways:
  • a 1-day pass is sold for costs[0] dollars;
  • a 7-day pass is sold for costs[1] dollars;
  • a 30-day pass is sold for costs[2] dollars.
The passes allow that many days of consecutive travel.  For example, if we get a 7-day pass on day 2, then we can travel for 7 days: day 2, 3, 4, 5, 6, 7, and 8.
Return the minimum number of dollars you need to travel every day in the given list of days.

Example 1:
Input: days = [1,4,6,7,8,20], costs = [2,7,15]
Output: 11
Explanation: 
For example, here is one way to buy passes that lets you travel your travel plan:
On day 1, you bought a 1-day pass for costs[0] = $2, which covered day 1.
On day 3, you bought a 7-day pass for costs[1] = $7, which covered days 3, 4, ..., 9.
On day 20, you bought a 1-day pass for costs[0] = $2, which covered day 20.
In total you spent $11 and covered all the days of your travel.
Example 2:
Input: days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
Output: 17
Explanation: 
For example, here is one way to buy passes that lets you travel your travel plan:
On day 1, you bought a 30-day pass for costs[2] = $15 which covered days 1, 2, ..., 30.
On day 31, you bought a 1-day pass for costs[0] = $2 which covered day 31.
In total you spent $17 and covered all the days of your travel.

Note:
  1. 1 <= days.length <= 365
  2. 1 <= days[i] <= 365
  3. days is in strictly increasing order.
  4. costs.length == 3
  5. 1 <= costs[i] <= 1000
X. DP + Sliding window(Queue)
https://leetcode.com/problems/minimum-cost-for-tickets/discuss/226659/Two-DP-solutions-with-pictures


The higher is the bar, the more you're expected to use dynamic programming (DP) during an interview. This technique requires a lot of practice to grasp; if you've mastered the recursion, DP is the next level.

2. Track travel days

We track the minimum cost for each travel day. We process only travel days and store {day, cost} for 7-and 30-day passes in the last7 and last30 queues. After a pass 'expires', we remove it from the queue. This way, our queues only contains travel days for the last 7 and 30 days, and the cheapest pass prices are in the front of the queues.
image
int mincostTickets(vector<int>& days, vector<int>& costs, int cost = 0) {
  queue<pair<int, int>> last7, last30;
  for (auto d : days) {
    while (!last7.empty() && last7.front().first + 7 <= d) last7.pop();
    while (!last30.empty() && last30.front().first + 30 <= d) last30.pop();
    last7.push({ d, cost + costs[1] });
    last30.push({ d, cost + costs[2] });
    cost = min({ cost + costs[0], last7.front().second, last30.front().second });
  }
  return cost;
}

Complexity analysis

  • Time Complexity: O(n), where n is the number of travel days.
  • Space Complexity: O(38). Stricter, it's a sum of duration for all pass types (1 + 7 + 30 in our case)
https://leetcode.com/articles/minimum-cost-for-tickets/
Approach 2: Dynamic Programming (Window Variant)
As in Approach 1, we only need to buy a travel pass on a day we intend to travel.
Now, let dp(i) be the cost to travel from day days[i] to the end of the plan. If say, j1 is the largest index such that days[j1] < days[i] + 1j7 is the largest index such that days[j7] < days[i] + 7, and j30 is the largest index such that days[j30] < days[i] + 30, then we have:
\text{dp}(i) = \min(\text{dp}(j1) + \text{costs}[0], \text{dp}(j7) + \text{costs}[1], \text{dp}(j30) + \text{costs}[2])
  • Time Complexity: O(N), where N is the number of unique days in your travel plan.
  • Space Complexity: O(N)
  int[] days, costs;
  Integer[] memo;
  int[] durations = new int[] { 1, 7, 30 };

  public int mincostTickets(int[] days, int[] costs) {
    this.days = days;
    this.costs = costs;
    memo = new Integer[days.length];

    return dp(0);
  }

  public int dp(int i) {
    if (i >= days.length)
      return 0;
    if (memo[i] != null)
      return memo[i];

    int ans = Integer.MAX_VALUE;
    int j = i;
    for (int k = 0; k < 3; ++k) {
      while (j < days.length && days[j] < days[i] + durations[k])
        j++;
      ans = Math.min(ans, dp(j) + costs[k]);
    }

    memo[i] = ans;
    return ans;

  }
https://zhanghuimeng.github.io/post/leetcode-983-minimum-cost-for-tickets/
    int mincostTickets(vector<int>& days, vector<int>& costs) {
        int f[365];
        int N = days.size();
        for (int i = 0; i < N; i++) {
            if (i == 0) {
                f[0] = min(costs[0], costs[1]);
                f[0] = min(costs[2], f[0]);
                continue;
            }
            f[i] = f[i-1] + costs[0];
            // 感觉这个转移条件也是有点奇怪……
            for (int j = 1; j <= 30 && j <= i; j++) {
                int x = j < i ? f[i-j-1] : 0;
                if (days[i] - days[i-j] + 1 <= 7)
                    f[i] = min(f[i], x + costs[1]);
                if (days[i] - days[i-j] + 1 <= 30)
                    f[i] = min(f[i], x + costs[2]);
            }
        }
        return f[N - 1];
    }

https://www.acwing.com/solution/LeetCode/content/877/
状态 f(i)f(i) 表示满足了前 ii 个日期的最少花费。日期下标从 1 开始。
初始值 f(0)=0f(0)=0,其余为正无穷。可以想到,每次买票一定选在在某个存在的日期。
状态转移,f(i)=min(f(j)+cost(k))f(i)=min(f(j)+cost(k)),其中 jj 表示上一个买票的日期,kk 表示对应的价钱。倒序枚举 jj,如果两个日期差距大于等于 30 天,则无需继续枚举。
最终答案为 f(n)f(n)。
时间复杂度
状态数为 O(n)O(n) 个,注意到每次转移最多只要 30 个,故总时间复杂度为 O(n)O(n)
    int mincostTickets(vector<int>& days, vector<int>& costs) {
        int n = days.size();
        vector<int> f(n + 1, INT_MAX);
        f[0] = 0;
        days.insert(days.begin(), 0);
        for (int i = 1; i <= n; i++) {
            for (int j = i; j >= 1; j--) {
                if (days[i] - days[j] < 1)
                    f[i] = min(f[i], f[i - 1] + costs[0]);
                if (days[i] - days[j] < 7)
                    f[i] = min(f[i], f[j - 1] + costs[1]);
                if (days[i] - days[j] < 30)
                    f[i] = min(f[i], f[j - 1] + costs[2]);
                if (days[i] - days[j] >= 30)
                    break;
            }
        }
        return f[n];
    }

X. DP + two pointers/sliding window
https://blog.csdn.net/mike_learns_to_rock/article/details/86669196
  public int mincostTickets(int[] daysint[] costs) {
    // dp[i + 1] .. days[0, .., i]的最优解
    // dp[i] = min(dp[week] + costs[1], dp[month] + costs[2], dp[i - 1] + costs[0]);
    int week = 0;
    int month = 0;
    int[] dp = new int[days.length + 1];
    dp[0] = 0;
    for (int i = 0; i < days.lengthi++) {
      while (days[week] < days[i] - 6) {
        week++;
      }
      while (days[month] < days[i] - 29) {
        month++;
      }
      int tmp = Math.min(dp[i] + costs[0], dp[week] + costs[1]);
      dp[i + 1] = Math.min(tmpdp[month] + costs[2]);
    }
    return dp[days.length];

  }


X. 
https://leetcode.com/problems/minimum-cost-for-tickets/discuss/226659/Two-DP-solutions-with-pictures

For each travel day, we can buy a one-day ticket, or use 7-day or 30-day pass as if we would have purchased it 7 or 30 days ago. We need to track rolling costs for at least 30 days back, and use them to pick the cheapest option for the next travel day.
Here, we can use two approaches: track cost for all calendar days, or process only travel days. The first approach is simpler to implement, but it's slower. Since the problem is limited to one calendar year, it does not make much of a difference; for a generalized problem I would recommend the second approach.

1. Track calendar days

We track the minimum cost for all calendar days in dp. For non-travel days, the cost stays the same as for the previous day. For travel days, it's a minimum of yesterday's cost plus single-day ticket, or cost for 8 days ago plus 7-day pass, or cost 31 days ago plus 30-day pass.
image
int mincostTickets(vector<int>& days, vector<int>& costs) {
  unordered_set<int> travel(begin(days), end(days));
  int dp[366] = {};
  for (int i = 1; i < 366; ++i) {
    if (travel.find(i) == travel.end()) dp[i] = dp[i - 1];
    else dp[i] = min({ dp[i - 1] + costs[0], dp[max(0, i - 7)] + costs[1], dp[max(0, i - 30)] + costs[2]});
  }
  return dp[365];
}

Optimizations

In the previous solution, we store cost for all calendar days. However, since we only look 31 days back, we can just store the cost for last 31 days in a rolling array.
In addition, we can only look at calendar days within our first and last travel dates, as @zengxinhai suggested.
int mincostTickets(vector<int>& days, vector<int>& costs) {
  unordered_set<int> travel(begin(days), end(days));
  int dp[31] = {};
  for (int i = days.front(); i <= days.back(); ++i) {
    if (travel.find(i) == travel.end()) dp[i % 31] = dp[(i - 1) % 31];
    else dp[i % 31] = min({ dp[(i - 1) % 31] + costs[0],
        dp[max(0, i - 7) % 31] + costs[1], dp[max(0, i - 30) % 31] + costs[2] });
  }
  return dp[days.back() % 31];
}

Complexity analysis

  • Time Complexity: O(N), where N is the number of calendar days.
  • Space Complexity: O(N) or O(31) for the optimized solution. Stricter, it's a maximum duration among all pass types.

Approach 1: Dynamic Programming (Day Variant)
We can express those choices as a recursion and use dynamic programming. Let's say dp(i) is the cost to fulfill your travel plan from day i to the end of the plan. Then, if you have to travel today, your cost is:



\text{dp}(i) = \min(\text{dp}(i+1) + \text{costs}[0], \text{dp}(i+7) + \text{costs}[1], \text{dp}(i+30) + \text{costs}[2])
  • Time Complexity: O(W), where W = 365 is the maximum numbered day in your travel plan.
  • Space Complexity: O(W)
  int[] costs;
  Integer[] memo;
  Set<Integer> dayset;

  public int mincostTickets(int[] days, int[] costs) {
    this.costs = costs;
    memo = new Integer[366];
    dayset = new HashSet();
    for (int d : days)
      dayset.add(d);

    return dp(1);
  }

  public int dp(int i) {
    if (i > 365)
      return 0;
    if (memo[i] != null)
      return memo[i];

    int ans;
    if (dayset.contains(i)) {
      ans = Math.min(dp(i + 1) + costs[0], dp(i + 7) + costs[1]);
      ans = Math.min(ans, dp(i + 30) + costs[2]);
    } else {
      ans = dp(i + 1);
    }

    memo[i] = ans;
    return ans;

  }

https://leetcode.com/problems/minimum-cost-for-tickets/discuss/226670/Java-DP-Solution-with-explanation-O(n)
public int mincostTickets(int[] days, int[] costs) {
        boolean[] dayIncluded = new boolean[366];
        for (int day : days) {
            dayIncluded[day] = true;
        }
        int[] minCost = new int[366];
        minCost[0] = 0;
        for (int day = 1; day <= 365; ++day) {
            if (!dayIncluded[day]) {
                minCost[day] = minCost[day-1];
                continue;
            }
            int min;
            min = minCost[day-1] + costs[0];
            min =Math.min(min, minCost[Math.max(0, day-7)] + costs[1]);
            min = Math.min(min, minCost[Math.max(0, day-30)] + costs[2]);
            minCost[day] = min;
        }

        return minCost[365];

    }

    int mincostTickets(vector<int>& days, vector<int>& costs) {
        int f[366];
        f[0] = 0;
        int i = 0;
        for (int j = 1; j <= 365; j++) {
            while (days[i] < j && i < days.size()) i++;
            if (i < days.size() && j == days[i]) {
                f[j] = 1e9;
                // 1 day
                f[j] = min(f[j], f[j-1] + costs[0]);
                // 7 days
                // buy on day f[j-k+1]
                for (int k = 1; k <= 7 && j - k >= 0; k++)
                    f[j] = min(f[j], f[j-k] + costs[1]);
                // 30 days
                for (int k = 1; k <= 30 && j - k >= 0; k++)
                    f[j] = min(f[j], f[j-k] + costs[2]);
            }
            else
                f[j] = f[j-1];
        }
        return f[365];
    }
X.
https://leetcode.com/problems/minimum-cost-for-tickets/discuss/226704/Java-O(nlogn)-DP-solution-and-recursion-%2B-memorization-solution
    public int mincostTickets(int[] days, int[] costs) {
        if (days == null || days.length == 0) return 0;
        int n = days.length;
        int[] dp = new int[n + 1];
        for (int i = 0; i < n; i++) {
            int res = dp[i] + costs[0]; //previous cost + today's cost...
            
            //find the left most index that is larger than days[i] - 7..
            //calculate the cost by using prev cost + one-week cost...
            int index7 = binarySearch(days, i, days[i] - 7);
            res = Math.min(res, dp[index7] + costs[1]);
            
            //find the left most index that is larger than days[i] - 30..
            //calculate the cost by using prev cost + one-month cost...
            int index30 = binarySearch(days, i, days[i] - 30);
            res = Math.min(res, dp[index30] + costs[2]);
            dp[i + 1] = res;
        }
        return dp[n];
    }
    
    private int binarySearch(int[] days, int i, int target) {
        int lo = 0, hi = i;
        while (lo  < hi) {
            int mid = lo + (hi - lo) / 2;
            if (days[mid] > target) {
                hi = mid;
            } else {
                lo = mid + 1;
            }
        }
        return lo;
    }




Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts