Leetcode 第196场周赛题解

5452. 判断能否形成等差数列

知识点:排序;等差数列;
首先要明确一点,如果一个数列是等差序列,那么一定是排列有序的。因此可以先进行排序,然后逐一检查相邻两项的差值是否都相同。

class Solution {
public:
    bool canMakeArithmeticProgression(vector<int>& arr) {
        sort(arr.begin(), arr.end());
        for(int i = 1; i < arr.size(); i++) {
            if(arr[i] - arr[i-1] != arr[1] - arr[0]) {
                return false;
            }
        }
        return true;
    }
};

所有蚂蚁掉下来前的最后一刻

知识点:思维题
不得不说总部出的题还是很有弯弯绕的,先赞一个。
首先来简化一下问题,假设只有两只蚂蚁,分别从左右端点向对端出发,然后他们会在中点相遇。按照题意,相遇后两只蚂蚁会立即改变方向继续前进
那么,如果相遇后不是掉头跑路而是擦肩而过呢
并不会对答案产生影响啊!
那么题目就变成了寻找距离初始方向的端点最远的那只蚂蚁,然后计算出它掉落下的时间。

class Solution {
public:
    int getLastMoment(int n, vector<int>& left, vector<int>& right) {
        int anw = 0;
        for(auto x : right) {
            anw = max(anw, n-x);
        }
        for(auto x : left) {
            anw = max(anw, x);
        }
        return anw;
    }
};

5454. 统计全 1 子矩形

知识点:二分;容斥原理;
首先来思考下最暴力的解决方案:枚举所有子矩形(枚举右下角坐标,然后枚举长宽),然后对每个子矩形进行检查。
不过这个时间复杂度太高了,我们尝试优化下。

首先考虑检查部分,常规手段是校验子矩阵和:需要O(n*m)的预处理,之后每次查询为 O(1)。
预处理部分为:设 f(x, y) 是以(x,y) 为右下角的长宽分别为 x,y 的矩形的和,那么有 f(x, y) = f(x-1, y) + f(x, y-1) - f(x-1, y-1) + mat(x, y)。如下图所示:

同样的,查询逻辑为:设 s(x, y, w, h) 为以 (x, y) 为右下角,且长宽分别为w, h 的子矩形和,
那么 s(x, y, w, h) = f(x, y) - f(x-w, y) - f(x, y-h) + f(x-w, y-h)。
如果 s(x, y, w, h) 为 w*h,那么该子矩形内的元素全为 1,否则不是。

接下来继续考虑枚举部分:假设已经确定矩形(x,y,w,h) 的元素全为 1,那么对于所有的 h’ ∈[1, h] 都满足子矩形(x,y,w,h’)的元素全为 1。那么很可以开心的二分了。

class Solution {
public:
    //为了应付边界问题,sum 的下标比 mat 大 1。
    int sum [151][151]; 
    int numSubmat(vector<vector<int>>& mat) {
        memset(sum, 0, sizeof(sum));
        int n = mat.size();
        if(n == 0) {
            return 0;
        }
        int m = mat[0].size();
        if(m == 0) {
            return 0;
        }
        //预处理子矩阵和
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + mat[i-1][j-1];
              }
        }
        int64_t count = 0;
        for(int i = 1;i <= n; i++) { 
            for(int j = 1; j <= m; j++) { // i,j 为右下角坐标
                for(int k = 1; k <= i; k++) { //k 为高度
                    int L = 1, R = j;
                    int anw = -1;
                    while(L <= R) { // 二分宽度
                        int mid = (L+R)>>1;
                        if(sum[i][j] - sum[i-k][j] - sum[i][j-mid] + sum[i-k][j-mid] == (k*mid)) {
                            L = mid+1;
                            anw = mid; // 记录一下最大的全为1的子矩形宽度
                        } else {
                            R = mid-1;
                        }
                    }
                    if(anw == -1) { //该轮枚举不存在全为 1 的子矩形。
                        continue;
                    }
                    count += anw;
                }
            }
        }
        return count;
    }
};

5455. 最多 K 次交换相邻数位后得到的最小整数

知识点:贪心;排序;区间查询;
首先,比较数字大小时,越高位的字符其权重越大。也就是说,应该在限定的移动次数内,让位置靠前的字符尽可能的小
因为最高位的权重最大,所以首先来考虑最高位。设此时的移动次数为 k,num 的长度 为 n。我们应该在min(k+1, n) 个字符中寻找一个最小的,将它移动到最高位,如果有多个最小字符,则选择移动代价最小的那个。之后再维护次高位的字符,次次高位的…依次类推,直到移动次数用尽或者维护到最后一个字符。

可以通过预处理,得到每种字符的位置的优先队列。然后对于每种字符检查是否可移动到指定位置(仅检查队首即可)。如果有多种字符满足需求,选取最小的字符即可。
计算移动代价时,设待移动的元素初始下标为 i,需统计初始下标在[0,i]中且已被移动的字符数量,可以使用线段树或者树状数组完成。

class Solution {
    //预处理字符位置的优先队列
    priority_queue<int, vector<int>, greater<int>> pq[10];
    
    int st[30001*4]; //线段树所需的数组
    //更新线段树的代码
    void update(int root, int L, int R, int goal) {
        st[root] += 1;
        if(L == R) {
            return ;
        }
        int mid = (L+R)>>1;
        if(goal <= mid) {
            update(root<<1, L, mid, goal);
        } else {
            update(root<<1|1, mid+1, R, goal);
        } 
    }
    //线段树查询代码
    int query(int root, int L, int R, int goal) {
        if(R == goal) {
            return st[root];
        }
        int mid = (L+R)>>1;
        if(mid < goal) {
            return st[root<<1] + query(root<<1|1, mid+1, R, goal);
        }
        return query(root<<1, L, mid, goal);
    }
public:
    string minInteger(string num, int k) {
        memset(st, 0, sizeof(st));
        //处理字符位置
        for(int i = 0; i < num.size(); i++) {
            pq[num[i]-'0'].push(i);
        }
        string anw;
        while(anw.size() < num.size()) {
            for(int i = 0; i < 10; i++) { //枚举字符
                if(pq[i].size() <= 0) { 
                    continue;
                }
                //pq[i].top()为当前字符最靠近目标位置的下标
                int cost = pq[i].top() - query(1, 1, 30000, pq[i].top()+1);
                if(cost <= k) {
                    k -= cost;
                    anw += char(i+'0');
                    update(1, 1, 30000, pq[i].top()+1);
                    pq[i].pop();
                    break;
                }
            }
        }
        return anw;
    }
};

猜你喜欢

转载自blog.csdn.net/Time_Limit/article/details/107139134