双周赛 51,单周赛 239 题解
本场比赛涵盖很多知识点,具体为 dfs
,构造,双指针,set
,单调队列,排列距离,逆序数
双周赛 \(51\)
将所有数字用字符替换
题意
给定一个下标从 \(0\) 开始的字符串 s
,其中偶数下标处为小写字母 alpha
,奇数下标处为数字 num
,现在将所有奇数下标的数字替换成小写字母 alpha + num
,保证 alpha + num <= 'z'
例如 a1b2
,将被替换成 abbd
,a1c1e1
将被替换成 abcdef
题解
签到,按照题意模拟
class Solution {
public:
string replaceDigits(string s) {
for (int i = 0; i < s.length(); ++i)
if (i % 2 == 1) s[i] = s[i - 1] + s[i] - '0';
return s;
}
};
座位预约管理系统
题意
设计一个管理 \(n\) 个座位的系统,座位从 \(1\) 编号到 \(n\)
请你实现 SeatManager
类
SeatManager(int n)
初始化一个SeatManager
对象,它管理从1
到n
编号的n
个座位。所有座位初始都是可预约的。int reserve()
返回可以预约座位的最小编号,并且此座位变为不可预约。void unreserve(int seatNumber)
将给定编号seatNumber
对应的座位变成可以预约。
\(1\leq n\leq 100,000\)
题解
维护一个 set
,初始化时将 \(1\) 到 \(n\) 全部扔到 set
中,预约时返回 set.begin()
,同时删除 set.begin()
,取消预约时 set.insert(seatnumver)
class SeatManager {
public:
set<int> st;
SeatManager(int n) {
for (int i = 1; i <= n; ++i)
st.insert(i);
}
int reserve() {
int temp = *st.begin();
st.erase(st.begin());
return temp;
}
void unreserve(int seatNumber) {
st.insert(seatNumber);
}
};
减小和重新排列数组后的最大元素
题意
给一个正整数数组 arr
,可以执行一些操作(也可以不进行任何操作),使得数组满足以下条件
arr
中第一个元素必须为1
- 任意相邻两个元素的差的绝对值小于等于
1
,也就是说,对于任意的1 <= i < arr.length
(数组下标从0
开始),都满足abs(arr[i] - arr[i - 1]) <= 1
,abs(x)
为x
的绝对值。
你可以执行以下 2
种操作任意次数
- 减小
arr
中任意元素的值,使其变为一个更小的正整数 - 重新排列
arr
中的元素,你可以以任意顺序重新排列。
请你返回执行以上操作后,在满足前文所述的条件下,arr
中可能的最大值。
\(1\leq arr.length\leq 100,000\)
题解
既然可以重排列,那么我们可以对数组排序
我们可以把任意元素变小,那么只需要把数组头设为 1
,剩下的元素取 min(arr[i - 1] + 1, arr[i])
即可
class Solution {
public:
int maximumElementAfterDecrementingAndRearranging(vector<int>& arr) {
sort(arr.begin(), arr.end());
if (arr[0] != 1) arr[0] = 1;
for (int i = 1; i < arr.size(); ++i)
arr[i] = min(arr[i], arr[i - 1] + 1);
int ans = *max_element(arr.begin(), arr.end());
return ans;
}
};
最近的房间
题意
给定 \(n\) 个房间,每个房间的结构为二元组 (roomID, size)
,分别代表房间 ID
和房间面积,保证房间 ID
两两不同
给 \(k\) 个查询,每个查询为二元组 (preferred, minSize)
现在要求满足 size >= minSIze
同时 abs(roomID - preferred)
最小的房间 ID
如果 abs(roomID - preferred)
相等,那么取小的 roomID
,例如 preferred = 4, roomID = 3, 5
的两个房间同时满足题意,选择 roomID = 3
,如果没有这样的房间,答案为 -1
\(1\leq n\leq 100,000,\ 1\leq k\leq 10,000\)
题解
如果不考虑面积,只考虑计算最小的 roomID
,使得 abs(roomID - preferred)
最小,我们可以维护有序数组 roomID
,并二分查找 preferred
前后的两个位置,比较得到最小值,这个可以用 set
维护,lower_bound
计算
考虑面积,若将房间和询问按照面积排序,我们发现倒序枚举询问有奇效。倒序枚举询问时,需求的 minSize
在递减,那么满足 size >= minSize
要求的房间就更多
可以动态维护一个 set
,倒序枚举询问,利用双指针不断扩充 set
,同时在 set
中二分查找,计算答案
每个房间二元组都只扫描一次,时间复杂度 \(O(nlogn + \sum\limits_{i = 1}^{n}{log_{2}i}) = O(nlogn)\)
#define pb push_back
class Solution {
public:
static bool cmp(vector<int> &x, vector<int> &y)
{
if (x[1] == y[1]) return x[0] < y[0];
return x[1] < y[1];
}
vector<int> closestRoom(vector<vector<int>>& rooms, vector<vector<int>>& queries) {
int n = rooms.size();
int k = queries.size();
for (int i = 0; i < n; ++i) rooms[i].pb(i);
for (int i = 0; i < k; ++i) queries[i].pb(i);
vector<int> ans(k);
sort(rooms.begin(), rooms.end(), cmp);
sort(queries.begin(), queries.end(), cmp);
set<int> st;
for (int i = k - 1, j = n - 1; i >= 0; --i) {
while (j >= 0 && rooms[j][1] >= queries[i][1]) st.insert(rooms[j--][0]);
int res = queries[i][2];
if (st.empty()) ans[res] = -1;
else {
auto it = st.lower_bound(queries[i][0]);
if (it == st.end()) ans[res] = *(--it);
else if (it == st.begin()) ans[res] = *it;
else {
int x = *it, y = *(--it);
int dis1 = abs(x - queries[i][0]), dis2 = abs(y - queries[i][0]);
if (dis1 < dis2) ans[res] = x;
else ans[res] = y;
}
}
}
return ans;
}
};
单周赛 \(239\)
到目标元素的最小距离
题意
给定一个整数数组 nums
,以及两个整数 target, start
,找出一个下标 i
,满足 nums[i] == target
同时 abs(i - start)
最小化
题解
签到,按题意模拟
class Solution {
public:
int getMinDistance(vector<int>& nums, int target, int start) {
int n = nums.size();
int ans = INT_MAX;
for (int i = 0; i < n; ++i)
if (target == nums[i]) ans = min(ans, abs(start - i));
return ans;
}
};
将字符串拆分为递减的连续值
题意
给定一个仅由数字组成的字符串 s
请判断能否将 s
分割成两个或多个非空子串(连续子序列,使得子串的 数字 按照 降序 排列,且相邻数字的差为 1
- 例如
009008
可以划分成9, 8
- 例如
200100
可以划分成2, 1
- 例如
1234
无法被划分
\(1\leq n\leq 20\)
题解
数据很小,考虑搜索
用 pre
记录之前的值,cur
记录当前的值,cnt
记录划分的子串个数
每一层 dfs
枚举隔板的位置,具体来说,如果 cnt == 0
或者 pre == cur + 1
,就可以防止隔板,进行新一轮的划分
数据很大,得用高科技
细节较多,好想不好写
#define ull unsigned long long
class Solution {
public:
bool dfs(ull pre, int p, string s, int cnt)
{
if (p == s.length()) return cnt > 1;
ull cur = 0;
for (int i = p; i < s.length(); ++i) {
cur = cur * 10 + s[i] - '0';
if (!cnt || cur == pre - 1) { // 符合条件,就试一下能不能继续划分
if (dfs(cur, i + 1, s, cnt + 1))
return 1;
}
}
return 0;
}
bool splitString(string s) {
return dfs(0, 0, s, 0);
}
};
邻位交换的最小次数
题意
给一个表示大整数的字符串 num
,和一个整数 k
如果某个整数是 num
中各位数字的一个排列,且它的值大于 num
,则称这个整数为 妙数,我们关注值 最小 的妙数
返回要得到第 k
个 最小妙数,需要对 num
执行的相邻位数字交换的最小次数。
题解
最小妙数可以用 next_permutation
解决
下面考虑最小交换次数,即为 排列的距离
考虑 各位不同 的整数 p
,以及其排列 q
,计算从 p
到 q
相邻数字的最小交换次数
有一个结论,若要使得任意排列有序,那么相邻数字交换的最小次数为逆序数
作下标映射 p[i] -> i
,那么 p
映射成 1, 2, 3..., n
,再计算出 q
中对应的下标,求出 q
的逆序数即可
例如,p = 4312, q = 1243
,作下标映射,那么 p = 1234, q = 3412
,得到逆序数为 4
考虑 存在相同位,令相同位的 相对顺序不改变,例如 12322 -> 32122
,12322
中 2
的下标分别为 2, 4, 5
,那么 32122
中对应的下标为 32145
,逆序数为 3
本题带点思维含量,知道逆序数的结论就好做了
#define pb push_back
class Solution {
public:
int getInvNum(vector<int> s)
{
int n = s.size();
int num = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (s[i] > s[j]) num++;
}
}
return num;
}
int getMinSwaps(string num, int k) {
string s = num;
int n = num.length();
while (k--) next_permutation(s.begin(), s.end());
vector<int> mp[10];
for (int i = 0; i < n; ++i)
mp[s[i] - '0'].pb(i);
vector<int> a(n);
vector<int> idx(10);
for (int i = 0; i < n; ++i) {
a[i] = mp[num[i] - '0'][idx[num[i] - '0']];
idx[num[i] - '0']++;
}
return getInvNum(a);
}
};
包含每个查询的最小区间
题意
给定一个二维数组 intervals
,每个元素是个二元组 (left, right)
,表示一个 闭区间
给定一个询问数组 queries
,第 j
个查询的答案是满足 lefti <= queries[j] <= righti
的长度最小的区间 i
的长度。如果不存在这样的区间,那么答案是 -1
。
\(1\leq intervals.size\leq 100,000\)
\(1\leq queries.size\leq 100,000\)
题解
本题和 最远的房间 有异曲同工之妙,具体维护一个存放区间长度和区间右端点的 单调队列
对 intervals
和 queries
排序,考虑 顺序枚举查询,动态维护单调队列 set<pair<int, int>>
,其中 pair.first
记录区间长度,pair.second
记录区间右端点
- 考虑入队,设区间长度
Li = intervals[i][1] - intervals[i][0] + 1
,利用双指针,将所有满足intervals[i][0] <= queries[j]
的Li, intervals[i][1]
打包成二元组放入set
。由于是顺序枚举,后面的查询值一定大于队列中所有区间的左端点 - 考虑出队,需要把队列中所有
set.begin()->second < queries[j]
的二元组剔除,因为他们的右端点小于目标值 - 考虑查询,返回
set.begin()->first
每个区间最多入队一次,出队一次,单次复杂度为 \(O(logn)\),因此总的时间复杂度为 \(O(nlogn)\)
#define pb push_back
class Solution
{
public:
vector<int> minInterval(vector<vector<int>> &intervals, vector<int> &queries)
{
int n = intervals.size(), k = queries.size();
vector<int> idx(k), ans(k);
for (int i = 0; i < k; ++i) idx[i] = i;
sort(idx.begin(), idx.end(), [&](int x, int y)
{
return queries[x] < queries[y];
});
sort(intervals.begin(), intervals.end());
set<pair<int, int>> st;
int j = 0;
for (auto &i: idx) {
int tar = queries[i];
while (j < n && intervals[j][0] <= tar) {
st.emplace(intervals[j][1] - intervals[j][0] + 1, intervals[j][1]);
++j;
}
while (!st.empty() && st.begin()->second < tar)
st.erase(st.begin());
if (st.empty()) ans[i] = -1;
else ans[i] = st.begin()->first;
}
return ans;
}
};