1848. 到目标元素的最小距离
时间复杂度:O(n)
从 start 向两端寻找 target,找到的第一个目标元素即最近的。
class Solution {
public:
int getMinDistance(vector<int>& nums, int target, int start) {
for (int i = 0, n = nums.size(); i < n; i++) {
if ((start+i < n && nums[start+i] == target) || (start-i >= 0 && nums[start-i] == target)) {
return i;
}
}
return 0;
}
};
1849. 将字符串拆分为递减的连续值
知识点:递归
时间复杂度:O(n^2)
因为两个数字相差必须是1,所以只要前一个数字确定了,当前数字的值就确定了。
首先,枚举第一个数字的长度,一旦长度确定了,值就确定了,后续数字的值也就确定了。然后按值尝试分割字符串即可。
需要注意的是,如果直接把s转换为数字,有可能超出int64的取值范围。但是因为要将s分割成两个数字,所以第一个数字的上限是999,999,999,9。
class Solution {
public:
int64_t toNumber(const std::string &s, int l, int r) {
int64_t anw = 0;
while(l <= r) {
anw *= 10;
anw += (s[l] - '0');
l++;
}
return anw;
}
bool check(const std::string &s, int pos, int64_t pre) {
if (pos == s.size()) {
return true;
}
for (int i = pos; i < s.size(); i++) {
int64_t val = toNumber(s, pos, i);
if (val == pre-1) {
if (check(s, i+1, val)) {
return true;
}
} else if (val >= pre) {
return false;
}
}
return false;
}
bool splitString(string s) {
if (s.size() <= 1) {
return false;
}
int n = s.size()-1;
int64_t limit = 1L<<40;
for (int i = 0; i < n; i++) {
int64_t val = toNumber(s, 0, i);
if (val > limit) {
return false;
}
if (check(s, i+1, val)) {
return true;
}
}
return false;
}
};
1850. 邻位交换的最小次数
知识点:next_permutation
时间复杂度:O(n^2)
设第 k 个最小妙数为target,如果能构造出 target,则求最小次数是很简单的。那问题变成了如何构造 target。
竞赛时可以借助库函数 next_permutation 构造target。不过面试时还是要能手写next_permutation。接下来简单介绍下next_permutation的实现。
设有字符串s,及s的下一个排列 p,两者长度为n,最长公共前缀的长度为 c。则必有:
- 构成
p[c .. n)和s[c .. n)的字符集合相同。 p[c]为s[c+1 .. n)中,大于s[c]的最小字符。p[c+1 .. n)升序排列。
那么将 s 构造为 p,只需要三步:
- 找到
s中满足s[i] < s[i+1]的最大的i,该i即为c。 - 找到
s[c+1 .. n)中大于s[c]的最小字符s[t],并将两者交换。 - 将
s[c+1 .. n]升序排列。
现在我们可以重复k次上述构造过程,得到target,然后模拟题目的交换过程并计数,即可得到答案。
class Solution {
public:
int getMinSwaps(string num, int k) {
cout << s << endl;
string old = num;
int end = num.size()-1;
while(k--) {
for (int i = end; i >= 1; i--) {
if (num[i-1] < num[i]) {
int l = i-1;
while(i+1 <= end && num[l] < num[i+1]) {
i++;
}
swap(num[l], num[i]);
reverse(num.begin() + l + 1, num.end());
break;
}
}
}
int anw = 0;
for (int i = end; i >= 0; i--) {
if (num[i] != old[i]) {
int j = i-1;
while(old[j] != num[i]) {
j--;
}
anw += i-j;
while(j < i) {
swap(old[j], old[j+1]);
j++;
}
}
}
return anw;
}
};
1851. 包含每个查询的最小区间
知识点:离线处理
时间复杂度:O(n*lgn)
设有 left, right 分别存储了将interval按左右端点排序之后的结果。
设当前正在被处理的询问为 query。
设 lp 为满足 left[lp][0] 不超过 query 的最大坐标,即left[0 .. lp] 中所有区间的左端点都在query左边或与query相等,而其他区间的左端点都在query右边。
设 rp 为满足 right[rp][1] 小于 query 的最大坐标,及right[0 .. rp] 中的所有区间的右端点都在query的左边,而其他区间的右端点都在query的右边或与query相等。
换言之,在left[0 .. lp]中而不在right[0 .. rp]中的最短的区间的长度,即为query的答案。
在代码实现上,可以用一个 multiset 来维护这部分数据:
- 首先对于当前的
query,先找到lp,将left[0 .. lp]中所有区间的长度都插入到容器中。 - 然后找到
rp,将right[0 .. rp]中所有区间的长度都从容器中删除。 - 容器中最小的元素即为答案,如果容器为空,则答案不存在。
另外,如果所有的查询 queries 是有序的,不难发现,随着query的增大,lp和rp是单调递增的,也就是说,没必要每次都从0开始寻找lp和rp并维护容器。而是可以从前一次查询的基础上,继续维护容器内的元素。
class Solution {
public:
vector<int> minInterval(vector<vector<int>>& l, vector<int>& queries) {
multiset<int> len;
auto r = l;
sort(l.begin(), l.end(), [](const auto &lhs, const auto &rhs) { return lhs[0] < rhs[0]; });
sort(r.begin(), r.end(), [](const auto &lhs, const auto &rhs) { return lhs[1] < rhs[1]; });
vector<vector<int>> query;
for (int i = 0; i < queries.size(); i++) {
query.push_back(std::vector<int>{queries[i], i});
}
sort(query.begin(), query.end(), [](const auto &lhs, const auto &rhs) { return lhs[0] < rhs[0];});
int lp = 0, rp = 0;
vector<int> anw(query.size(), 0);
for (auto q : query) {
while (lp < l.size() && l[lp][0] <= q[0]) {
len.insert(l[lp][1] - l[lp][0] + 1);
lp++;
}
while (rp < r.size() && r[rp][1] < q[0]) {
auto it = len.find(r[rp][1] - r[rp][0] + 1);
len.erase(it);
rp++;
}
anw[q[1]] = len.empty() ? -1 : *len.begin();
}
return anw;
}
};