Leetcode 第56场双周赛 / 第249场周赛 / 1411. 给 N x 3 网格图涂色的方案数(这次主要还是学动规思想和迪杰斯克拉)

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode 第56场双周赛 / 第249场周赛 / 1411. 给 N x 3 网格图涂色的方案数(这次主要还是学动规思想和迪杰斯克拉)相关的知识,希望对你有一定的参考价值。

第56场双周赛

这次第一次进前五百,有点开心

5792. 统计平方和三元组的数目

题目描述

一个 平方和三元组 (a,b,c) 指的是满足 a2 + b2 = c2 的 整数 三元组 a,b 和 c 。

给你一个整数 n ,请你返回满足 1 <= a, b, c <= n 的 平方和三元组 的数目。

示例 1:

输入:n = 5
输出:2
解释:平方和三元组为 (3,4,5) 和 (4,3,5) 。
示例 2:

输入:n = 10
输出:4
解释:平方和三元组为 (3,4,5),(4,3,5),(6,8,10) 和 (8,6,10) 。

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

思路

计算两数平方之和的平方根,然后判断平方根的平方是否等于两数平方之和

class Solution {
    public int countTriples(int n) {
        int res = 0;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                int c = i * i + j * j;
                int t = (int)Math.sqrt(c);
                if(t <= n && t * t == c)
                    res++;
            }
        }
        return res;
    }
}

5793. 迷宫中离入口最近的出口

给你一个 m x n 的迷宫矩阵 maze (下标从 0 开始),矩阵中有空格子(用 ‘.’ 表示)和墙(用 ‘+’ 表示)。
同时给你迷宫的入口 entrance ,用 entrance = [entrancerow, entrancecol] 表示你一开始所在格子的行和列。

每一步操作,你可以往 上,下,左 或者 右 移动一个格子。你不能进入墙所在的格子,你也不能离开迷宫。
你的目标是找到离 entrance 最近 的出口。出口 的含义是 maze 边界 上的 空格子。entrance 格子 不算 出口。

请你返回从 entrance 到最近出口的最短路径的 步数 ,如果不存在这样的路径,请你返回 -1 。

示例 1:
在这里插入图片描述

输入:maze = [["+","+",".","+"],[".",".",".","+"],["+","+","+","."]], entrance = [1,2]
输出:1
解释:总共有 3 个出口,分别位于 (1,0),(0,2) 和 (2,3) 。
一开始,你在入口格子 (1,2) 处。

  • 你可以往左移动 2 步到达 (1,0) 。
  • 你可以往上移动 1 步到达 (0,2) 。
    从入口处没法到达 (2,3) 。
    所以,最近的出口是 (0,2) ,距离为 1 步。
    示例 2:
    在这里插入图片描述

输入:maze = [["+","+","+"],[".",".","."],["+","+","+"]], entrance = [1,0]
输出:2
解释:迷宫中只有 1 个出口,在 (1,2) 处。
(1,0) 不算出口,因为它是入口格子。
初始时,你在入口与格子 (1,0) 处。

  • 你可以往右移动 2 步到达 (1,2) 处。
    所以,最近的出口为 (1,2) ,距离为 2 步。
    示例 3:

在这里插入图片描述

输入:maze = [[".","+"]], entrance = [0,0]
输出:-1
解释:这个迷宫中没有出口。

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

思路

典型迷宫计算最少步数的题,广度优先搜索,当时写的时候,明知应该用bfs,还是先写了个dfs结果提交超时了,又改了个bfs,作死。。

class Solution {
    int[][] direction = {{1,0},{-1,0},{0,1},{0,-1}};
    int m;
    int n;
    boolean[][] used;
    public int nearestExit(char[][] maze, int[] entrance) {
        m = maze.length;
        n = maze[0].length;
        used = new boolean[m][n];
        int i = entrance[0];
        int j = entrance[1];
        used[i][j] = true;
        int step = 0;
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(entrance);
        while(!queue.isEmpty()){
            int size = queue.size();
            step++;
            while(size-- > 0){
                int[] temp = queue.poll();
                for(int k = 0; k < 4; k++){
                    int x = temp[0] + direction[k][0];
                    int y = temp[1] + direction[k][1];
                    if(!check(x, y)){
                        if(step != 1){
                            return step - 1;
                        }
                        continue;
                    }
                    if(maze[x][y] == '+' || used[x][y])
                        continue;
                    queue.add(new int[]{x, y});
                    used[x][y] = true;
                }
            }
        }        
        return -1;
    }
    public boolean check(int x, int y){
        return x >= 0 && x < m && y >= 0 && y < n;
    }
}

5794. 求和游戏

题目描述

Alice 和 Bob 玩一个游戏,两人轮流行动,Alice 先手 。

给你一个 偶数长度 的字符串 num ,每一个字符为数字字符或者 ‘?’ 。每一次操作中,如果 num 中至少有一个 ‘?’ ,那么玩家可以执行以下操作:

选择一个下标 i 满足 num[i] == ‘?’ 。
将 num[i] 用 ‘0’ 到 ‘9’ 之间的一个数字字符替代。
当 num 中没有 ‘?’ 时,游戏结束。

Bob 获胜的条件是 num 中前一半数字的和 等于 后一半数字的和。Alice 获胜的条件是前一半的和与后一半的和 不相等 。

比方说,游戏结束时 num = “243801” ,那么 Bob 获胜,因为 2+4+3 = 8+0+1 。如果游戏结束时 num = “243803” ,那么 Alice 获胜,因为 2+4+3 != 8+0+3 。
在 Alice 和 Bob 都采取 最优 策略的前提下,如果 Alice 获胜,请返回 true ,如果 Bob 获胜,请返回 false 。

示例 1:

输入:num = “5023”
输出:false
解释:num 中没有 ‘?’ ,没法进行任何操作。
前一半的和等于后一半的和:5 + 0 = 2 + 3 。
示例 2:

输入:num = “25??”
输出:true
解释:Alice 可以将两个 ‘?’ 中的一个替换为 ‘9’ ,Bob 无论如何都无法使前一半的和等于后一半的和。
示例 3:

输入:num = “?3295???”
输出:false
解释:Bob 总是能赢。一种可能的结果是:

  • Alice 将第一个 ‘?’ 用 ‘9’ 替换。num = “93295???” 。
  • Bob 将后面一半中的一个 ‘?’ 替换为 ‘9’ 。num = “932959??” 。
  • Alice 将后面一半中的一个 ‘?’ 替换为 ‘2’ 。num = “9329592?” 。
  • Bob 将后面一半中最后一个 ‘?’ 替换为 ‘7’ 。num = “93295927” 。
    Bob 获胜,因为 9 + 3 + 2 + 9 = 5 + 9 + 2 + 7 。

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

思路

第一眼看到是这种题感觉凉了,最后根据数据范围排除了复杂的方法有惊无险做出来了

我是这样想的,首先求出前后半段和,还有前后半段有几个问号
然后因为操作是交替进行的,所以两个人如果填在不同边(指前后不同),那么就可以填相同的数相互抵消。
而如果前面sum之和大于后面sum之和,而且前面的?也比后面的多,那么两边之和肯定不相同,肯定Alice胜利
同理,后面sum之和大于前面sum之和,后面?也比前面多,那么Alice胜利
如果前后和还有?都相同,那么Bob可以通过在不同段填和Alice一样的数 来保证胜利

剩下两种情况就是前面大于等于后面和,但是前面的?比后面的少,或者反过来,那么这种怎么想呢?
我想的是先前后抵消,然后此时需要在后面填 k 个空,使得和为 differ。
如果此时?为奇数个,那么不管Bob最后一次怎么填,Alice都可以使结果不相同,Alice必胜
如果为偶数个,那么Bob进行最后一次操作,就有可能扭转局面哈哈,什么情况下才能扭转呢。
两人一个填k个空,也就是一个人填count次,和要等于differ,而Alice是不想让Bob赢的,如果differ大于或者小于9*count,那么Alice可以只填0或者只填9来使和不可能等于differ,而只有等于9 *count的时候,Alice填了一个数x,那么Bob就填9-x来保证每一组数都是9,从而获得胜利

class Solution {
    public boolean sumGame(String num) {
        //Bob能获胜的话,是不管怎么添数,前后都相同
        char[] ss = num.toCharArray();
        int l = ss.length;
        int presum = 0;
        int postsum = 0;
        int pre = 0;
        int post = 0;
        for(int i = 0; i < l / 2; i++){
            if(ss[i] == '?')
                pre++;
            else
                presum += ss[i] - '0';
        }
        for(int i = l / 2; i < l; i++){
            if(ss[i] == '?')
                post++;
            else
                postsum += ss[i] - '0';
        }
        System.out.println(pre);
        System.out.println(post);
        System.out.println(presum);
        System.out.println(postsum);
        
        if(presum > postsum && pre >= post)
            return true;
        if(presum < postsum && pre <= post)
            return true;
        if(presum == postsum && pre == post)
            return false;
        if(presum >= postsum && pre < post){
            int sum = presum - postsum;
            int kong = post - pre;
            int count = kong / 2;
            System.out.println(kong);
            if(kong % 2 == 0 && count * 9 == sum)
                return false;
        }
        if(presum <= postsum && pre > post){
            int sum = postsum - presum;
            int kong = pre - post;
            int count = kong / 2;
            if(kong % 2 == 0 && count * 9 == sum)
                return false;
        }
        return true;
    }
}

5795. 规定时间内到达终点的最小花费

题目描述

一个国家有 n 个城市,城市编号为 0 到 n - 1 ,题目保证 所有城市 都由双向道路 连接在一起 。
道路由二维整数数组 edges 表示,其中 edges[i] = [xi, yi, timei] 表示城市 xi 和 yi 之间有一条双向道路,耗费时间为 timei 分钟。两个城市之间可能会有多条耗费时间不同的道路,但是不会有道路两头连接着同一座城市。

每次经过一个城市时,你需要付通行费。通行费用一个长度为 n 且下标从 0 开始的整数数组 passingFees 表示,其中 passingFees[j] 是你经过城市 j 需要支付的费用。

一开始,你在城市 0 ,你想要在 maxTime 分钟以内 (包含 maxTime 分钟)到达城市 n - 1 。旅行的 费用 为你经过的所有城市 通行费之和 (包括 起点和终点城市的通行费)。

给你 maxTime,edges 和 passingFees ,请你返回完成旅行的 最小费用 ,如果无法在 maxTime 分钟以内完成旅行,请你返回 -1 。

示例 1:
在这里插入图片描述

输入:maxTime = 30, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:11
解释:最优路径为 0 -> 1 -> 2 -> 5 ,总共需要耗费 30 分钟,需要支付 11 的通行费。
示例 2:

在这里插入图片描述

输入:maxTime = 29, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:48
解释:最优路径为 0 -> 3 -> 4 -> 5 ,总共需要耗费 26 分钟,需要支付 48 的通行费。
你不能选择路径 0 -> 1 -> 2 -> 5 ,因为这条路径耗费的时间太长。
示例 3:

输入:maxTime = 25, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:-1
解释:无法在 25 分钟以内从城市 0 到达城市 5 。

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

思路

做完第三道还有三分钟,这道题都没来的及看…
按照大佬的思路写了个动态规划,但是超时了,不知道为什么,感觉一样的啊,C++能过,java过不了

动态规划的思路是这样,先预处理邻接矩阵
然后dp[t][i]定义为t时间内能到达位置i 的最少花费
初始化很重要,因为任意时间,只要不走动,都在0位置,所以dp[t][0] = passingFees[0]
而转移的时候,也和一般转移情况不同,而是对于当前时刻位置,找能转移的点转移。而不是和一般情况一样,在当前点找之前的点转移。因为要保证是从0位置开始往后走,所以这样进行转移

超时看了一下,应该是因为第三层循环,这里C++代码里面是这样写的
for (auto [v, w] : adj[i])
这样写取的也是从i开始可以到达的点,为啥这里不会超时。而且给出的时间复杂度也没有计算这层循环,看了一下这种写法用java能过的,是把i到达的点用哈希表存储起来了。。实际上感觉还是遍历,只不过遍历的范围少了一点,这里就不改了,懂这个意思就行了

class Solution {
    public int minCost(int maxTime, int[][] edges, int[] passingFees) {
        //这道题咋说呢,感觉应该能写出来,毕竟思路很明确
        //dp[i][j]为在i时刻前到达j城市的最小花费
        //那么转移方程就应该是所有能到达j的城市x花费的最小值,dp[i][j] = min(dp[i - t][x] + cost)
        
        int n = passingFees.length;
        //邻接矩阵
        int[][] adj = new int[n][n];
        //赋初值,不可达
        for(int i = 0; i < n; i++){
            Arrays.fill(adj[i], Integer.MAX_VALUE);
        }
        for(int[] edge : edges){
            int pos1 = edge[0];
            int pos2 = edge[1];
            int time = edge[2];
            if(time > maxTime)
                continue;
            if(adj[pos1][pos2] == Integer.MAX_VALUE || adj[pos1][pos2] > time){
                adj[pos1][pos2] = time;
                adj[pos2][pos1] = time;
            }
        }
        
        //动态规划
        int[][] dp = new int[maxTime + 1][n];
        for(int i = 0; i <= maxTime; i++){
            Arrays.fill(dp[i], Integer.MAX_VALUE);
        }
        //初始化,任意时刻在0位置,有花费passingFees[0]
        for(int i = 0; i <= maxTime; i++){
            dp[i][0] = passingFees[0];
        }
        //dp[0][0] = passingFees[0];
        for(int t = 0; t < maxTime; t++){
            for(int i = 0; i < n; i++){
                //如果在t时刻无法到达 i 位置,那么跳过
                if(dp[t][i] == Integer.MAX_VALUE)
                    continue;
                //然后遍历当前位置所有能到达的点
                for(int pos = 0; pos < n; pos++){
                    //如果两个点不可达
                    if(adj[i][pos] == Integer.MAX_VALUE)
                        continue;
                    //如果可达
                    int time = adj[i][pos];
                    //超时了
                    if(t + time > maxTime)
                        continue;
                    //在规定时间内能到达的点,花的费用就是达到该点要的费用
                    dp[t + time][pos] = Math.min(dp[t + time][pos], dp[t][i] + passingFees[pos]);                          
                }
            }
        }
        
        //处理完后,输出
        return dp[maxTime][n - 1] == Integer.MAX_VALUE ? -1 : dp[maxTime][n - 1];
    }
}

写一下官解给的动规看行不行
这个思路减少了邻接矩阵的创建,另外转移方程就和常规转移思路一样
这里比较难理解的点是,为什么每次只遍历edges矩阵就可以保证输出是合理的。
因为这里如果不是从0位置开始走的话,dp[t][i]的值是很大的,这样就保证了结果是从0出发的

class Solution {
    public int minCost(int maxTime, int[][] edges, int[] passingFees) {
        //这道题咋说呢,感觉应该能写出来,毕竟思路很明确
        //dp[i][j]为在i时刻前到达j城市的最小花费
        //那么转移方程就应该是所有能到达j的城市x花费的最小值,dp[i][j] = min(dp[i - t][x] + cost)
        
        int n = passingFees.length;
 
        //动态规划
        int[][] dp = new int[maxTime + 1][n];
        for(int i = 0; i <= maxTime; i++){
            Arrays.fill(dp[i], Integer.MAX_VALUE / 2);
        }
        //初始化,任意时刻在0位置,有花费passingFees[0]
        for(int i = 0; i <= maxTime; i++){
            dp[i][0] = passingFees[0];
        }
        //dp[0][0] = passingFees[0];
        for(int t = 1; t <= maxTime; t++){
            //遍历所有的边
            for(int[] edge : edges){
                int pos1 = edge[0];
                int pos2 = edge[1];
                int time = edge[2];
                //时间不够跳过
                if(t < time)
                    continue;
                //从pos2到达pos1
                dp[t][pos1] = Math.min(dp[t][pos1], dp[t - time][pos2] + passingFees[pos1]);  
                //从pos1到达pos2
                dp[t][pos2] = Math.min(dp[t][pos2], dp[t - time][pos1] + passingFees[pos2]);  
            }
        }
        
        
        //处理完后,输出
        return dp[maxTime][n - 1] == Integer.MAX_VALUE / 2 ? -1 : dp[maxTime][n - 1];
    }
}

再来写迪杰斯特拉算法
迪杰斯特拉算法是基于贪心策略的,就是从原点开始扩展,找到距离原点最短距离的点,然后将这个点标记为true,意思是找到了到达这个点的最短路径,并且根据当前这个点更新邻接矩阵。下次找,还是找距离原点最近的点,重复上述步骤,直到扩展完所有点

本题就是加了一个时间,但是时间并不影响什么,只是将时间大于maxtime的话,就把这个路径舍弃。思路就是按最小的花费扩展,这里邻接矩阵必须写成哈希表的形式,写成数组还是会超时

class Solution {
    public int minCost(int maxTime, int[][] edges,

以上是关于Leetcode 第56场双周赛 / 第249场周赛 / 1411. 给 N x 3 网格图涂色的方案数(这次主要还是学动规思想和迪杰斯克拉)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode852. 山脉数组的峰顶索引 / 374. 猜数字大小 / 278. 第一个错误的版本 / 第 54 场双周赛 / 第 245 场周赛

Leetcode第187场周赛——菜鸡依旧

LeetCode第82场双周赛

LeetCode第82场双周赛

LeetCode第82场双周赛

LeetCode第69场双周赛