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 场周赛