Leetcode 第 422 场周赛题解
- Leetcode 第 422 场周赛题解
- 题目1:3340. 检查平衡字符串
- 思路
- 代码
- 复杂度分析
- 题目2:3341. 到达最后一个房间的最少时间 I
- 思路
- 代码
- 复杂度分析
- 题目3:3342. 到达最后一个房间的最少时间 II
- 思路
- 代码
- 复杂度分析
- 题目4:3343. 统计平衡排列的数目
- 思路
- 代码
- 复杂度分析
Leetcode 第 422 场周赛题解
题目1:3340. 检查平衡字符串
思路
模拟。
代码
/*
* @lc app=leetcode.cn id=3340 lang=cpp
*
* [3340] 检查平衡字符串
*/
// @lc code=start
class Solution
{
public:
bool isBalanced(string num)
{
int oddSum = 0, evenSum = 0;
for (int i = 0; i < num.length(); i++)
(i % 2 ? oddSum : evenSum) += (num[i] - '0');
return oddSum == evenSum;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是字符串 num 的长度。
空间复杂度:O(1)。
题目2:3341. 到达最后一个房间的最少时间 I
思路
广度优先搜索。
一个节点的最短路径可能来自于上下左右,单纯用BFS会导致重复访问节点,造成死循环问题。为此,使用一个distance[m][n]的数组来记录从(0,0)节点到当前节点(i,j)的最短路径,当重复访问节点时,只有在当前最小值被更新时,才将这个节点加入队列(相当于更新操作,重新从该点出发去找寻到达最终节点的最短路径),否则不加入队列,这样就不会造成死循环问题。
代码
/*
* @lc app=leetcode.cn id=3341 lang=cpp
*
* [3341] 到达最后一个房间的最少时间 I
*/
// @lc code=start
class Solution
{
private:
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
public:
int minTimeToReach(vector<vector<int>> &moveTime)
{
int m = moveTime.size(), n = m ? moveTime[0].size() : 0;
// distance[i][j]记录从节点(0,0)到当前节点(i,j)的最短路径
vector<vector<int>> distance(m, vector<int>(n, INT_MAX / 2));
queue<pair<int, int>> q;
int ans = INT_MAX;
q.push({0, 0});
distance[0][0] = 0;
while (!q.empty())
{
auto t = q.front();
q.pop();
int x = t.first / n, y = t.first % n;
if (x == m - 1 && y == n - 1)
ans = min(ans, t.second);
for (int i = 0; i < 4; i++)
{
int r = x + dx[i], c = y + dy[i];
if (r >= 0 && r < m && c >= 0 && c < n)
{
if (moveTime[r][c] >= distance[x][y])
{
if (distance[r][c] <= moveTime[r][c] + 1)
continue;
distance[r][c] = moveTime[r][c] + 1;
q.push({r * n + c, distance[r][c]});
}
else
{
if (distance[r][c] <= distance[x][y] + 1)
continue;
distance[r][c] = distance[x][y] + 1;
q.push({r * n + c, distance[r][c]});
}
}
}
}
return ans;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(m * n),其中 m 和 n 分别表示二维数组 moveTime 的长度和宽度。
空间复杂度:O(m * n),其中 m 和 n 分别表示二维数组 moveTime 的长度和宽度。
题目3:3342. 到达最后一个房间的最少时间 II
思路
题目要计算从左上角到右下角的最短路,这可以用 Dijkstra 算法解决。
设从起点走到 (i,j) 的最短路为 distance[i][j]。
那么从 (i,j) 走到相邻格子 (x,y),到达 (x,y) 的时间为 max(dis[i][j],moveTime[x][y])+time。
对于本题,由于每走一步 time 都会在 1,2 之间变化,联系国际象棋棋盘,(i+j) 的奇偶性就决定了 time,即 time=(i+j)mod2+1。
由于一定可以从起点走到终点,我们可以在循环中判断,只要出堆的点是终点,就立刻返回 dis[m−1][n−1]。
代码
/*
* @lc app=leetcode.cn id=3342 lang=cpp
*
* [3342] 到达最后一个房间的最少时间 II
*/
// @lc code=start
class Solution
{
private:
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
public:
int minTimeToReach(vector<vector<int>> &moveTime)
{
int m = moveTime.size(), n = m ? moveTime[0].size() : 0;
// distance[i][j]记录从节点(0,0)到当前节点(i,j)的最短路径
vector<vector<int>> distance(m, vector<int>(n, INT_MAX));
priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<>> pq;
pq.emplace(0, 0, 0);
distance[0][0] = 0;
while (1)
{
auto [dis, x, y] = pq.top();
pq.pop();
if (x == m - 1 && y == n - 1)
return dis;
if (dis > distance[x][y])
continue;
for (int i = 0; i < 4; i++)
{
int r = x + dx[i], c = y + dy[i];
if (r >= 0 && r < m && c >= 0 && c < n)
{
int new_dis = max(dis, moveTime[r][c]) + (x + y) % 2 + 1;
if (new_dis < distance[r][c])
{
distance[r][c] = new_dis;
pq.emplace(new_dis, r, c);
}
}
}
}
}
};
// @lc code=end
复杂度分析
时间复杂度:O(m * n * log(m * n)),其中 m 和 n 分别表示二维数组 moveTime 的长度和宽度。
空间复杂度:O(m * n),其中 m 和 n 分别表示二维数组 moveTime 的长度和宽度。
题目4:3343. 统计平衡排列的数目
思路
多重集排列数 + 计数 DP。
题解:https://leetcode.cn/problems/count-number-of-balanced-permutations/solutions/2975507/duo-zhong-ji-pai-lie-shu-ji-shu-dppython-42ky/
代码
/*
* @lc app=leetcode.cn id=3343 lang=cpp
*
* [3343] 统计平衡排列的数目
*/
// @lc code=start
const int MOD = 1'000'000'007;
const int MX = 41;
long long F[MX]; // F[i] = i!
long long INV_F[MX]; // INV_F[i] = i!^-1
long long pow(long long x, int n)
{
long long res = 1;
for (; n; n /= 2)
{
if (n % 2)
{
res = res * x % MOD;
}
x = x * x % MOD;
}
return res;
}
auto init = []
{
F[0] = 1;
for (int i = 1; i < MX; i++)
{
F[i] = F[i - 1] * i % MOD;
}
INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);
for (int i = MX - 1; i; i--)
{
INV_F[i - 1] = INV_F[i] * i % MOD;
}
return 0;
}();
class Solution
{
public:
int countBalancedPermutations(string num)
{
int cnt[10];
int total = 0;
for (char c : num)
{
cnt[c - '0']++;
total += c - '0';
}
if (total % 2)
return 0;
// 原地求前缀和
partial_sum(cnt, cnt + 10, cnt);
int n = num.size(), n1 = n / 2;
vector<vector<vector<int>>> memo(10, vector(n1 + 1, vector<int>(total / 2 + 1, -1))); // -1 表示没有计算过
auto dfs = [&](auto &dfs, int i, int left1, int left_s) -> int
{
if (i < 0)
{
return left_s == 0;
}
int &res = memo[i][left1][left_s]; // 注意这里是引用
if (res != -1)
{ // 之前计算过
return res;
}
res = 0;
int c = cnt[i] - (i ? cnt[i - 1] : 0);
int left2 = cnt[i] - left1;
for (int k = max(c - left2, 0); k <= min(c, left1) && k * i <= left_s; k++)
{
int r = dfs(dfs, i - 1, left1 - k, left_s - k * i);
res = (res + r * INV_F[k] % MOD * INV_F[c - k]) % MOD;
}
return res;
};
return F[n1] * F[n - n1] % MOD * dfs(dfs, 9, n1, total / 2) % MOD;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n2S),其中 n 为字符串 num 的长度,S 为字符串 num 的数字和的一半,这不超过 9n/2。注意本题要把状态 i 和枚举 k 结合起来看,这二者一起是 O(n) 的。
空间复杂度:O(DnS),其中 D=10。保存多少状态,就需要多少空间。