Skip to content

LeetCode 568: Maximum Vacation Days

A clear explanation of Maximum Vacation Days using dynamic programming over weeks and cities.

Problem Restatement

We are given two matrices:

MatrixMeaning
flightsWhether we can fly from one city to another
daysHow many vacation days we can take in each city during each week

There are n cities, indexed from 0 to n - 1.

We start in city 0 on Monday morning before week 0.

Each week, we may either:

  1. Stay in the current city.
  2. Fly to another city if there is a direct flight.

Flights can only happen on Monday morning of each week. After choosing the city for that week, we gain days[city][week] vacation days.

Return the maximum total vacation days possible.

The original constraints are 1 <= n, k <= 100, flights[i][j] is 0 or 1, and days[i][j] is between 0 and 7. The flight matrix is not necessarily symmetric.

Input and Output

ItemMeaning
Inputflights, an n x n matrix
Inputdays, an n x k matrix
OutputMaximum total vacation days
Start cityCity 0
Travel ruleStay or take one allowed flight at the start of each week

Example function shape:

def maxVacationDays(flights: list[list[int]], days: list[list[int]]) -> int:
    ...

Examples

Example 1:

flights = [
    [0, 1, 1],
    [1, 0, 1],
    [1, 1, 0],
]

days = [
    [1, 3, 1],
    [6, 0, 3],
    [3, 3, 3],
]

We start in city 0.

One good plan is:

WeekCityVacation days
016
123
213

Total:

6 + 3 + 3 = 12

So the answer is:

12

Example 2:

flights = [
    [0, 0, 0],
    [0, 0, 0],
    [0, 0, 0],
]

days = [
    [1, 1, 1],
    [7, 7, 7],
    [7, 7, 7],
]

There are no flights.

We must stay in city 0 every week.

So the answer is:

1 + 1 + 1 = 3

First Thought: Try Every Travel Plan

A direct solution is to recursively try every city choice for every week.

At each week, from the current city, we try:

  1. Staying.
  2. Flying to every reachable city.

This explores many possible schedules.

With up to 100 weeks and 100 cities, the number of possible paths is far too large.

We need dynamic programming because the same state appears many times.

Key Insight

At the beginning of a week, the future only depends on two things:

StateMeaning
weekWhich week we are about to process
cityWhich city we are currently in

It does not matter how we reached that city. Only the best total vacation days so far matters.

So we define a DP state:

dp[city]

as the maximum vacation days we can have before the current week while being in city.

Then each week, we build a new array next_dp.

Transition

Suppose we are currently in city src.

During the next week, we can go to city dst if:

src == dst

or:

flights[src][dst] == 1

Then we can update:

next_dp[dst] = max(next_dp[dst], dp[src] + days[dst][week])

The vacation days come from the destination city, because we spend that week there.

Algorithm

  1. Let n = len(flights).
  2. Let k = len(days[0]).
  3. Initialize all cities as unreachable:
    dp = [-inf] * n
  4. Set:
    dp[0] = 0
  5. For each week:
    1. Create next_dp = [-inf] * n.
    2. For each source city src:
      1. If dp[src] is unreachable, skip it.
      2. For each destination city dst:
        1. If src == dst or flights[src][dst] == 1, update next_dp[dst].
    3. Replace dp with next_dp.
  6. Return max(dp).

Correctness

Before week w, let dp[city] be the maximum vacation days possible after finishing weeks 0 through w - 1 and ending in city.

This is true initially because before any week starts, we are in city 0 with 0 vacation days, and every other city is unreachable.

For each week, the algorithm considers every reachable source city and every valid destination city. A destination is valid exactly when we either stay in the same city or there is a direct flight from the source city to the destination city.

When we choose destination dst for week w, we gain:

days[dst][w]

vacation days. Therefore the transition computes the best possible total after week w.

Since every legal schedule consists of exactly one such stay-or-flight choice per week, every legal schedule is considered. Since the algorithm always keeps the maximum value for each destination city, no worse schedule can affect the answer.

After all weeks are processed, we may end in any city. So the maximum value over all cities is exactly the maximum total vacation days possible.

Complexity

Let n be the number of cities and k be the number of weeks.

MetricValueWhy
TimeO(k n^2)For each week, we try every source and destination city
SpaceO(n)We keep only the previous and current week DP arrays

Implementation

class Solution:
    def maxVacationDays(self, flights: list[list[int]], days: list[list[int]]) -> int:
        n = len(flights)
        k = len(days[0])

        NEG_INF = -10**18

        dp = [NEG_INF] * n
        dp[0] = 0

        for week in range(k):
            next_dp = [NEG_INF] * n

            for src in range(n):
                if dp[src] == NEG_INF:
                    continue

                for dst in range(n):
                    if src == dst or flights[src][dst] == 1:
                        next_dp[dst] = max(
                            next_dp[dst],
                            dp[src] + days[dst][week],
                        )

            dp = next_dp

        return max(dp)

Code Explanation

We start with city 0 reachable:

dp[0] = 0

Every other city starts as unreachable:

dp = [NEG_INF] * n

For each week, we create a new DP array:

next_dp = [NEG_INF] * n

Then we try all possible transitions from source city to destination city:

if src == dst or flights[src][dst] == 1:

The condition includes staying in the same city. This is necessary because flights[i][i] is 0, but staying is always allowed.

If the move is valid, we update the best total for the destination:

next_dp[dst] = max(next_dp[dst], dp[src] + days[dst][week])

After all transitions for the week are done, we move forward:

dp = next_dp

Finally, the answer is the best total among all ending cities.

Testing

def run_tests():
    s = Solution()

    assert s.maxVacationDays(
        [
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 0],
        ],
        [
            [1, 3, 1],
            [6, 0, 3],
            [3, 3, 3],
        ],
    ) == 12

    assert s.maxVacationDays(
        [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
        ],
        [
            [1, 1, 1],
            [7, 7, 7],
            [7, 7, 7],
        ],
    ) == 3

    assert s.maxVacationDays(
        [
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 0],
        ],
        [
            [7, 0, 0],
            [0, 7, 0],
            [0, 0, 7],
        ],
    ) == 21

    assert s.maxVacationDays(
        [[0]],
        [[5, 6, 7]],
    ) == 18

    print("all tests passed")

run_tests()
TestWhy
Fully connected sampleChecks normal weekly city choices
No flightsMust stay in city 0
Best city changes each weekChecks flight transitions
One cityOnly staying is possible