第 225 场周赛
Problem A - 替换隐藏数字得到的最晚时间
贪心替换即可。注意第一个数字和第二个数字有相互的依赖关系。
- 时间复杂度。
- 空间复杂度。
参考代码(C++)
class Solution {
public:
    string maximumTime(string time) {
        if (time[0] == '?') {
            if (time[1] == '?' || time[1] < '4')
                time[0] = '2';
            else
                time[0] = '1';
        }
        if (time[1] == '?') {
            if (time[0] == '2')
                time[1] = '3';
            else
                time[1] = '9';
        }
        if (time[3] == '?')
            time[3] = '5';
        if (time[4] == '?')
            time[4] = '9';
        return time;
    }
};
Problem B - 满足三条件之一需改变的最少字符数
计数后分别考虑三种条件即可:
- 要实现条件三,我们只需要找出在两个字符串中总计出现次数最多的那个字符,然后将剩余字符都替换为它。 
- 条件一和二实际上是一样的,我们只需要考虑条件一,然后交换两个字符串的计数数组再计算一次就等于考虑了条件二。 - 对于条件一,我们考虑令中所有字符都小于,中所有字符都不小于,枚举找出最优解即可。
- 这里可以利用前缀和进一步优化,但因为本身很小,所以不用前缀和也没关系。
 
- 时间复杂度。其中为字母表的大小,本题中。 
- 空间复杂度。 
参考代码(C++)
class Solution {
public:
    int minCharacters(string a, string b) {
        vector<int> ca(26), cb(26);
        int n = a.size(), m = b.size();
        for (char c : a)
            ca[c - 'a']++;
        for (char c : b)
            cb[c - 'a']++;
        int ans = n + m;
        for (int i = 0; i < 26; ++i)
            ans = min(ans, n + m - ca[i] - cb[i]);
        
        auto make_smaller = [&](vector<int> &a, vector<int> &b) {
            for (int i = 1; i < 26; ++i) {
                int tot = 0;
                for (int j = i; j < 26; ++j)
                    tot += a[j];
                for (int j = 0; j < i; ++j)
                    tot += b[j];
                ans = min(ans, tot);
            }
        };        
        
        make_smaller(ca, cb);
        make_smaller(cb, ca);
        
        return ans;
    }
};
Problem C - 找出第 K 大的异或坐标值
首先,如何求出每个坐标的异或值?我们可以用类似二维前缀和的办法来求。因为本身就是自己的逆运算,所以不需要像二维前缀和那样有有,直接全用即可。
一共个数,我们要找出第大,这里有几种常用的办法:
- 快速选择(快速排序思想),平均时间复杂度,但可能存在极端情况,为了避免极端情况,可以对得到的数组预先进行一次洗牌。
- 排序,平均时间复杂度。
- 小根堆,维护个最大的元素,平均时间复杂度。
这里我使用的是第三种方法。实际测试下来,使用第二种方法也可以通过。
- 时间复杂度,其中是矩阵的行数,是矩阵的列数。
- 空间复杂度。
参考代码(C++)
class Solution {
public:
    int kthLargestValue(vector<vector<int>>& matrix, int k) {
        int n = matrix.size(), m = matrix[0].size();
        vector<vector<int>> v(n + 1, vector<int>(m + 1));
        priority_queue<int, vector<int>, greater<>> q;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) {
                v[i][j] = v[i - 1][j] ^ v[i][j - 1] ^ v[i - 1][j - 1] ^ matrix[i - 1][j - 1];
                q.emplace(v[i][j]);
                if (q.size() > k)
                    q.pop();
            }
        return q.top();
    }
};
Problem D - 放置盒子
本题需要一些观察。题目中的几个范例其实已经给出了提示:最理想的情况下,每一层都是从开始的等差数列。特别地,在当前层为的情况下,上一层恰好可以放下个箱子。
那么,在某一层的箱子数不能表示为一个等差数列的和的情况下,它的上面一层可以放多少个箱子呢?经过简单的尝试,我们可以发现,当时,无法比之前多放箱子;而随着继续增加,每增加就可以多放个箱子。这在数学上也是严密的,因为相邻的两个等差数列和和之间的差为,而它们能放箱子的个数的差为。
这样,我们就可以用二分搜索来确定最下层的箱子个数。在最下层箱子数一定的情况下,我们根据上面的推导,利用与当前层箱子数相邻的两个等差数列和,确定当前层的上方可以放的箱子个数,然后逐层上推即可。
- 预处理时间复杂度,单次运行时间复杂度。考虑到使用了记忆化,在连续多次运行的情况下,运行时间可以进一步缩短。
- 空间复杂度。
参考代码(C++)
bool init = false;
int MAXN = 2e9;
vector<int> total;
unordered_map<int, long long> memo;
class Solution {
public:
    int minimumBoxes(int n) {
        if (!init) {
            init = true;
            total = {0};
            for (int i = 0; 1LL * i * (i + 1) / 2 < MAXN; ++i)
                total.emplace_back(1LL * i * (i + 1) / 2);
        }
        
        auto solve = [&](int num) {
            if (memo.count(num))
                return memo[num];
            int num0 = num;
            long long ans = num;
            while (num > 1) {
                auto it = lower_bound(total.begin(), total.end(), num);
                int r = *it, l = *prev(it), vl = *prev(prev(it));
                ans += num = vl + max(0, num - l - 1);
            }
            return memo[num0] = ans;
        };
        
        
        int lo = 1, hi = n;
        while (lo <= hi) {
            int mid = (lo + hi) >> 1;
            if (solve(mid) >= n)
                hi = mid - 1;
            else
                lo = mid + 1;
        }
        return lo;
    }
};