Leetcode 第 377 场周赛题解

Leetcode 第 377 场周赛题解

题目1:2974. 最小数字游戏

思路

排序。

将数组 nums 从小到大排序。

每一轮,Alice 先从 nums 中移除一个最小元素,然后 Bob 执行同样的操作。接着,Bob 会将移除的元素添加到数组 arr 中,然后 Alice 也执行同样的操作。

对排序后的数组的每两个元素做交换,再到下一组两个元素,直至数组末尾。

代码

/*
 * @lc app=leetcode.cn id=2974 lang=cpp
 *
 * [2974] 最小数字游戏
 */

// @lc code=start
class Solution
{
public:
    vector<int> numberGame(vector<int> &nums)
    {
        // 特判
        if (nums.empty())
            return {};
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i += 2)
            swap(nums[i], nums[i + 1]);
        return nums;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(nlogn),其中 n 是数组 nums 的长度。

空间复杂度:O(1)。

题目2:2975. 移除栅栏得到的正方形田地的最大面积

思路

哈希 + 暴力枚举。

水平栅栏和垂直栅栏分开计算。

  • 对于水平栅栏,计算出任意两个栅栏之间的距离,存到一个哈希表 hDistances 中。
  • 对于垂直栅栏,计算出任意两个栅栏之间的距离,存到一个哈希表 vDistances 中。

答案就是 hDistances 和 vDistances 交集中的最大值的平方。如果不存在最大值,返回 −1。

代码

/*
 * @lc app=leetcode.cn id=2975 lang=cpp
 *
 * [2975] 移除栅栏得到的正方形田地的最大面积
 */

// @lc code=start
class Solution
{
private:
    const int MOD = 1e9 + 7;

public:
    int maximizeSquareArea(int m, int n, vector<int> &hFences, vector<int> &vFences)
    {
        // 特判
        if (m == n)
            return (long long)(m - 1) * (n - 1);
        if (hFences.empty() || vFences.empty())
            return pow(min(m, n) - 1, 2);
        // 预处理
        hFences.push_back(1), hFences.push_back(m);
        vFences.push_back(1), vFences.push_back(n);
        // 得到距离
        unordered_set<int> hDistances = calDistances(hFences);
        unordered_set<int> vDistances = calDistances(vFences);
        // 计算正方形田地的最大面积
        int max_len = 0;
        for (int h_distance : hDistances)
            if (vDistances.contains(h_distance))
                max_len = max(max_len, h_distance);
        return max_len == 0 ? -1 : (long long)max_len * max_len % MOD;
    }
    // 辅函数 - 计算任意两个栅栏之间的距离
    unordered_set<int> calDistances(vector<int> &fences)
    {
        unordered_set<int> distances;
        sort(fences.begin(), fences.end());
        for (int i = 0; i < fences.size() - 1; i++)
            for (int j = i + 1; j < fences.size(); j++)
                distances.insert(fences[j] - fences[i]);
        return distances;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(h2+v2),其中 h 是数组 hFences 的长度,v 是数组 hFences 的长度。

空间复杂度:O(h2+v2),其中 h 是数组 hFences 的长度,v 是数组 hFences 的长度。

题目3:2976. 转换字符串的最小成本 I

思路

建图,从 original[i] 向 changed[i] 连边,边权为 cost[i]。没边的边权设为 INF。

然后用 Floyd 算法求图中任意两点最短路,得到 g 矩阵。这里得到的 g[i][j] 表示字母 i 通过若干次替换操作变成字母 j 的最小成本。

最后累加所有 g[original[i]],即为答案。如果中间遇到边权为 INF,说明无法转换,返回 −1。

代码

/*
 * @lc app=leetcode.cn id=2976 lang=cpp
 *
 * [2976] 转换字符串的最小成本 I
 */

// @lc code=start
class Solution
{
private:
    const int INF = 1e9;

public:
    long long minimumCost(string source, string target, vector<char> &original, vector<char> &changed, vector<int> &cost)
    {
        // 邻接矩阵
        vector<vector<long long>> g(26, vector<long long>(26, INF));
        // 初始化
        for (int i = 0; i < 26; i++)
            g[i][i] = 0;
        // 建图
        for (int i = 0; i < original.size(); i++)
        {
            int x = original[i] - 'a', y = changed[i] - 'a';
            g[x][y] = min(g[x][y], (long long)cost[i]);
        }
        // Floyd 算法求最短路
        for (int k = 0; k < 26; k++)
            for (int i = 0; i < 26; i++)
                for (int j = 0; j < 26; j++)
                    g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
        // 计算最小成本
        long long minCost = 0;
        for (int i = 0; i < source.size(); i++)
        {
            int x = source[i] - 'a', y = target[i] - 'a';
            if (x != y)
            {
                // x 不能变成 y,无解
                if (g[x][y] >= INF)
                    return -1;
                // 否则答案增加把 x 改成 y 的最小代价
                minCost += g[x][y];
            }
        }
        return minCost;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(n+m+∣Σ∣3),其中 n 为字符串 source/target 的长度,m 为数组 original/changed/cost 的长度,∣Σ∣ 为字符集合的大小,本题中字符均为小写字母,所以 ∣Σ∣=26。

空间复杂度:O(∣Σ∣2),其中 ∣Σ∣ 为字符集合的大小,本题中字符均为小写字母,所以 ∣Σ∣=26。

题目4:2977. 转换字符串的最小成本 II

思路

  1. 把每个字符串转换成一个整数编号,这一步可以用字典树完成。
  2. 建图,从 original[i] 向 changed[i] 连边,边权为 cost[i]。
  3. 用 Floyd 算法求图中任意两点最短路,得到 dis 矩阵。这里得到的 dis[i][j] 表示编号为 i 的子串,通过若干次替换操作变成编号为 j 的子串的最小成本。
  4. 动态规划。定义 dp[i] 表示从 source[i] 开始向后修改的最小成本。
  5. 如果 source[i]=target[i],可以不修改,dp[i]=dp[i+1]。
  6. 也可以从 source[i] 开始向后修改,利用字典树快速判断 source 和 target 的下标从 i 到 j 的子串是否在 original 和 changed 中,如果在就用 dis[x][y]+dp[j+1] 更新 dp[i] 的最小值,其中 x 和 y 分别是 source和 target 的这段子串对应的编号。
  7. dp[0] 即为答案。如果答案是无穷大则返回 −1。

代码

/*
 * @lc app=leetcode.cn id=2977 lang=cpp
 *
 * [2977] 转换字符串的最小成本 II
 */

// @lc code=start
struct Node
{
    Node *son[26]{};
    int sid = -1; // 字符串的编号
};

class Solution
{
public:
    long long
    minimumCost(string source, string target, vector<string> &original, vector<string> &changed, vector<int> &cost)
    {
        Node *root = new Node();
        int sid = 0;
        auto put = [&](string &s) -> int
        {
            Node *o = root;
            for (char b : s)
            {
                int i = b - 'a';
                if (o->son[i] == nullptr)
                    o->son[i] = new Node();
                o = o->son[i];
            }
            if (o->sid < 0)
                o->sid = sid++;
            return o->sid;
        };

        // 初始化距离矩阵
        int m = cost.size();
        vector<vector<int>> dis(m * 2, vector<int>(m * 2, INT_MAX / 2));
        for (int i = 0; i < m * 2; i++)
            dis[i][i] = 0;
        for (int i = 0; i < m; i++)
        {
            int x = put(original[i]);
            int y = put(changed[i]);
            dis[x][y] = min(dis[x][y], cost[i]);
        }

        // Floyd 求任意两点最短路
        for (int k = 0; k < sid; k++)
        {
            for (int i = 0; i < sid; i++)
            {
                if (dis[i][k] == INT_MAX / 2) // 加上这句话,巨大优化!
                    continue;
                for (int j = 0; j < sid; j++)
                {
                    dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
                }
            }
        }

        int n = source.size();
        vector<long long> dp(n + 1);
        for (int i = n - 1; i >= 0; i--)
        {
            // 不修改 source[i]
            dp[i] = source[i] == target[i] ? dp[i + 1] : LONG_LONG_MAX / 2;
            Node *p = root, *q = root;
            for (int j = i; j < n; j++)
            {
                p = p->son[source[j] - 'a'];
                q = q->son[target[j] - 'a'];
                if (p == nullptr || q == nullptr)
                    break;
                if (p->sid < 0 || q->sid < 0)
                    continue;
                // 修改从 i 到 j 的这一段
                int d = dis[p->sid][q->sid];
                if (d < INT_MAX / 2)
                    dp[i] = min(dp[i], dis[p->sid][q->sid] + dp[j + 1]);
            }
        }
        return dp[0] < LONG_LONG_MAX / 2 ? dp[0] : -1;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(n2+mn+m3),其中 n 是字符串 source 的长度,m 是数组 cost 的长度。动态规划需要 O(n2) 的时间,把 2m 个长度至多为 n 的字符串插入字典树需要 O(mn) 的时间,Floyd 算法需要 O(m3) 的时间。

空间复杂度:O(n+mn+m2),其中 n 是字符串 source 的长度,m 是数组 cost 的长度。动态规划需要 O(n) 的空间,把 2m 个长度至多为 n 的字符串插入字典树需要 O(mn) 的空间,Floyd 算法需要 O(m2)的空间。

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值