算法思想一:递归+记忆化递归

解题思路:

可以对种状态利用二进制掩码形式逐一对其递归进行搜索,每次查看经费和前后续关系。搜索函数中为当前的状态数,为当前最大这段序列可访问的最大城市数,递归的时候减小经费,改变即可进入子问题。为了防止递归处理的问题太大,可以用数组dp记录所有的前面计算过的序列的可访问的最大值。

图解

代码展示:

C++版本

/**
 * struct Point {
 *    int x;
 *    int y;
 *    Point(int xx, int yy) : x(xx), y(yy) {}
 * };
 */
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param N int整型 N
     * @param V int整型 V
     * @param A int整型vector A
     * @param List Point类vector List
     * @return int整型
     */
    int search(vector<vector<int> >& city, vector<int>& dp, vector<int>& A, int N, int V, int m, int n){
        if(dp[m] != 0)
            return dp[m];
        int maxn = n;
        for(int i = 0; i < N; i++){
            if(((1  0 || A[i] > V)  // 如果当前 节点已经走过或者费用不足,就不需要进行搜索
                continue;
            bool flag = true;
            for(int j = 0; j < city[i].size(); j++){ //查看它的前驱节点是否走过
                if(((1 << city[i][j]) & m) == 0){
                    flag = false;
                    break;
                }
            }
            if(flag) //找到最大值
                maxn = max(maxn, search(city, dp, A, N, V-A[i], m ^ (1 << i), n + 1));
        }
        dp[m] = maxn;  //记忆避免多次递归
        return dp[m];
    }
    int Travel(int N, int V, vector<int>& A, vector<Point>& List) {
        // write code here
        vector dp(1 << N, 0); //一共2^N个状态
        vector > city(N, vector()); //记录城市的先后顺序
        for(int i = 0; i < List.size(); i++)
            city[List[i].y - 1].push_back(List[i].x - 1);
        return search(city, dp, A, N, V, 0, 0);
    }
};

复杂度分析

时间复杂度:递归中需要循环的部分其实只有dp的大小,其他的都是直接返回

空间复杂度:dp占用空间,city最差情况下

算法思想二:状压dp

解题思路:

解题思路
1、状态定义:dp[i]表示二进制mask为i时,需要的旅游开销。
2、状态初始化:将mask为0设为起点,表示不去任何城市时,旅游开销是0。其他mask位设为无穷大,表示不可达。
3、状态转移:如果当前城市没有来过,并且当前城市i的前置城市都去过了,则可转移到下一个mask位,对应开销加上当前城市开销,即
当所有的mask位都统计完成后,计算满足开销不大于V的最大可去城市数目(mask位中1的个数)。

代码展示:

JAVA版本

import java.util.*;
/*
 * public class Point {
 *   int x;
 *   int y;
 *   public Point(int x, int y) {
 *     this.x = x;
 *     this.y = y;
 *   }
 * }
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param N int整型 N
     * @param V int整型 V
     * @param A int整型一维数组 A
     * @param List Point类一维数组 List
     * @return int整型
     */
    public int Travel (int N, int V, int[] A, Point[] List) {
        // write code here
        //初始化限制数组
        int[][] limit=new int[N][N];
        for(Point p:List){
            limit[p.y-1][p.x-1]=1;
        }
        //初始化状态压缩dp,dp[i]表示二进制mask为i时,需要的旅游开销
        int[] dp=new int[1<<N];
        Arrays.fill(dp,Integer.MAX_VALUE/2);
        dp[0]=0;
        for(int mask=0;mask<(1<<N);mask++){
            //如果mask不可达,直接跳过
            if(dp[mask]==Integer.MAX_VALUE/2) continue;
            for(int i=0;i<N;i++){
                //如果当前城市已经来过,则跳过
                if(((mask>>i)&1)!=0) continue;
                //判断当前城市i是否还有未去过的前置城市
                boolean pass=true;
                for(int j=0;j<N;j++){
                    if(limit[i][j]==1&&((mask>>j)&1)==0){
                        pass=false;
                        break;
                    }
                }
                if(!pass) continue;
                //如果前置城市都去过了,则跳到下一个mask
                dp[mask|1<<i]=dp[mask]+A[i];
            }
        }

        int res=0;
        for(int mask=0;mask<(1<<N);mask++){
            //如果旅游开销不大于V,则计算对应的最大的可去城市数
            if(dp[mask]<=V){
                res=Math.max(res,Integer.bitCount(mask));
            }
        }
        return res;
    }
}

复杂度分析

时间复杂度:总共三层循环,第一层大小2^n,第二层和第三层大小都是n

空间复杂度:dp占用空间