手速场打慢了QAQ
T1 每个字符最多出现两次的最长子字符串
给你一个字符串 s ,请找出满足每个字符最多出现两次的最长子字符串,并返回该子字符串的最大长度。
【暴力】 or 【双指针】
考虑到这题的数据范围很小
N
≤
100
N ≤ 100
N≤100,直接暴力枚举所有子串的
O
(
n
3
)
O(n^3)
O(n3) 做法就能过。
不过,这题其实只要
O
(
n
)
O(n)
O(n) 即可。
主要思想为 双指针 ,枚举右边界,右边界向右移动的过程中,符合条件的最右的左边界也一定有一个向右移动的趋势(只可能在原地不动,或者向右移动),而不会向左退。
具体代码如下:
class Solution {
public:
int maximumLengthSubstring(string s) {
int res = 0, left = 0;
int sz = s.size();
vector<int> cnt(26, 0);
for(int i=0; i<sz; i++) {
int cur = s[i] - 'a';
cnt[cur] ++;
while(cnt[cur] > 2) {
cnt[s[left] - 'a'] --;
left ++;
}
res = max(res, i - left + 1);
}
return res;
}
};
但毕竟是 T1 其实也不用这么麻烦,亲测暴力也是可以过的。
class Solution {
public:
int maximumLengthSubstring(string s) {
int n = s.size();
int res = 0;
for(int i=0; i<n; i++) {
for(int j=i; j<n; j++) {
string t = s.substr(i, j-i+1);
map<char, int> cnt;
for(auto c: t)
cnt[c] ++;
bool p = 0;
for(auto [x, y]: cnt) {
if(y > 2) {
p = 1;
break;
}
}
if(!p) res = max(res, j-i+1);
}
}
return res;
}
};
T2 执行操作使数据元素之和大于等于 K
【暴力】【数学】【贪心】
假设我们一共执行
i
i
i 次操作1,
j
j
j 次操作2,最优策略一定是把
i
i
i 次操作1全部放在操作2之前,这样就会保证所有操作2 add 的值都是最大的。
而如果
i
i
i 的值确定,操作2的最小操作次数
j
j
j 也是确定的。
具体来说,执行
i
i
i 次操作1后,数组为
[
(
1
+
i
)
]
[(1 + i)]
[(1+i)],那么最少我们用
(
k
/
(
1
+
i
)
(
向上取整
)
−
1
)
(k / (1+i) (向上取整) - 1)
(k/(1+i)(向上取整)−1) 次操作2,就能让总和达到
k
k
k 。
如果我们只用操作1,最坏情况下
k
−
1
k-1
k−1 次就能达到总和为
k
k
k。
考虑到
k
≤
1
0
5
k ≤ 10^5
k≤105 ,我们直接对操作1的执行次数
i
i
i 进行枚举,然后确定一个
i
+
(
k
/
(
1
+
i
)
(
向上取整
)
−
1
)
i + (k / (1+i) (向上取整) - 1)
i+(k/(1+i)(向上取整)−1) 的最小值就可。
class Solution {
public:
int minOperations(int k) {
int res = 1e9;
for(int i=0; i<=k-1; i++) {
int j = (k+i)/(i+1) - 1; // k/(i+1)向上取整 - 1
res = min(i + j, res);
}
return res;
}
};
j = (k+i)/(i+1) - 1
这一步 也可简化为 j = (k-1)/(i+1)
当然,再用基本不等式稍加推导,发现最小值一定是在
k
−
1
\sqrt{k - 1}
k−1 处取到。
所以用
O
(
1
)
O(1)
O(1) 即可解决此问题
class Solution {
public:
int minOperations(int k) {
int i = sqrt(k-1);
int j = (k-1)/(i+1); // k/(i+1)向上取整 - 1
return i + j;
}
};
T3 最高频率的 ID
【STL的运用】or 【线段树】等
方法一:上大根堆暴力模拟这个过程即可
tips:优先队列的元素如果是pair,默认情况下先以first为关键字降序排,再以second为关键字降序排。(默认是大根堆嘛)
class Solution {
public:
const int N = 1e5+5;
vector<long long> mostFrequentIDs(vector<int>& nums, vector<int>& freq) {
int n = nums.size();
priority_queue<pair<long long, int>> hp;
vector<long long> res;
vector<long long> s(N);
for(int i=0; i<n; i++) {
s[nums[i]] += 1ll*freq[i];
hp.push({s[nums[i]], nums[i]});
while(s[hp.top().second] != hp.top().first)
hp.pop();
res.push_back(hp.top().first);
}
return res;
}
};
方法二:这个问题也可看成是一个单点修改 + 区间查询最大值的问题
但显然就把问题搞复杂了,还是方法一更优。
不过我还是把代码放着,需要的同学可以参考一下。
struct info {
long long maxv;
};
info operator + (const info &l, const info &r) {
info a;
a.maxv = max(l.maxv, r.maxv);
return a;
}
const int N = 2e5+5;
struct node {
info val;
} tr[N * 4];
int a[N];
void update(int id)
{
tr[id].val = tr[id*2].val + tr[id*2+1].val;
}
void build(int id, int l, int r)
{
if(l == r)
tr[id].val = {a[l]};
else {
int mid = l+r >> 1;
build(id*2, l, mid);
build(id*2+1, mid+1, r);
update(id);
}
}
void change(int id, int l, int r, int pos, long long val)
{
if(l == r)
tr[id].val = {val};
else {
int mid = l+r >> 1;
if(pos <= mid) change(id*2, l, mid, pos, val);
else change(id*2+1, mid+1, r, pos, val);
update(id);
}
}
info query(int id, int l, int r, int ql, int qr)
{
if(l == ql && r == qr) return tr[id].val;
int mid = l+r >> 1;
if(qr <= mid) return query(id*2, l, mid, ql, qr);
else if(ql > mid) return query(id*2+1, mid+1, r, ql, qr);
else return query(id*2, l, mid, ql, mid) +
query(id*2+1, mid+1, r, mid+1, qr);
}
class Solution {
public:
vector<long long> mostFrequentIDs(vector<int>& nums, vector<int>& freq) {
int n = nums.size();
int maxn = 1e5;
build(1, 1, maxn);
vector<long long> res;
for(int i=0; i<n; i++) {
long long t = query(1, 1, maxn, nums[i], nums[i]).maxv;
change(1, 1, maxn, nums[i], t+freq[i]);
res.push_back(tr[1].val.maxv);
}
return res;
}
};
T4 最长公共后缀查询
【字典树】
大致思路:后缀变前缀 -> 根据优先级排序编号 -> 字典树查询最长前缀
字典树维护的是第一个到达某个树节点的字符串的编号,因为我按照题目要求的优先级来编号(第一关键字:长度小优先,第二关键字:初始编号小优先),所以第一个到达的编号(即最小的那个编号)就一定是最优解。
class Trie { //字符串统计数
struct node {
int son[26];
int id;
node() : id(-1) {
for(int i=0; i<26; i++)
son[i] = 0;
}
} NIL;
vector<node> tr;
int idx;
public:
Trie() : tr(1), idx(0) {}
void insert(string x, int y) {
int p = 0;
if(tr[p].id == -1) tr[p].id = y;
for(char c: x) {
int cur = c - 'a';
if (! tr[p].son[cur]) {
tr.push_back(NIL);
tr[p].son[cur] = ++idx;
}
p = tr[p].son[cur];
if(tr[p].id == -1) tr[p].id = y;
}
}
int search(string x) {
int p = 0;
for(char c: x) {
int cur = c - 'a';
if (! tr[p].son[cur]) {
return tr[p].id;
}
p = tr[p].son[cur];
}
return tr[p].id;
}
};
class Solution {
public:
vector<int> stringIndices(vector<string>& wordsContainer, vector<string>& wordsQuery) {
int n = wordsQuery.size();
int m = wordsContainer.size();
for(int i=0; i<m; i++) {
reverse(wordsContainer[i].begin(), wordsContainer[i].end());
}
for(int i=0; i<n; i++) {
reverse(wordsQuery[i].begin(), wordsQuery[i].end());
}
vector<pair<string, int>> a;
for(int i=0; i<m; i++)
a.push_back({wordsContainer[i], i});
sort(a.begin(), a.end(), [](pair<string, int> x, pair<string, int> y) {
if(x.first.size() != y.first.size()) return x.first.size() < y.first.size();
return x.second < y.second;
});
Trie T;
for(int i=0; i<m; i++) {
T.insert(a[i].first, i);
}
vector<int> res;
for(int i=0; i<n; i++) {
int pos = T.search(wordsQuery[i]);
res.push_back(a[pos].second);
}
return res;
}
};