[LeetCode] 983. 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

這道題給了兩個數組 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 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM