LeetCode wants to give one of its best employees the option to travel among N cities to collect algorithm problems. But all work and no play makes Jack a dull boy, you could take vacations in some particular cities and weeks. Your job is to schedule the traveling to maximize the number of vacation days you could take, but there are certain rules and restrictions you need to follow.
Rules and restrictions:
- You can only travel among N cities, represented by indexes from 0 to N-1. Initially, you are in the city indexed 0 on Monday.
- The cities are connected by flights. The flights are represented as a N*N matrix (not necessary symmetrical), called flights representing the airline status from the city i to the city j. If there is no flight from the city i to the city j, flights[i][j] = 0; Otherwise, flights[i][j] = 1. Also, flights[i][i] = 0 for all i.
- You totally have K weeks (each week has 7 days) to travel. You can only take flights at most once per day and can only take flights on each week's Monday morning. Since flight time is so short, we don't consider the impact of flight time.
- For each city, you can only have restricted vacation days in different weeks, given an N*K matrix called days representing this relationship. For the value of days[i][j], it represents the maximum days you could take vacation in the city i in the week j.
You're given the flights matrix and days matrix, and you need to output the maximum vacation days you could take during K weeks.
Example 1:
Input:flights = [[0,1,1],[1,0,1],[1,1,0]], days = [[1,3,1],[6,0,3],[3,3,3]] Output: 12 Explanation:
Ans = 6 + 3 + 3 = 12.
One of the best strategies is: 1st week : fly from city 0 to city 1 on Monday, and play 6 days and work 1 day.
(Although you start at city 0, we could also fly to and start at other cities since it is Monday.) 2nd week : fly from city 1 to city 2 on Monday, and play 3 days and work 4 days. 3rd week : stay at city 2, and play 3 days and work 4 days.
Example 2:
Input:flights = [[0,0,0],[0,0,0],[0,0,0]], days = [[1,1,1],[7,7,7],[7,7,7]] Output: 3 Explanation:
Ans = 1 + 1 + 1 = 3.
Since there is no flights enable you to move to another city, you have to stay at city 0 for the whole 3 weeks.
For each week, you only have one day to play and six days to work.
So the maximum number of vacation days is 3.
Example 3:
Input:flights = [[0,1,1],[1,0,1],[1,1,0]], days = [[7,0,0],[0,7,0],[0,0,7]] Output: 21 Explanation:
Ans = 7 + 7 + 7 = 21
One of the best strategies is: 1st week : stay at city 0, and play 7 days. 2nd week : fly from city 0 to city 1 on Monday, and play 7 days. 3rd week : fly from city 1 to city 2 on Monday, and play 7 days.
Note:
- N and K are positive integers, which are in the range of [1, 100].
- In the matrix flights, all the values are integers in the range of [0, 1].
- In the matrix days, all the values are integers in the range [0, 7].
- You could stay at a city beyond the number of vacation days, but you should work on the extra days, which won't be counted as vacation days.
- If you fly from the city A to the city B and take the vacation on that day, the deduction towards vacation days will count towards the vacation days of city B in that week.
- We don't consider the impact of flight hours towards the calculation of vacation days.
這道題給了我們一個NxN的數組,表示城市i是否有飛機直達城市j,又給了我們一個NxK的數組days,表示在第j周能在城市i休假的天數,讓我們找出一個行程能使我們休假的天數最大化。開始嘗試寫了個遞歸的暴力破解法,結果TLE了。其實這道題比較適合用DP來解,我們建立一個二維DP數組,其中dp[i][j]表示目前是第j周,並且在此時在城市i,總共已經獲得休假的總日子數。我們采取從后往前更新的方式(不要問我為什么,因為從前往后更新的寫法要復雜一些),我們從第k周開始往第一周遍歷,那么最后結果都累加在了dp[i][0]中,i的范圍是[0, n-1],找出其中的最大值就是我們能休息的最大假期數了。難點就在於找遞推式了,我們想dp[i][j]表示的是當前是第j周並在城市i已經獲得的休假總日子數,那么上一個狀態,也就是j+1周(因為我們是從后往前更新),跟當前狀態有何聯系,上一周我們可能還在城市i,也可能在其他城市p,那么在其他城市p的條件是,城市p有直飛城市i的飛機,那么我們可以用上一個狀態的值dp[p][j+1]來更新當前值dp[i][j],還要注意的是我們要從倒數第二周開始更新,因為倒數第一周沒有上一個狀態,還有就是每個狀態dp[i][j]都初始化賦為days[i][j]來更新,這樣一旦沒有任何城市可以直飛當前城市,起碼我們還可以享受當前城市的假期,最后要做的就是想上面所說在dp[i][0]中找最大值,下面的代碼是把這一步融合到了for循環中,所以加上了一堆判斷條件,我們也可以在dp數組整個更新結束之后再來找最大值,參見代碼如下:
解法一:
class Solution { public: int maxVacationDays(vector<vector<int>>& flights, vector<vector<int>>& days) { int n = flights.size(), k = days[0].size(), res = 0; vector<vector<int>> dp(n, vector<int>(k, 0)); for (int j = k - 1; j >= 0; --j) { for (int i = 0; i < n; ++i) { dp[i][j] = days[i][j]; for (int p = 0; p < n; ++p) { if ((i == p || flights[i][p]) && j < k - 1) { dp[i][j] = max(dp[i][j], dp[p][j + 1] + days[i][j]); } if (j == 0 && (i == 0 || flights[0][i])) res = max(res, dp[i][0]); } } } return res; } };
下面這種方法優化了空間復雜度,只用了一個一維的DP數組,其中dp[i]表示在當前周,在城市i時已經獲得的最大假期數,並且除了第一個數初始化為0,其余均初始化為整型最小值,然后我們從第一周往后遍歷,我們新建一個臨時數組t,初始化為整型最小值,然后遍歷每一個城市,對於每一個城市,我們遍歷其他所有城市,看是否有飛機能直達當前城市,或者就是當前的城市,我們用dp[p] + days[i][j]來更更新dp[i],當每個城市都遍歷完了之后,我們將t整個賦值給dp,然后進行下一周的更新,最后只要在dp數組中找出最大值返回即可,這種寫法不但省空間,而且也相對簡潔一些,很贊啊~
解法二:
class Solution { public: int maxVacationDays(vector<vector<int>>& flights, vector<vector<int>>& days) { int n = flights.size(), k = days[0].size(); vector<int> dp(n, INT_MIN); dp[0] = 0; for (int j = 0; j < k; ++j) { vector<int> t(n, INT_MIN); for (int i = 0; i < n; ++i) { for (int p = 0; p < n; ++p) { if (i == p || flights[p][i]) { t[i] = max(t[i], dp[p] + days[i][j]); } } } dp = t; } return *max_element(dp.begin(), dp.end()); } };
之前提到了遞歸的DFS會TLE,但是如果我們使用一個memo數組來保存中間計算結果,就能省去大量的重復計算,並且能夠通過OJ,解題思想跟解法一非常的類似,參見代碼如下:
解法三:
class Solution { public: int maxVacationDays(vector<vector<int>>& flights, vector<vector<int>>& days) { int n = flights.size(), k = days[0].size(); vector<vector<int>> memo(n, vector<int>(k, 0)); return helper(flights, days, 0, 0, memo); } int helper(vector<vector<int>>& flights, vector<vector<int>>& days, int city, int day, vector<vector<int>>& memo) { int n = flights.size(), k = days[0].size(), res = 0; if (day == k) return 0; if (memo[city][day] > 0) return memo[city][day]; for (int i = 0; i < n; ++i) { if (i == city || flights[city][i] == 1) { res = max(res, days[i][day] + helper(flights, days, i, day + 1, memo)); } } return memo[city][day] = res; } };
參考資料:
https://discuss.leetcode.com/topic/87865/java-dfs-tle-and-dp-solutions/2
https://discuss.leetcode.com/topic/87869/c-clean-code-graphic-explanation
https://discuss.leetcode.com/topic/89353/short-java-recursion-dfs-memoization