Leetcode 第 398 场周赛题解
Leetcode 第 398 场周赛题解
题目1:3151. 特殊数组 I
思路
遍历数组 nums,依次比较相邻元素是否奇偶性不同即可。
代码
class Solution {
public:
bool isArraySpecial(vector<int>& nums) {
if (nums.size() == 1)
return true;
int n = nums.size();
for (int i = 0; i < n - 1; i++)
if (!different(nums[i], nums[i + 1]))
return false;
return true;
}
bool different(int a, int b) { return abs(a - b) % 2; }
};
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
题目2:3152. 特殊数组 II
思路
预处理 + 前缀和。
定义一个长为 n−1 的数组 a,其中 a[i] = nums[i] % 2 == nums[i+1] % 2,那么 a 的从 to-1 到 from 的子数组和等于 0,就说明询问的子数组是特殊数组。
计算 a 的前缀和 preSum,可以快速判断子数组和是否为 0。
代码
/*
* @lc app=leetcode.cn id=3152 lang=cpp
*
* [3152] 特殊数组 II
*/
// @lc code=start
class Solution
{
public:
vector<bool> isArraySpecial(vector<int> &nums, vector<vector<int>> &queries)
{
int n = nums.size();
// 定义一个长为 n−1 的数组 a,其中 a[i] = nums[i] % 2 == nums[i+1] % 2
// 那么 a 的从 to-1 到 from 的子数组和等于 0,就说明询问的子数组是特殊数组。
// 计算 a 的前缀和 preSum,可以快速判断子数组和是否为 0
vector<int> preSum(n, 0);
for (int i = 1; i < n; i++)
{
// 相邻两数的异或和的最低位取反
int a = !((nums[i] ^ nums[i - 1]) & 1);
preSum[i] = preSum[i - 1] + a;
}
int m = queries.size();
vector<bool> ans(m);
for (int i = 0; i < m; i++)
{
int from = queries[i][0], to = queries[i][1];
ans[i] = preSum[to] - preSum[from] == 0;
}
return ans;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n+m),其中 n 是数组 nums 的长度,m 是数组 queries 的长度。
空间复杂度:O(n),其中 n 是数组 nums 的长度。
题目3:3153. 所有数对中数位不同之和
思路
遍历数组 nums, 对每一位的数字进行计数并保存在数组 cnt 中。
遍历完数组得到 cnt 后,cnt[i][j] 表示在第 i 位,数字为 j 的数的个数,那么在第 i 位,数字不为 j 的数的个数为 n−cnt[i][j],其中 n 为数组 nums 的长度。根据乘法原理可以知道,在第 i 位,一共有 cnt[i][j]∗(n−cnt[i][j])/2 对数在该位的数字不相同。累加每一位数字不相同的数对数量,即可得到所需要的答案。
代码
/*
* @lc app=leetcode.cn id=3153 lang=cpp
*
* [3153] 所有数对中数位不同之和
*/
// @lc code=start
class Solution
{
public:
long long sumDigitDifferences(vector<int> &nums)
{
int n = nums.size();
int cnt[9][10];
memset(cnt, 0, sizeof(cnt));
for (int num : nums)
{
int i = 0;
while (num)
{
cnt[i][num % 10]++;
i++;
num /= 10;
}
}
long long ans = 0LL;
for (int i = 0; i < 9; i++)
for (int j = 0; j < 10; j++)
ans += cnt[i][j] * (n - cnt[i][j]);
return ans >> 1;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(l*n+l*d),其中 n 为数组 nums 的长度,l 为数组 nums 中数据的十进制位数,l 的最大值为 9,d 为从 0 到 9 共 10 个数字。
空间复杂度:O(l*d),其中 l 为数组 nums 中数据的十进制位数,l 的最大值为 9,d 为从 0 到 9 共 10 个数字。
题目4:3154. 到达第 K 级台阶的方案数
思路
记忆化搜索。
定义 dfs(int i, int jump, bool preDown) 表示当前位于台阶 i,已经使用了 jump 次第二种操作,preDown:上一次操作是否为操作一时,到达台阶 k 的方案数。
枚举当前使用哪个操作:
- 使用操作一:前提是 i != 0 && preDown == false。使用操作一后,要解决的子问题是 dfs(i - 1, jump, true),加入返回值中。
- 使用操作二:要解决的子问题是 dfs(i + (1 << jump), jump + 1, false),加入返回值中。
- 此外,如果 i=k,可以把返回值加一。
递归边界:如果 i>k+1,由于操作一不能连续使用,无法到达 k,返回 0。
递归入口:dfs(1,0,false),即答案。
代码
/*
* @lc app=leetcode.cn id=3154 lang=cpp
*
* [3154] 到达第 K 级台阶的方案数
*/
// @lc code=start
class Solution
{
private:
unordered_map<long long, int> memo;
public:
int waysToReachStair(int k)
{
// 当前位于台阶 i,已经使用了 jump 次第二种操作,preDown:上一次操作是否为操作一
function<int(int, int, bool)> dfs = [&](int i, int jump, bool preDown) -> int
{
if (i > k + 1)
return 0;
long long state = (long long)i << 32 | jump << 1 | preDown;
if (memo.contains(state))
return memo[state];
int res = 0;
if (i == k)
res++;
if (i != 0 && preDown == false)
{ // 操作一
res += dfs(i - 1, jump, true);
}
res += dfs(i + (1 << jump), jump + 1, false); // 操作二
memo[state] = res; // 记忆化
return res;
};
// 从台阶 1 开始,一开始 jump 为 0,前一次操作不为操作二
return dfs(1, 0, false);
}
};
// @lc code=end
复杂度分析
时间复杂度:O(log2k)。
空间复杂度:O(log2k)。