题目描述

✅ 568. 最大休假天数

题意分析

初始位于城市零,每周一早上可以乘一次直飞航班,也可以留在原地。该周休假天数取决于到达的城市和周数,要求最大化全部周的累计休假天数,最后不用回到城市零。

当前去哪个城市不仅影响本周假期,也影响以后能搭乘哪些航班,因此不能只选择本周休假最多的目的地。未来只需要知道当前周数和所在城市,可以按这两个条件保存最优累计收益。

解法:按周滚动动态规划

核心思路

[!blue]
按周记录每个城市的最优累计收益。 处理 week 之前,dp[city] 表示前面各周已经结束、目前在该城市时的最大累计天数。到达同一城市且周数相同的两段行程,未来可选航班和休假安排完全一样,所以只需保留累计天数较多的一段。

初始尚未休假,只有城市零可达,令 dp[0] = 0,其他状态为 -1。合法累计天数都非负,所以 -1 能区分“到不了”与“到得了但休假零天”。第一周也按正常转移处理,因而可以在第一次休假前先搭乘航班。

枚举可达出发城市 from 和目的城市 to:只有 from == to 或 flights[from][to] == 1 时可以转移,候选收益为 dp[from] + days[to][week]。飞行在周初完成,本周收益必须算目的城市;航班是有向的,不能默认反方向也能飞,停留则不依赖对角线航班值。

next[to] 取全部合法来源的最大候选。任何合法行程都能拆成前几周的行程与本周一次停留或直飞,所以枚举来源不会遗漏最优选择;每条转移也都符合本周限制。新旧数组分开,使所有转移只读取上周状态,避免把刚到达的城市再作为本周出发点继续飞行。

每周结束后用 next 替换 dp,处理完全部周数再取任意城市的最大值。始终可以停留在城市零,所以至少存在一条合法行程;没有任何航班时,结果就是城市零各周的休假天数总和。

解题步骤

  1. 初始化城市零可达,其他城市不可达。
  2. 每周准备新的不可达状态数组。
  3. 枚举停留或直飞,按目的地天数更新最大值。
  4. 切换到新一层,结束后取最大终态。

代码实现

class Solution {
    public int maxVacationDays(int[][] flights, int[][] days) {
        int cityCount = flights.length;
        int weekCount = days[0].length;
        int[] dp = new int[cityCount];

        Arrays.fill(dp, -1);
        // 假期开始前:人在城市 0,已休 0 天,其余城市不可达。
        dp[0] = 0;

        for (int week = 0; week < weekCount; week++) {
            // 新开数组,避免本周结果被当作上周状态二次转移。
            int[] next = new int[cityCount];

            Arrays.fill(next, -1);

            for (int from = 0; from < cityCount; from++) {
                if (dp[from] == -1) {
                    continue;
                }

                for (int to = 0; to < cityCount; to++) {
                    // from == to 必须单独判:题目规定 flights[i][i] == 0,但留在原地合法。
                    if (from == to || flights[from][to] == 1) {
                        // 飞行在周初完成,整周算在目的地。
                        next[to] = Math.max(next[to], dp[from] + days[to][week]);
                    }
                }
            }

            dp = next;
        }

        // 最后停在哪个城市无所谓;初值 0 会忽略值为 -1 的不可达城市。
        int answer = 0;

        for (int val : dp) {
            answer = Math.max(answer, val);
        }

        return answer;
    }
}
func maxVacationDays(flights [][]int, days [][]int) int {
    cityCount := len(flights)
    weekCount := len(days[0])

    dp := make([]int, cityCount)
    for i := range dp {
        dp[i] = -1
    }
    // 假期开始前:人在城市 0,已休 0 天,其余城市不可达。
    dp[0] = 0

    for week := 0; week < weekCount; week++ {
        // 新开数组,避免本周结果被当作上周状态二次转移。
        next := make([]int, cityCount)
        for i := range next {
            next[i] = -1
        }

        for from := 0; from < cityCount; from++ {
            if dp[from] == -1 {
                continue
            }
            for to := 0; to < cityCount; to++ {
                // from == to 必须单独判:题目规定 flights[i][i] == 0,但留在原地合法。
                if from == to || flights[from][to] == 1 {
                    // 飞行在周初完成,整周算在目的地。
                    val := dp[from] + days[to][week]
                    if val > next[to] {
                        next[to] = val
                    }
                }
            }
        }

        dp = next
    }

    // 最后停在哪个城市无所谓;初值 0 会忽略值为 -1 的不可达城市。
    answer := 0
    for _, val := range dp {
        if val > answer {
            answer = val
        }
    }

    return answer
}

复杂度分析

  • 时间复杂度:$O(KN²)$,K 为周数,N 为城市数。
  • 空间复杂度:$O(N)$,只保留两层城市状态。

关键点总结

[!green]

  • 本周收益属于目的地,飞行在周初完成。
  • 停留由 from==to 单独允许,不依赖自环航班。
  • 不可达与合法零收益需要区分。

易错点总结

[!yellow]

  • 初始所有城市都设为零:相当于免费选择出发城市。
  • 加出发地天数:收益错开本周实际所在位置。
  • 直接覆盖旧层:可能在一周内重复飞行并累加收益。
  • 只返回城市零状态:错误要求最后回到起点。

相似题目

题目 难度 关联与区别
787. K 站中转内最便宜的航班 中等 同样按有限步数或阶段在城市间做DP,本题每周累计最大假期,原题在最多中转次数内取最低票价。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2025/42471341
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!