• LeetCode 第415场周赛个人题解


    目录

    Q1. 数字小镇中的捣蛋鬼

    原题链接

    思路分析

    AC代码

    Q2. 最高乘法得分

    原题链接

    思路分析

    AC代码

    Q3. 形成目标字符串需要的最少字符串数 I

    原题链接

    思路分析

    AC代码

    Q4. 形成目标字符串需要的最少字符串数 II

    原题链接

    思路分析

    AC代码


    Q1. 数字小镇中的捣蛋鬼

    原题链接

    Q1. 数字小镇中的捣蛋鬼

    思路分析

    签到题

    AC代码

    1. class Solution:
    2. def getSneakyNumbers(self, nums: List[int]) -> List[int]:
    3. cnt = Counter(nums)
    4. return [x for x in set(nums) if cnt[x] == 2]

    Q2. 最高乘法得分

    原题链接

    Q2. 最高乘法得分

    思路分析

    划分型dp

    定义状态f(i, j) 为 b 前 i 个数 和 a 前 j 个数匹配的最大得分

    那么 f(i + 1, j) = max{f(i, j), f(i, j - 1) + a[j - 1] * b[j]}

    时间复杂度O(4len(b))

    AC代码

    1. class Solution:
    2. def maxScore(self, a: list[int], b: list[int]) -> int:
    3. n = len(b)
    4. f = [[-inf] * 5 for _ in range(n + 1)]
    5. f[0][0] = 0
    6. for i in range(n):
    7. f[i + 1][0] = 0
    8. for j in range(1, 5):
    9. f[i + 1][j] = max(f[i + 1][j], f[i][j - 1] + a[j - 1] * b[i], f[i][j])
    10. return max(f[i][4] for i in range(n + 1))

    Q3. 形成目标字符串需要的最少字符串数 I

    原题链接

    Q3. 形成目标字符串需要的最少字符串数 I

    思路分析

    见Q3

    AC代码

    1. auto FIO = []{
    2. std::ios::sync_with_stdio(false);
    3. std::cin.tie(nullptr);
    4. return 0;
    5. }();
    6. constexpr int inf = 1e9;
    7. struct AhoCorasick {
    8. static constexpr int ALPHABET = 26;
    9. struct Node {
    10. int len = 0;
    11. int fail = 0;
    12. array<int, ALPHABET> son;
    13. int last = 0;
    14. Node() : son{} {}
    15. };
    16. vector tr;
    17. AhoCorasick() {
    18. init();
    19. }
    20. void init() {
    21. tr.reserve(5000);
    22. tr.assign(2, Node());
    23. tr[0].son.fill(1);
    24. tr[1].last = 1;
    25. }
    26. int newNode() {
    27. tr.emplace_back();
    28. return tr.size() - 1;
    29. }
    30. void add(const string& s) {
    31. int p = 1, i = 1;
    32. for (char ch : s) {
    33. int x = ch - 'a';
    34. if (!tr[p].son[x])
    35. tr[p].son[x] = newNode();
    36. p = tr[p].son[x];
    37. tr[p].len = i ++;
    38. }
    39. }
    40. void build() {
    41. queue<int> q;
    42. q.push(1);
    43. while (q.size()) {
    44. int x = q.front();
    45. q.pop();
    46. for (int i = 0; i < ALPHABET; ++ i) {
    47. if (!tr[x].son[i]) {
    48. tr[x].son[i] = tr[tr[x].fail].son[i];
    49. continue;
    50. }
    51. tr[tr[x].son[i]].fail = tr[tr[x].fail].son[i];
    52. tr[tr[x].son[i]].last = tr[tr[tr[x].son[i]].fail].len ?
    53. tr[tr[x].son[i]].fail : tr[tr[tr[x].son[i]].fail].last;
    54. q.push(tr[x].son[i]);
    55. }
    56. }
    57. }
    58. int son(int p, int x) {
    59. return tr[p].son[x];
    60. }
    61. int fail(int p) {
    62. return tr[p].fail;
    63. }
    64. int last(int p) {
    65. return tr[p].last;
    66. }
    67. int len(int p) {
    68. return tr[p].len;
    69. }
    70. int size() {
    71. return tr.size();
    72. }
    73. };
    74. class Solution {
    75. public:
    76. int minValidStrings(vector& words, string target) {
    77. int m = words.size(), n = target.size();
    78. AhoCorasick ac;
    79. for (auto &s : words)
    80. ac.add(s);
    81. ac.build();
    82. vector<int> f(n + 1, inf);
    83. f[0] = 0;
    84. int cur = 1;
    85. for (int i = 1; i <= n; ++ i) {
    86. cur = ac.son(cur, target[i - 1] - 'a');
    87. if (ac.len(cur))
    88. f[i] = min(f[i], f[i - ac.len(cur)] + 1);
    89. }
    90. return f[n] == inf ? -1 : f[n];
    91. }
    92. };

    Q4. 形成目标字符串需要的最少字符串数 II

    原题链接

    Q4. 形成目标字符串需要的最少字符串数 II

    思路分析

    AC自动机+dp

    一个很暴力的思路就是把所有前缀都存AC自动机里面,然后在AC自动机上匹配target来进行dp

    3213. 最小代价构造字符串做法一样

    但是存所有前缀会爆掉,我们修改一下AC自动机的插入,对于一个单词的插入,路径上的节点都是一个单词结尾

    这样我们看似只插入了每个单词,实际上我们插入了每个单词的所有前缀

    定义状态 f(i) 为 构造target 前 i 个字符所需最少字符串数

    当前匹配到i,对应自动机节点为cur,那么转移方程为:

    f(i) = min{f[i], f[i - ac.len(cur)] + 1}

    为什么呢?不应该匹配所有前缀吗?

    事实上,如果target可以构造,事实上存在合法构造,下图一定存在若干不相交的线段覆盖整个区间那么我们把target当成一个文本串,我们可以在上面匹配出所有target包含的是自动机中单词的子串,而我们每次跳fail指针,都是跳最长后缀

    如果存在多组覆盖整个区间的不相交线段组,我们一定会走线段数最少的那一条

    事实上,多组合法覆盖一定是相交的,我们假如正走在一条非最优解路径,到达一条线段的结束的时候我们自然会往最优解路径的方向去跳

    这个过程可以手玩一下来加深理解

    时间复杂度O(Σlen(words[i]))

    AC代码

    1. auto FIO = []{
    2. std::ios::sync_with_stdio(false);
    3. std::cin.tie(nullptr);
    4. return 0;
    5. }();
    6. constexpr int inf = 1e9;
    7. struct AhoCorasick {
    8. static constexpr int ALPHABET = 26;
    9. struct Node {
    10. int len = 0;
    11. int fail = 0;
    12. array<int, ALPHABET> son;
    13. int last = 0;
    14. Node() : son{} {}
    15. };
    16. vector tr;
    17. AhoCorasick() {
    18. init();
    19. }
    20. void init() {
    21. tr.reserve(5000);
    22. tr.assign(2, Node());
    23. tr[0].son.fill(1);
    24. tr[1].last = 1;
    25. }
    26. int newNode() {
    27. tr.emplace_back();
    28. return tr.size() - 1;
    29. }
    30. void add(const string& s) {
    31. int p = 1, i = 1;
    32. for (char ch : s) {
    33. int x = ch - 'a';
    34. if (!tr[p].son[x])
    35. tr[p].son[x] = newNode();
    36. p = tr[p].son[x];
    37. tr[p].len = i ++;
    38. }
    39. }
    40. void build() {
    41. queue<int> q;
    42. q.push(1);
    43. while (q.size()) {
    44. int x = q.front();
    45. q.pop();
    46. for (int i = 0; i < ALPHABET; ++ i) {
    47. if (!tr[x].son[i]) {
    48. tr[x].son[i] = tr[tr[x].fail].son[i];
    49. continue;
    50. }
    51. tr[tr[x].son[i]].fail = tr[tr[x].fail].son[i];
    52. tr[tr[x].son[i]].last = tr[tr[tr[x].son[i]].fail].len ?
    53. tr[tr[x].son[i]].fail : tr[tr[tr[x].son[i]].fail].last;
    54. q.push(tr[x].son[i]);
    55. }
    56. }
    57. }
    58. int son(int p, int x) {
    59. return tr[p].son[x];
    60. }
    61. int fail(int p) {
    62. return tr[p].fail;
    63. }
    64. int last(int p) {
    65. return tr[p].last;
    66. }
    67. int len(int p) {
    68. return tr[p].len;
    69. }
    70. int size() {
    71. return tr.size();
    72. }
    73. };
    74. class Solution {
    75. public:
    76. int minValidStrings(vector& words, string target) {
    77. int m = words.size(), n = target.size();
    78. AhoCorasick ac;
    79. for (auto &s : words)
    80. ac.add(s);
    81. ac.build();
    82. vector<int> f(n + 1, inf);
    83. f[0] = 0;
    84. int cur = 1;
    85. for (int i = 1; i <= n; ++ i) {
    86. cur = ac.son(cur, target[i - 1] - 'a');
    87. if (ac.len(cur))
    88. f[i] = min(f[i], f[i - ac.len(cur)] + 1);
    89. }
    90. return f[n] == inf ? -1 : f[n];
    91. }
    92. };

  • 相关阅读:
    面试题 17.08. 马戏团人塔
    Java毕设项目——超市POS收银管理系统(java+SSM+Maven+Mysql+Jsp)
    移动端h5网页、微信网页调试之利用vConsole真机调试+显示控制台打印信息、调试接口(附带vue项目里的具体使用方法)
    解析分布式系统的缓存设计
    Docker简介
    爬虫(6) - 网页数据解析(2) | BeautifulSoup4在爬虫中的使用
    后端每日一题 1:说一下三次握手
    干货 | 每日十道Java集合面试题
    [山东科技大学OJ]1226 Problem B: 寻求勾股数
    透过等待看数据库
  • 原文地址:https://blog.csdn.net/EQUINOX1/article/details/142281886