LeetCode 789. 逃脱阻碍者(贪心) / 1646. 获取生成数组中的最大值 / 787. K 站中转内最便宜的航班(有限制的最短路,重新审视迪杰斯特拉,动态规划)

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 789. 逃脱阻碍者(贪心) / 1646. 获取生成数组中的最大值 / 787. K 站中转内最便宜的航班(有限制的最短路,重新审视迪杰斯特拉,动态规划)相关的知识,希望对你有一定的参考价值。

789. 逃脱阻碍者

2021.8.22 每日一题

题目描述

你在进行一个简化版的吃豆人游戏。你从 [0, 0] 点开始出发,你的目的地是 target = [xtarget, ytarget] 。地图上有一些阻碍者,以数组 ghosts 给出,第 i 个阻碍者从 ghosts[i] = [xi, yi] 出发。所有输入均为 整数坐标 。

每一回合,你和阻碍者们可以同时向东,西,南,北四个方向移动,每次可以移动到距离原位置 1 个单位 的新位置。当然,也可以选择 不动 。所有动作 同时 发生。

如果你可以在任何阻碍者抓住你 之前 到达目的地(阻碍者可以采取任意行动方式),则被视为逃脱成功。如果你和阻碍者同时到达了一个位置(包括目的地)都不算是逃脱成功。

只有在你有可能成功逃脱时,输出 true ;否则,输出 false 。

示例 1:

输入:ghosts = [[1,0],[0,3]], target = [0,1]
输出:true
解释:你可以直接一步到达目的地 (0,1) ,在 (1, 0) 或者 (0, 3) 位置的阻碍者都不可能抓住你。

示例 2:

输入:ghosts = [[1,0]], target = [2,0]
输出:false
解释:你需要走到位于 (2, 0) 的目的地,但是在 (1, 0) 的阻碍者位于你和目的地之间。

示例 3:

输入:ghosts = [[2,0]], target = [1,0]
输出:false
解释:阻碍者可以和你同时达到目的地。

示例 4:

输入:ghosts = [[5,0],[-10,-2],[0,-5],[-2,-2],[-7,1]], target = [7,7]
输出:false

示例 5:

输入:ghosts = [[-1,0],[0,1],[-1,0],[0,1],[-1,0]], target = [0,0]
输出:true

提示:

1 <= ghosts.length <= 100
ghosts[i].length == 2
-10^4 <= xi, yi <= 10^4
同一位置可能有 多个阻碍者 。
target.length == 2
-10^4 <= xtarget, ytarget <= 10^4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/escape-the-ghosts
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

按这个思路,写了个代码,然后一提交,过了…惊呆我了
然后一看题解,果然是这样,这个距离叫曼哈顿距离

class Solution {
    public boolean escapeGhosts(int[][] ghosts, int[] target) {
        //因为看到同时到达目的地也算被抓住,然后想到一个很直观的思路就是,
        //如果有一个阻碍者先到达目的地,等着,那么肯定就不能成功
        //那么算距离就行了吗?
        //再想想,有没有可能阻碍者的距离大,但是还是能抓住;感觉好像不行
        int min = Math.abs(target[0]) + Math.abs(target[1]);

        int l = ghosts.length;
        for(int i = 0; i < l; i++){
            int temp = Math.abs(ghosts[i][0] - target[0]) + Math.abs(ghosts[i][1] - target[1]);
            if(temp <= min)
                return false;
        }
        return true;

    }
}

1646. 获取生成数组中的最大值

2021.8.23 每日一题

题目描述

给你一个整数 n 。按下述规则生成一个长度为 n + 1 的数组 nums :

nums[0] = 0
nums[1] = 1
当 2 <= 2 * i <= n 时,nums[2 * i] = nums[i]
当 2 <= 2 * i + 1 <= n 时,nums[2 * i + 1] = nums[i] + nums[i + 1]
返回生成数组 nums 中的 最大 值。

示例 1:

输入:n = 7
输出:3
解释:根据规则:
nums[0] = 0
nums[1] = 1
nums[(1 * 2) = 2] = nums[1] = 1
nums[(1 * 2) + 1 = 3] = nums[1] + nums[2] = 1 + 1 = 2
nums[(2 * 2) = 4] = nums[2] = 1
nums[(2 * 2) + 1 = 5] = nums[2] + nums[3] = 1 + 2 = 3
nums[(3 * 2) = 6] = nums[3] = 2
nums[(3 * 2) + 1 = 7] = nums[3] + nums[4] = 2 + 1 = 3
因此,nums = [0,1,1,2,1,3,2,3],最大值 3

示例 2:

输入:n = 2
输出:1
解释:根据规则,nums[0]、nums[1] 和 nums[2] 之中的最大值是 1

示例 3:

输入:n = 3
输出:2
解释:根据规则,nums[0]、nums[1]、nums[2] 和 nums[3] 之中的最大值是 2

提示:

0 <= n <= 100

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/get-maximum-in-generated-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

class Solution {
    public int getMaximumGenerated(int n) {
        //模拟
        if(n == 0)
            return 0;
        int[] nums = new int[n + 1];
        nums[0] = 0;
        nums[1] = 1;
        int max = 1;
        for(int i = 2; i <= n; i++){
            if(i % 2 == 0){
                nums[i] = nums[i / 2];
            }else{
                nums[i] = nums[i / 2] + nums[i / 2 + 1]; 
            }
            max = Math.max(max, nums[i]);
        }
        return max;
    }
}

787. K 站中转内最便宜的航班

2021.8.24 每日一题

题目描述

有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei] ,表示该航班都从城市 fromi 开始,以价格 toi 抵达 pricei。

现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。

示例 1:

输入:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
输出: 200
解释:
城市航班图如下

从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。

示例 2:

输入:

n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
输出: 500
解释:
城市航班图如下

从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示。

提示:

1 <= n <= 100
0 <= flights.length <= (n * (n - 1) / 2)
flights[i].length == 3
0 <= fromi, toi < n
fromi != toi
1 <= pricei <= 10^4
航班没有重复,且不存在自环
0 <= src, dst, k < n
src != dst

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/cheapest-flights-within-k-stops
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

又是最短路问题,最近做了好多这种题了,应该还能用迪杰斯特拉,用一个维度来表示中转的站数,如果大于k了,那么就跳过这条路线,如果达到这个点了,就返回当前花费,如果都不行,就返回-1

我是没想到会超时,那个输入也就13个点,这就超时了???想到问题了,应该是有环
那么应该怎么办呢,

class Solution {
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        //应该还能用迪杰斯特拉,用一个维度来表示中转的站数,
        //如果大于k了,那么就跳过这条路线,如果达到这个点了,就返回当前花费,如果都不行,就返回-1

        int l = flights.length;

        //先存图
        List<int[]>[] map = new List[n];
        for(int i = 0; i < n; i++){
            map[i] = new ArrayList<int[]>();
        }
        for(int i = 0; i < l; i++){
            int[] f = flights[i];
            List<int[]> list = map[f[0]];
            list.add(new int[]{f[1], f[2]});;
        }

        //三个参数,第一个达到的点,第二个花费,第三个中转的站数
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> (a[1] - b[1]));
        //初始位置,花费为0,中转个数为-1
        pq.offer(new int[]{src, 0, -1});
        while(!pq.isEmpty()){
            //取出当前位置
            int[] curr = pq.poll();
            //如果小于等于k了,并且到达了终点
            if(curr[2] <= k && curr[0] == dst){
                return curr[1];
            }
            //如果中转站数超过了,直接跳过
            if(curr[2] >= k)
                continue;
            //取出当前位置能直接相连的点
            List<int[]> list = map[curr[0]];
            for(int[] pos : list){
                pq.offer(new int[]{pos[0], pos[1] + curr[1], curr[2] + 1});
            }
        }
        return -1;
    }
}

如果有环的话,每次到达该节点,花费就会不断增加,所以用一个数组记录每个节点的花费,如果到达某个节点时,花费大于当前cost[i],那么这个节点就不加入队列了
但是这样还会造成一个问题,因为这个题中,还有中转次数的限制;如果刚刚到达这个点的最小花费中转次数很多,导致不能达到目标了;而花费多的中转次数少,也是可行的。所以还要设置一个数组,表示达到这个点中转了多少次,如果中转次数小,还是要入队列
这样,如果存在环的话,首先花费是一个筛选条件,其次,中转次数又是一个条件;这样,肯定能遍历到环外的点

但是呢,我又想了一下,刚刚没有这些筛选条件的时候,在环中走,也是会消耗中转次数的,如果中转次数等于0,也是会跳出环去走别的路线的。刚刚k也不是很大,为什么呢?想不太通
这种就是相当于剪枝了,去除了一些情况

class Solution {
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        //应该还能用迪杰斯特拉,用一个维度来表示中转的站数,
        //如果大于k了,那么就跳过这条路线,如果达到这个点了,就返回当前花费,如果都不行,就返回-1

        int l = flights.length;

        //先存图
        List<int[]>[] map = new List[n];
        for(int i = 0; i < n; i++){
            map[i] = new ArrayList<int[]>();
        }
        for(int i = 0; i < l; i++){
            int[] f = flights[i];
            List<int[]> list = map[f[0]];
            list.add(new int[]{f[1], f[2]});;
        }

        int[] costs = new int[n];
        int[] station = new int[n];
        Arrays.fill(costs, Integer.MAX_VALUE);
        //三个参数,第一个达到的点,第二个花费,第三个中转的站数
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> (a[1] - b[1]));
        //初始位置,花费为0,中转个数为-1
        pq.offer(new int[]{src, 0, -1});
        while(!pq.isEmpty()){
            //取出当前位置
            int[] curr = pq.poll();
            //如果小于等于k了,并且到达了终点
            if(curr[2] <= k && curr[0] == dst){
                return curr[1];
            }
            //如果中转站数超过了,直接跳过
            if(curr[2] >= k)
                continue;
            //取出当前位置能直接相连的点
            List<int[]> list = map[curr[0]];
            for(int[] pos : list){
                int cost = costs[pos[0]];
                //如果当前花费小的话,那么就入队列
                if(curr[1] + pos[1] < cost){
                    costs[pos[0]] = curr[1] + pos[1];
                    pq.offer(new int[]{pos[0], pos[1] + curr[1], curr[2] + 1});
                    station[pos[0]] = curr[2] + 1;
                //虽然花费多,但是如果当前中转站数小的话,也得入队列
                }else if(curr[2] < station[pos[0]]){
                    pq.offer(new int[]{pos[0], pos[1] + curr[1], curr[2] + 1});
                }
            }
        }
        return -1;
    }
}

再来看主流写法,动态规划:

class Solution {
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        //再写动规吧
        //dp[i][j]表示经过i个站点,到达j位置的最小花费
        int l = flights.length;
        
        //中转k次,也就是经过了k+1个站(除去起始站)
        int[][] dp = new int[k + 2][n];
        for(int i = 0; i < k + 2; i++){
            Arrays.fill(dp[i], Integer.MAX_VALUE / 2);
        }
        dp[0][src] = 0;

        for(int p = 1; p < k + 2; p++){
            //遍历所有航班,这里为什么不和平时一样遍历第二维呢,因为第二维不是从0开始的
            //当然也可以遍历起始位置和结束位置,也就是加里面两层循环,但是麻烦了
            for(int[] f : flights){
                int s = f[0], t = f[1], cost = f[2];
                dp[p][t] = Math.min(dp[p][t], dp[p - 1][s] + cost); 
            }
        }
        int res = Integer.MAX_VALUE / 2;
        for(int i = 1; i < k + 2; i++){
            res = Math.min(res, dp[i][dst]);
        }
        return res == Integer.MAX_VALUE / 2 ? -1 : res;
    }
}

学习一下克隆的用法

class Solution {
    int N = 110, INF = 0x3f3f3f3f;
    int[] dist = new int[N];
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        Arrays.fill(dist, INF);
        dist[src] = 0;
        for (int limit = 0; limit < k + 1; limit++) {
            int[] clone = dist.clone();
            for (int[] f : flights) {
                int x = f[0], y = f[1], w = f[2];
                dist[y] = Math.min(dist[y], clone[x] + w);
            }
        }
        return dist[dst] > INF / 2 ? -1 : dist[dst];
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/cheapest-flights-within-k-stops/solution/gong-shui-san-xie-xiang-jie-bellman-ford-dc94/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

感觉这个题最应该想到的是bfs,因为给定中转次数,相当于是层层扩展,然后记录终点的最小花费;花费多了,就不要扩展了

在回看最近做的迪杰斯特拉,为什么没有出现这个题中的环的问题呢?
先看了1928,前几次周赛的题,这个问题中很明显还是会有环的出现,但是这个题中也是有一个最大时间的的限制,所以也可以,当时写的时候,这里有一个数组记录了在某个时间到达某个点,花费的费用,如果大于这个费用,那么就跳过了。这个处理和今天这个题中的处理方式是一样的,所以今天这个题中也可以改成一个二维数组记录当前状态,经过k次到达某一站花费的费用
这周双周赛的题1976,同样也会有这种问题,而同样的,也是给出了时间限制

那重新看一下迪杰斯特拉,它的应用场景是有向无环图;而在无向图中,要找最短路的话,可以用一个vis[]数组记录已经走过的点,避免重复遍历
而在这种有限制的最短路问题中,是不是也能用vis数组来记录走过的点呢,显然是不太行的,因为并不是要找一条路径。那么应该怎么做呢,就是利用限制条件,并且记录到达当前点的时间(费用等,看题目条件),如果已经超过了记录的时间(…),那么说明可能是存在环了,那么就跳过本次循环。该不该直接跳过还是看条件。
总之,再次用迪杰斯特拉超时的时候,要想到怎么剪枝

而这个题的动态规划方法,也是记录到达一个点,经过k站的最小花费。可以从前向后,也可以从后向前。应该是算简单的思路了,写起来也比迪杰斯特拉简单。再碰到要会写!!!!!

以上是关于LeetCode 789. 逃脱阻碍者(贪心) / 1646. 获取生成数组中的最大值 / 787. K 站中转内最便宜的航班(有限制的最短路,重新审视迪杰斯特拉,动态规划)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 789 逃脱阻碍者[数学] HERODING的LeetCode之路

LeetCode 789. Escape The Ghosts

阻碍你使用 GraphQL 的十个问题

贪心算法问题-LeetCode 5545(贪心算法,跳跃问题)

leetcode之贪心算法刷题总结3

leetcode之贪心算法刷题总结2