1. 程式人生 > 實用技巧 >[LeetCode] 983. Minimum Cost For Tickets 最低票價

[LeetCode] 983. Minimum Cost For Tickets 最低票價


In a country popular for train travel, youhave planned some train travelling one year in advance. The days of the year that you will travel is given as an arraydays. Each day is an integer from1to365.

Train tickets are sold in 3 different ways:

  • a 1-day pass is sold forcosts[0]dollars;
  • a 7-day pass is sold forcosts[1]
    dollars;
  • a 30-day pass is sold forcosts[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 ofdays

.

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. daysis in strictly increasing order.
  4. costs.length == 3
  5. 1 <= costs[i] <= 1000

這道題給了兩個陣列 days 和 costs,說是有人會在指定的天數進行旅遊,由於某些城市的旅遊景點比較多,短時間內可能玩不完,所有有些城市會推出 city pass,就是在特定的天數內,可以隨意玩。這裡博主不得不提亞特蘭大的 city pass,總體感覺還是蠻划算的,可以玩的很開心。現在說是有三種 city pass,一天,一週,和一個月的通玩票,價格不同,現在問應該如何去買,才能保證在給定的天數都玩到,而且花費最小。當然實際情況中,肯定是月票價格大於周票大於日票,但是這道題裡並沒有這個限制,cost 值之間並不存在大小關係。在實際情況中,如果需要連著幾天玩,肯定是用長期票划算,但這裡不一定哦,所以一定要算出各種情況。像這種每天遊玩的票可以有三種不同的選擇,即三種不同的狀態,又是一道求極值的問題,可以說基本上動態規劃 Dynamic Programming 就是不二之選。這裡可以使用一個一維的 dp 陣列,其中 dp[i] 表示遊玩到第 days[i] 天時所需要的最小花費。接下來就是最難的部分了,找出狀態轉移方程。對於第 days[i] 天的花費,可能有三種不同的情況,首先是第 days[i-1] 使用了一張日票,則當前天就有多種選擇,可以買日票,周票,或者月票。若之前使用買過了周票,則當前並不用再花錢了,所以只要一週內買過周票,當前就不用花錢了,但是當前的 dp 值還是需要被更新的,用買周票的前一天的 dp 值加上週票的價格來更新當前的 dp 值,所以顯而易見是需要兩個 for 迴圈的,外層的是遍歷遊玩天數,內層是不停的通過用買周票或者月票的方式,來查詢一種最省錢的方法。具體來看程式碼,這裡的 dp 陣列大小為 n+1,為了防止減1溢位,並且都初始化為整型最大值,但是 dp[0] 要初始化為0。然後就是外層 for 迴圈了,i從1遍歷到n,由於每一天都可以買日票,所以都可以用前一天的 dp 值加上日票價格來更新當前的 dp 值。然後就是內層迴圈了,j從1遍歷到i,只要遍歷到的某天在當前天的7天之內,就可以用嘗試著替換成周票來更新當前的 dp 值,同理,若只要遍歷到的某天在當前天的 30 天之內,就可以用嘗試著替換成月票來更新當前的 dp 值,這樣更新下來,最優解就會存到 dp 陣列種的最後一個位置上了,參見程式碼如下:


解法一:

class Solution {
public:
    int mincostTickets(vector<int>& days, vector<int>& costs) {
        int n = days.size();
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 1; i <= n; ++i) {
            dp[i] = min(dp[i], dp[i - 1] + costs[0]);
            for (int j = 1; j <= i; ++j) {
                if (days[j - 1] + 7 > days[i - 1]) {
                    dp[i] = min(dp[i], dp[j - 1] + costs[1]);
                }
                if (days[j - 1] + 30 > days[i - 1]) {
                    dp[i] = min(dp[i], dp[j - 1] + costs[2]);
                }
            }
        }
        return dp.back();
    }
};

下面來看一種更簡潔的寫法,由於規定了遊玩的天數是在一年內,實際上可以將 dp 陣列的大小確定為 366,然後只要更新好這個 dp 陣列就行了。同時,由於並不是每天都要玩,所以需要知道到底是哪些天需要玩,比較簡單的方法就是把遊玩的天數放到一個 TreeSet 中,以便於快速的查詢。用 for 迴圈遍歷1到 365 天,用前一天的 dp 值來更新當前天,因為就算今天沒有玩,之前花了的錢也都已經花了,還是要記在那,以便年底算總賬。若當前天遊玩了,即在 TreeSet 裡面,則考慮是否可以優化當前的花費,通過三種途徑,今天買日票,一週前買周票,或者一個月錢買月票,看哪種花費最低,用來更新當前的 dp 值,參見程式碼如下:


解法二:

class Solution {
public:
    int mincostTickets(vector<int>& days, vector<int>& costs) {
        unordered_set<int> st(days.begin(), days.end());
        vector<int> dp(366);
        for (int i = 1; i <= 365; ++i) {
            dp[i] = dp[i - 1];
            if (st.count(i)) {
                dp[i] = min({dp[i - 1] + costs[0], dp[max(0, i - 7)] + costs[1], dp[max(0, i - 30)] + costs[2]});
            }
        }
        return dp.back();
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/983


類似題目:

Coin Change


參考資料:

https://leetcode.com/problems/minimum-cost-for-tickets/

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

https://leetcode.com/problems/minimum-cost-for-tickets/discuss/630868/explanation-from-someone-who-took-2-hours-to-solve


LeetCode All in One 題目講解彙總(持續更新中...)