自学内容网 自学内容网

Leetcode 第 415 场周赛题解

Leetcode 第 415 场周赛题解

题目1:3289. 数字小镇中的捣蛋鬼

思路

用一个哈希表记录数组 nums 中每一个元素的出现次数,出现次数为 2 的数字即为答案。

代码

/*
 * @lc app=leetcode.cn id=3289 lang=cpp
 *
 * [3289] 数字小镇中的捣蛋鬼
 */

// @lc code=start
class Solution
{
public:
    vector<int> getSneakyNumbers(vector<int> &nums)
    {
        unordered_map<int, int> cnt;
        for (int &num : nums)
            cnt[num]++;
        vector<int> ans;
        for (auto &[num, c] : cnt)
            if (c == 2)
                ans.push_back(num);
        return ans;
    }
};
// @lc code=end

复杂度分析

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

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

题目2:3290. 最高乘法得分

思路

动态规划。

设 dp[i][j] 表示从 b[0] 到 b[i-1] 选 j 个数,与 a[0] 到 a[j-1] 计算点积,结果的最大值。

考虑 b[i] 数字,分类讨论:

  • 如果不选 b[i],那么需要解决的问题为:从 b[0] 到 b[i−1] 选 j+1 个数,与 a[0] 到 a[j] 计算点积,结果的最大值,即 dp[i−1,j]。
  • 如果选 b[i],那么需要解决的问题为:从 b[0] 到 b[i−1] 选 j 个数,与 a[0] 到 a[j−1] 计算点积,结果的最大值,即 dp[i−1,j−1]+a[j]⋅b[i]。

这两种情况取最大值。

代码

/*
 * @lc app=leetcode.cn id=3290 lang=cpp
 *
 * [3290] 最高乘法得分
 */

// @lc code=start
class Solution
{
public:
    long long maxScore(vector<int> &a, vector<int> &b)
    {
        int n = b.size();
        // dp[i][j] 表示从 b[0] 到 b[i] 选 j+1 个数,与 a[0] 到 a[j] 计算点积,结果的最大值
        vector<vector<long long>> dp(n + 1, vector<long long>(5));
        // 初始化
        for (int j = 1; j < 5; j++)
            dp[0][j] = (long long)INT_MIN * 20;
        // 状态转移
        for (int i = 1; i <= n; i++)
            for (int j = 1; j < 5; j++)
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + (long long)a[j - 1] * b[i - 1]);

        return dp[n][4];
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(m * n),其中 m=4 是数组 a 的长度,n 是数组 b 的长度。

空间复杂度:O(m * n),其中 m=4 是数组 a 的长度,n 是数组 b 的长度。

题目3:3291. 形成目标字符串需要的最少字符串数 I

思路

线段树 + 动态规划。

首先对于所有的word,我们都可以使用Trie来初始化,方便查找最长前缀。

我们定义 dp[i] 为 target 前i项的答案。初始化dp[0]为0,其他均为INT_MAX。

对于每个可以到达的 dp[i] ,我们只需要考虑 target 的 [i+1,n] 区间最长可行前缀。

所以顺着前缀树,从target[i+1]开始向下,找出一个Trie中最长的与target前缀相等的前缀。我们假设找到的最长长度是len。接着,由于一个可行前缀去掉后面任意多个字符仍然可行,我们遍历 j=1~len,更新:dp[i+j]=min(dp[i+j],dp[i]+1)。

最后返回dp[n]。

代码

/*
 * @lc app=leetcode.cn id=3291 lang=cpp
 *
 * [3291] 形成目标字符串需要的最少字符串数 I
 */

// @lc code=start

// 线段树

struct TrieNode
{
    TrieNode *child[26] = {nullptr};
};
class Trie
{
public:
    TrieNode *root;
    Trie() { root = new TrieNode(); }
    void insert(string &word)
    {
        TrieNode *node = root;
        for (char &c : word)
        {
            int index = c - 'a';
            if (node->child[index] == nullptr)
                node->child[index] = new TrieNode();
            node = node->child[index];
        }
    }
    vector<int> search(string &target, int pos)
    {
        TrieNode *node = root;
        vector<int> pres;
        for (int i = pos; i < target.size(); i++)
        {
            int index = target[i] - 'a';
            if (node->child[index] == nullptr)
                break;
            node = node->child[index];
            pres.push_back(i - pos + 1);
        }
        return pres;
    }
};

class Solution
{
public:
    int minValidStrings(vector<string> &words, string target)
    {
        Trie trie;
        // 将所有单词插入前缀树
        for (string &word : words)
            trie.insert(word);

        int n = target.size();
        vector<int> dp(n + 1, INT_MAX);
        // 初始化
        dp[0] = 0;
        // 状态转移
        for (int i = 0; i < n; i++)
        {
            if (dp[i] == INT_MAX)
                continue;
            vector<int> pres = trie.search(target, i);
            for (int len : pres)
                dp[i + len] = min(dp[i + len], dp[i] + 1);
        }

        return dp[n] == INT_MAX ? -1 : dp[n];
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(n2),其中 n 是字符串 target 的长度。

空间复杂度:O(L * U),其中 L 是 words 中所有字符串的长度之和,U = 26 是小写字母的个数。

题目4:3292. 形成目标字符串需要的最少字符串数 II

思路

题解:https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii/solutions/2917929/ac-zi-dong-ji-pythonjavacgo-by-endlessch-hcqk/

字符串哈希 + 动态规划。

用线段树处理 words,这个方法在题目 4 会超时。

一般地,对于每个 i,都计算一个最大的 leni,满足从 target[i] 开始的长为 leni 的子串是某个 words[i] 的前缀。

预处理每个 words[i] 的每个前缀的字符串哈希值,按照前缀长度分组,保存到不同的集合中。每个集合保存的是相同前缀长度的哈希值。

对于 i,二分 leni,每次 check 只需要看子串哈希值是否在集合中。

代码

/*
 * @lc app=leetcode.cn id=3292 lang=cpp
 *
 * [3292] 形成目标字符串需要的最少字符串数 II
 */

// @lc code=start
class Solution
{
public:
    int minValidStrings(const vector<string> &words, const string &target)
    {
        int n = target.length();

        // 多项式字符串哈希(方便计算子串哈希值)
        // 哈希函数 hash(s) = s[0] * base^(n-1) + s[1] * base^(n-2) + ... + s[n-2] * base + s[n-1]
        const int MOD = 1'070'777'777;
        mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
        const int BASE = uniform_int_distribution<>(8e8, 9e8)(rng); // 随机 base,防止 hack
        vector<int> pow_base(n + 1);                                // pow_base[i] = base^i
        vector<int> pre_hash(n + 1);                                // 前缀哈希值 pre_hash[i] = hash(s[:i])
        pow_base[0] = 1;
        for (int i = 0; i < n; i++)
        {
            pow_base[i + 1] = (long long)pow_base[i] * BASE % MOD;
            pre_hash[i + 1] = ((long long)pre_hash[i] * BASE + target[i]) % MOD; // 秦九韶算法计算多项式哈希
        }
        // 计算 target[l] 到 target[r-1] 的哈希值
        auto sub_hash = [&](int l, int r)
        {
            return ((pre_hash[r] - (long long)pre_hash[l] * pow_base[r - l]) % MOD + MOD) % MOD;
        };

        int max_len = 0;
        for (auto &w : words)
        {
            max_len = max(max_len, (int)w.length());
        }
        vector<unordered_set<int>> sets(max_len);
        for (auto &w : words)
        {
            long long h = 0;
            for (int j = 0; j < w.size(); j++)
            {
                h = (h * BASE + w[j]) % MOD;
                sets[j].insert(h); // 注意 j 从 0 开始
            }
        }
        auto calc_sz = [&](int i) -> int
        {
            // 开区间二分,left 一定满足要求,right 一定不满足要求
            int left = 0, right = min(n - i, max_len) + 1;
            while (left + 1 < right)
            {
                int mid = (left + right) / 2;
                (sets[mid - 1].contains(sub_hash(i, i + mid)) ? left : right) = mid;
            }
            return left;
        };

        int ans = 0;
        int cur_r = 0; // 已建造的桥的右端点
        int nxt_r = 0; // 下一座桥的右端点的最大值
        for (int i = 0; i < n; i++)
        {
            int sz = calc_sz(i);
            nxt_r = max(nxt_r, i + sz);
            if (i == cur_r)
            { // 到达已建造的桥的右端点
                if (i == nxt_r)
                { // 无论怎么造桥,都无法从 i 到 i+1
                    return -1;
                }
                cur_r = nxt_r; // 造一座桥
                ans++;
            }
        }
        return ans;
    }
};
// @lc code=end

复杂度分析

时间复杂度:O(L+nlogn),其中 n 是字符串 target 的长度,L 是 words 中所有字符串的长度之和。

空间复杂度:O(L+n)其中 n 是字符串 target 的长度,L 是 words 中所有字符串的长度之和。


原文地址:https://blog.csdn.net/ProgramNovice/article/details/142470595

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!