LeetCode 1575. 统计所有可行路径 深搜和动态规划解法

给你一个 互不相同 的整数数组,其中 locations[i] 表示第 i 个城市的位置。同时给你 start,finish 和 fuel 分别表示出发城市、目的地城市和你初始拥有的汽油总量

每一步中,如果你在城市 i ,你可以选择任意一个城市 j ,满足  j != i 且 0 <= j < locations.length ,并移动到城市 j 。从城市 i 移动到 j 消耗的汽油量为 |locations[i] - locations[j]|,|x| 表示 x 的绝对值。

请注意, fuel 任何时刻都 不能 为负,且你 可以 经过任意城市超过一次(包括 start 和 finish )。

请你返回从 start 到 finish 所有可能路径的数目。

由于答案可能很大, 请将它对 10^9 + 7 取余后返回。

示例 1:

输入:locations = [2,3,6,8,4], start = 1, finish = 3, fuel = 5
输出:4
解释:以下为所有可能路径,每一条都用了 5 单位的汽油:
1 -> 3
1 -> 2 -> 3
1 -> 4 -> 3
1 -> 4 -> 2 -> 3
示例 2:

输入:locations = [4,3,1], start = 1, finish = 0, fuel = 6
输出:5
解释:以下为所有可能的路径:
1 -> 0,使用汽油量为 fuel = 1
1 -> 2 -> 0,使用汽油量为 fuel = 5
1 -> 2 -> 1 -> 0,使用汽油量为 fuel = 5
1 -> 0 -> 1 -> 0,使用汽油量为 fuel = 3
1 -> 0 -> 1 -> 0 -> 1 -> 0,使用汽油量为 fuel = 5
示例 3:

输入:locations = [5,2,1], start = 0, finish = 2, fuel = 3
输出:0
解释:没有办法只用 3 单位的汽油从 0 到达 2 。因为最短路径需要 4 单位的汽油。
示例 4 :

输入:locations = [2,1,5], start = 0, finish = 0, fuel = 3
输出:2
解释:总共有两条可行路径,0 和 0 -> 1 -> 0 。
 

方案一:

看到题首先就想到了深搜,从开始城市进行搜索,直到到达目的地且汽油>=0时,路径+1,如果中途汽油<0则返回重新进行搜索

     int num = 0;
	 public int countRoutes2(int[] locations, int start, int finish, int fuel) {
		 //从开始城市进行搜索
		 dfs(locations, start, finish, fuel);
		 return num;
	 }
	 public void dfs(int[] locations, int start, int finish, int fuel){
		//到达目的地且油量>=0时,路径总数+1
		 if(start == finish && fuel>=0){
			 num = num+1>1000000007?(num+1)%1000000007:num+1;
		 }
		//搜索可到达的下一个城市
		 for(int i = 0;i<locations.length;i++){
			 //到达下一个城市还剩的油量
			 int k = fuel-Math.abs(locations[start] - locations[i]);
			//如果下一个城市和就是当前城市或者油量不足,则不去,直接查询下一个城市
			 if(i == start || k<0){
				 continue;
			 }
			//搜索下一个可达城市
			dfs(locations, i, finish, k);
		 }
	 }

LeetCode打上Hard标签的题目显然不会这么简单,果然,运行后超时了,接下来就想办法优化一下时间复杂度,对于深搜的优化首先想到的就应该是剪枝操作,即记忆化搜索,将搜索过的路径记录下来,不用重复搜索,提高效率

      //map[i][j]记录从i到j需要消耗的油量
	  int[][] map;
	  //k=dp[start][fuel] 拥有fuel单位的油从start城市到达目的地,共有temp条路径,
	  int dp[][];
	 public int countRoutes(int[] locations, int start, int finish, int fuel) {
	        int len = locations.length;
	        map = new int[len][len];
	        //初始化map
	        for (int i = 0; i < len; i++) {
	            for (int j = i+1; j < len; j++) {
	            	int num = Math.abs(locations[i] - locations[j]);
	                map[i][j] = num;
	                map[j][i] = num;
	            }
	        }
	        dp = new int[len][fuel + 1];
	        //初始化dp
	        for (int[] temp : dp) {
	            Arrays.fill(temp, -1);
	        }
	        //开始深搜
	        return dfs(start, finish, fuel);
	    }

	    private int dfs(int start, int finish, int fuel) {
	    	//判断在start城市拥有fuel的油量是否已经搜索过,没有搜索过才进行搜索,如果搜索过直接返回
	        if (dp[start][fuel] == -1) {
	        	//判断是否到达目的地,到达则路径数为1,没到达则为0
	            int temp = start == finish ? 1 : 0;
	            for (int i = 0; i < map.length; i++) {
	            	//搜索拥有fuel油量可到达的非当前城市
	                if (i != start && map[start][i] <= fuel) {
	                	//继续从当前城市开始搜索,累加路径数
	                    temp += dfs(i, finish, fuel - map[start][i]);
	                    temp %= 1000000007;
	                }
	            }
	            //拥有fuel单位的油从start城市到达目的地,共有temp条路径,
	            dp[start][fuel] = temp;
	        }
	        return dp[start][fuel];
	    }

果然一下子提升了效率通过了

然后在贴出来题解里边大神们用动态规划的解题方法

/**
	 *  先求出每两个地点所需油量,用w[][]表示。
		用dp[city][used]表示到达city时用了累计油量为used;
		状态转移:
		dp[city][used] = sum(dp[before][used-w[before][city]]), before!=city;
		其中要特殊考虑是否为第一次出发(used - w[city][before]==0时),详情看代码给出的特殊处理;
		最后对dp[finish]求和,其中由于dp[city][used]可能很大,每一步计算都要取模。
	 */
	public int countRoutes1(int[] locations, int start, int finish, int fuel) {
		int[][] w = new int[locations.length][locations.length];
		for (int i = 0; i < locations.length; i++) {
			for (int j = i+1; j < locations.length; j++) {
				int num = Math.abs(locations[i] - locations[j]);
				w[i][j] = num;
				w[j][i] = num;
			}
		}
		long[][] dp = new long[locations.length][fuel + 1];
		dp[start][0] = 1;
		for (int used = 1; used < fuel + 1; used++) {
			for (int city = 0; city < locations.length; city++) {
				for (int before = 0; before < locations.length; before++) {
					if (used - w[city][before] < 0 || before == city) {
						continue;
					}
					if (used - w[city][before] == 0) {
						if (before == start) {
							dp[before][used - w[city][before]] = 1;
						}
					}
					dp[city][used] = (dp[city][used] + dp[before][used - w[city][before]]) % 1000000007;
				}
			}
	    }
		    return (int) (LongStream.of(dp[finish]).sum() % 1000000007);
	}
  public int countRoutes4(int[] locations, int start, int finish, int fuel) {
        int n=locations.length;
      //代表当前有fuel油,最后在n位置有多少种可能
        long[][] dp=new long[fuel+1][n];
      //初始化,开始位置可能为1,没油时其余位置为0
        dp[0][start]=1;
        for (int i = 1; i <= fuel; i++) {
            for (int j = 0; j < n; j++) {
                for (int k = 0; k < n; k++) {
                    int cost=Math.abs(locations[k]-locations[j]);
                    if(k==j || cost>i)continue;
                    dp[i][j]+=dp[i-cost][k];
                }
                dp[i][j]=dp[i][j]%1000000007;
            }
        }
        long ans=0;
        for (int i = 0; i <= fuel; i++) {
            ans+=dp[i][finish]%1000000007;
        }
        long a=ans%1000000007;
        return (int)a;
    }

总体来说效率最高的就是优化后的深搜

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值