知识点:排序;等差数列;
首先要明确一点,如果一个数列是等差序列,那么一定是排列有序的。因此可以先进行排序,然后逐一检查相邻两项的差值是否都相同。
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;
}
};
知识点:二分;容斥原理;
首先来思考下最暴力的解决方案:枚举所有子矩形(枚举右下角坐标,然后枚举长宽),然后对每个子矩形进行检查。
不过这个时间复杂度太高了,我们尝试优化下。
首先考虑检查部分,常规手段是校验子矩阵和:需要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;
}
};
知识点:贪心;排序;区间查询;
首先,比较数字大小时,越高位的字符其权重越大。也就是说,应该在限定的移动次数内,让位置靠前的字符尽可能的小。
因为最高位的权重最大,所以首先来考虑最高位。设此时的移动次数为 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;
}
};