小红的字符串生成
题目描述
小红拿到了一个空字符串 s s s。她有以下两种操作:
- 将任意一个字母添加在 s s s 的末尾。
- 选择 s s s 的一个长度不小于 2 2 2 的连续子串,复制下来添加到 s s s 的末尾。
小红希望用空串 s s s 生成一个给定的字符串 s s s,她想知道有多少种不同的生成方式?由于答案可能过大,请对 1 0 9 + 7 10^9+7 109+7 取模。
数据范围
字符串 t t t 仅包含小写字母,且长度不超过 300 300 300。
解题思路
令 d p [ i ] dp[i] dp[i] = s [ i . . n ] s[i..n] s[i..n] 的生成方式的数量,考虑如何生成 s [ i − 1.. n ] s[i-1..n] s[i−1..n]
- 若 s [ i ] s[i] s[i] 来自添加,则 d p [ i ] = d p [ i + 1 ] dp[i] = dp[i+1] dp[i]=dp[i+1]。
- 若 s [ i . . i + 1 ] s[i..i+1] s[i..i+1] 来自复制,则 d p [ i ] dp[i] dp[i] = s [ 1.. i − 1 ] s[1..i-1] s[1..i−1] 中包含 s [ i . . i + 1 ] s[i..i+1] s[i..i+1] 的数量 × \times × d p [ i + 2 ] dp[i+2] dp[i+2]。
- 若 s [ i . . i + 2 ] s[i..i+2] s[i..i+2] 来自复制,则 d p [ i ] dp[i] dp[i] = s [ 1.. i − 1 ] s[1..i-1] s[1..i−1] 中包含 s [ i . . i + 2 ] s[i..i+2] s[i..i+2] 的数量 × × × d p [ i + 3 ] dp[i+3] dp[i+3]。
- 以此类推。
- d p [ 1 ] dp[1] dp[1] = s [ 1.. n ] s[1..n] s[1..n] 的生成方式的数量,即 s s s 的生成方式的数量,也即答案。
两点需要注意:
- 若 s [ i . . i − 1 ] s[i..i-1] s[i..i−1] 包含 s [ i . . i + x ] s[i..i+x] s[i..i+x],则也包含 s [ i . . i + x − 1 ] s[i..i+x-1] s[i..i+x−1] ,因此,可依次判断 s [ i . . i − 1 ] s[i..i-1] s[i..i−1] 是否包含 s [ i . . i + 1 ] , s [ i . . i + 2 , s [ i . . i + 3 ] , . . . s[i..i+1],s[i..i+2,s[i..i+3],... s[i..i+1],s[i..i+2,s[i..i+3],...。
- s [ i . . i − 1 ] s[i..i-1] s[i..i−1] 可能包含多个 s [ i . . i + x ] s[i..i+x] s[i..i+x]。
代码实现
typedef long long ll;
const int N = 3e2 + 5, MOD = 1e9 + 7;
// dp[i] = s[0..n-1] 的生成方式的数量
ll dp[N];
int cntOfMethod(string t) {
int n = t.size();
// 生成dp[n-1..n-1]的方式有1种
// dp[n]=1作为边界值
dp[n - 1] = dp[n] = 1;
for (int i = n - 2; i >= 0; i--) {
// 添加任意字母
dp[i] = dp[i + 1];
// 复制连续子串
auto pre = t.substr(0, i), sub = t.substr(i, 2);
int st = -1;
// pre 中可能包含多个 sub
while ((st = pre.find(sub, st + 1)) != -1) {
// 依次判断是否包含 s[i..i+1],s[i..i+2]...
for (int j = 1; st + j < i && i + j < n; j++) {
if (pre[st + j] != t[i + j])break;
dp[i] += dp[i + j + 1];
}
}
dp[i] %= MOD;
}
return (int) dp[0];
}
时间复杂度: O ( n 3 ) O(n^3) O(n3)。
空间复杂度: O ( n ) O(n) O(n)。
小红的二叉树拼接
题目描述
小红拿到了 n n n 个二叉树,她准备把这些二叉树拼接起来。
拼接的方式是:选择一个二叉树 a a a 的一个叶子,将二叉树 b b b 的根作为该叶子的左儿子或者右儿子。这样就把 a a a 和 b b b 拼接起来了。
小红希望最终将这 n n n 个二叉树拼接成一个二叉树,需要满足最终二叉树的高度尽可能大。小红想知道,有多少种不同的拼接方式?由于答案可能过大,请对 1 0 9 + 7 10^9+7 109+7 取模。
数据范围
所有二叉树的节点数量之和不超过 200000 200000 200000。
每个二叉树至少有一个节点。
每个节点的权值随机。
$ 1 \leq n \leq 200000$。
解题思路
-
对于两个二叉树 a , b a,b a,b,如何拼接会使得高度尽可能大?必定是将一个二叉树的根节点拼接到另一个二叉树的最底一层节点上,最低一层的节点必为叶子节点。由此可知,拼接方式的数量取决于最低一层的叶子的数量。
-
考虑 n n n 个二叉树,不妨编号为 t 1 t_1 t1 到 t n t_n tn,设 t i t_i ti 最低一层有 x i x_i xi 个叶子节点,由于可以作为左、右孩子,令 y i = x i × 2 y_i=x_i \times 2 yi=xi×2,令 s u m = y 1 × y 2 × . . . × y n sum = y_1 \times y_2 \times ... \times y_n sum=y1×y2×...×yn。不妨先考虑当 t 1 t_1 t1 是拼接后得到的二叉树中最底下的二叉树的情况。
t 2 t_2 t2 到 t n t_n tn 共有 A ( n − 1 , n − 1 ) A(n-1,n-1) A(n−1,n−1) 种排列方式,对于每种排列方式,共有 x 2 × x 3 × . . . × x ( n − 1 ) x_2 \times x_3 \times ... \times x_{(n-1)} x2×x3×...×x(n−1) 种拼接方式,也即有 s u m / y 1 sum / y_1 sum/y1 种排列方式, 故,此种情况下,共有 A ( n − 1 , n − 1 ) × s u m / y 1 A(n-1,n-1) \times sum /y_1 A(n−1,n−1)×sum/y1 种排列方式。
-
由上述分析可知, n n n 个二叉树共有如下数量的排列方式:
r e s = A ( n − 1 , n − 1 ) × s u m × ( 1 / y 1 + 1 / y 2 + . . . + 1 / y n ) res = A(n-1,n-1) \times sum \times (1/y_1 + 1/y_2 + ... + 1/y_n) res=A(n−1,n−1)×sum×(1/y1+1/y2+...+1/yn)
-
费马小定理:若 p p p 为质数,则 ( 1 / a ) m o d p = a ( p − 2 ) m o d p (1/a) \mod p = a^{(p-2)} \mod p (1/a)modp=a(p−2)modp。
综上所述,需要解决两个问题:
- 求得每个二叉树最低一层的叶子节点的数量。
- 通过费马小定理进行分数取模。
代码实现
typedef long long ll;
const int MOD = 1e9 + 7;
/**
* 二叉树节点
*/
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
/**
* @param rt根节点
* @return {以rt为根节点的二叉树的层数, 最低层所包含的叶子节点的数量}
*/
pair<int, int> dfs(TreeNode *rt) {
if (rt->left == nullptr && rt->right == nullptr)return {1, 1};
if (rt->left == nullptr || rt->right == nullptr) {
auto cr = rt->left != nullptr ? rt->left : rt->right;
auto t = dfs(cr);
return {t.first + 1, t.second};
}
auto l = dfs(rt->left), r = dfs(rt->right);
if (l.first == r.first)return {l.first + 1, l.second + r.second};
return l.first > r.first ? make_pair(l.first + 1, l.second) : make_pair(r.first + 1, r.second);
}
/**
* 快速幂
* @return x^n % MOD
*/
ll ksm(ll x, ll n) {
ll res = 1 % MOD;
while (n) {
if (n & 1)res = res * x % MOD;
x = x * x % MOD;
n >>= 1;
}
return res;
}
int cntOfMethods(vector<TreeNode *> &trees) {
ll x = 1, y = 0;
auto &t = trees;
for (int i = 0; i < t.size(); i++) {
int k = dfs(t[i]).second << 1;
// A×sum 部分
x = x * (i + 1) * k % MOD;
// 分数求和部分
y = (y + ksm(k, MOD - 2)) % MOD;
}
x = x * ksm(t.size(), MOD - 2) % MOD;
return x * y % MOD;
}
时间复杂度: O ( n ) O(n) O(n), n n n 是所有二叉树的节点数量之和, n ≤ 2 × 1 0 5 n \leq 2 \times 10^5 n≤2×105。
空间复杂度: O ( 1 ) O(1) O(1)。
小红的数位删除
题目描述
小红拿到了一个正整数 n n n,她每次操作可以删除该正整数的一个数位。小红想知道,自己最少操作多少次可以使得是 5 5 5 的倍数?
注:删除所有数位后正整数会变成 0 0 0,所以一定有解。
数据范围
1 ≤ n ≤ 1 0 9 1 \leq n \leq 10^9 1≤n≤109。
解题思路
5 5 5 的倍数必以 0 0 0 或者 5 5 5 结尾,从后往前删,直到碰到 0 0 0 或者 5 5 5 即可。
代码实现
int fun(int n) {
string s = to_string(n);
int cnt = 0;
for (int i = s.size() - 1; i >= 0; i--, cnt++)
// 5的倍数的末尾必为0或者5
if (s[i] == '0' || s[i] == '5')break;
return cnt;
}
时间复杂度: O ( 1 ) O(1) O(1)。
空间复杂度: O ( 1 ) O(1) O(1)。
无重复字符的最长子串
题目描述
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
解题思路
使用 m a p map map 记录每个字符是否出现过,使用双指针遍历字符串。
代码实现
int main() {
string s;
cin >> s;
unordered_map<char, bool> cnt;
int i, j, res = 0;
for (i = j = 0; j < s.size(); i++, j++) {
// 右指针往右方,直到碰到重复元素
while (j < s.size() && !cnt[s[j]])cnt[s[j++]] = true;
res = max(j - i, res);
if (j >= s.size())break;
// 左指针往右,直到碰到重复元素
while (s[i] != s[j])cnt[s[i++]] = false;
}
printf("%d", res);
return 0;
}
时间复杂度: O ( n ) O(n) O(n), n n n 为字符串的长度。
空间复杂度: O ( n ) O(n) O(n)。
END
题目来源:腾讯音乐娱乐集团2024校园招聘-技术研究类笔试(II)
文章声明:题目来源 牛客 平台,如有侵权,请联系删除!
文章文档:公众号 字节幺零二四
回复关键字可获取本文文档。