双周赛 51,单周赛 239 题解

本场比赛涵盖很多知识点,具体为 dfs,构造,双指针,set,单调队列,排列距离,逆序数

双周赛 \(51\)

将所有数字用字符替换

题意

给定一个下标从 \(0\) 开始的字符串 s,其中偶数下标处为小写字母 alpha,奇数下标处为数字 num,现在将所有奇数下标的数字替换成小写字母 alpha + num,保证 alpha + num <= 'z'

例如 a1b2,将被替换成 abbda1c1e1 将被替换成 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]) <= 1abs(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,计算从 pq 相邻数字的最小交换次数

有一个结论,若要使得任意排列有序,那么相邻数字交换的最小次数为逆序数

作下标映射 p[i] -> i,那么 p 映射成 1, 2, 3..., n,再计算出 q 中对应的下标,求出 q 的逆序数即可

例如,p = 4312, q = 1243 ,作下标映射,那么 p = 1234, q = 3412,得到逆序数为 4

考虑 存在相同位,令相同位的 相对顺序不改变,例如 12322 -> 32122123222 的下标分别为 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\)

题解

本题和 最远的房间 有异曲同工之妙,具体维护一个存放区间长度和区间右端点的 单调队列

intervalsqueries 排序,考虑 顺序枚举查询,动态维护单调队列 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;
    }
};
posted @ 2021-05-02 21:43  徐摆渡  阅读(101)  评论(0编辑  收藏  举报