11.16、11.17、11.18灵神题单补充
11/16
每日一题:3240. 最少翻转次数使二进制矩阵回文 II
思路:由一般到特殊,分类讨论
当a为2 * 2时,如果回文的话,所有元素应该都是一样,1的个数为0或4,恰好满足条件;当a扩大为4 * 4时,发现也只需要看左上角四个元素,利用对称性即可得到对称位置的元素。
由于所有行和列都必须是回文的,所以要满足:
a[i][j] = a[i][n−1−j] = a[m−1−i][j] = a[m−1−i][n−1−j]也就是这四个数要么都是 0,要么都是 1。其中
0 ≤ i < ⌊m/2⌋, 0 ≤ j < ⌊n/2⌋。设cnt 1 =a[i][j]+a[i][n−1−j]+a[m−1−i][j]+a[m−1−i][n−1−j]把这四个数都变成 0 需要翻转cnt 1次,都变成 1 需要翻转4−cnt 1次。两种情况取最小值,把min(cnt 1 ,4−cnt 1 )加入答案。分析:1 的数目被 4 整除
分类讨论:
如果 m 和 n 都是偶数:由于上面四个数四个数一组,都翻转成了 0 或者 1,所以 1 的数目能被 4 整除自动成立。
如果 m 是奇数,n 是偶数:正中间一排需要翻转成回文的,且 1 的数目需要能被 4 整除。
如果 m 是偶数,n 是奇数:正中间一列需要翻转成回文的,且 1 的数目需要能被 4 整除。
如果 m 和 n 都是奇数:正中间一排和正中间一列都需要翻转成回文的,且 1 的数目需要能被 4 整除。除了正中央的格子以外,每个格子都有镜像位置,这些格子两个数两个数一组,都翻转成了 0 或者 1。那么翻转之后,不考虑正中央的格子,1 的数目就是偶数。
所以正中央的格子必须是 0,否则最终 1 的数目是奇数,无法被 4 整除。
如何处理正中间一排和正中间一列,是本题的重点。
首先,如果 m 和 n 都是奇数,那么正中央的格子
(⌊m/2⌋,⌊n/2⌋)必须是 0,把其元素值加入答案。然后统计正中间一排(如果 m 是奇数)和正中间一列(如果 n 是奇数)的格子:
设
diff为镜像位置不同的数对个数。注意统计的是数对。
设cnt 1为镜像位置相同的 1的个数。注意统计的是个数。
这diff对 0 和 1 必须翻转其中一个数,所以答案至少要增加 diff。什么情况下,可以只增加 diff?分类讨论:
- 如果 cnt 1 是 4 的倍数,那么只需把这 diff 对 0 和 1 中的 1 全部变成 0,这样 1 的个数就是 4 的倍数了。所以把 diff 加入答案。
- 如果 cnt1 不是 4 的倍数,由于 cnt 1 是偶数,其除以 4 必定余 2(此时cnt1 % 4 == 2)(注意这说明 cnt 1 ≥2)。
- 继续讨论:
如果diff>0,把其中一对数都变成 1,(cnt1+2后为4的倍数)。其余diff−1对数全部变成 0,这样 1 的个数就是 4 的倍数了。所以同样地,把diff加入答案。- 如果
diff=0,我们只能把 cnt 1 中的两个 1 变成 0(多了2个1),使得 1 的个数是 4 的倍数。所以把答案增加 2。综上所述:
- 如果
diff>0,额外把diff加入答案。- 如果
diff=0,额外把cnt 1 mod 4加入答案。
class Solution {
public:
int minFlips(vector<vector<int>>& a) {
int m = a.size(), n = a[0].size();
int ans = 0;
for (int i = 0; i < m / 2; i++) {
for (int j = 0; j < n / 2; j++) {
int cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j];
ans += min(cnt1, 4 - cnt1); // 全为 1 或全为 0
}
}
if (m % 2 && n % 2) {
// 正中间的数必须是 0
ans += a[m / 2][n / 2];
}
int diff = 0, cnt1 = 0;
if (m % 2) {
// 统计正中间这一排
for (int j = 0; j < n / 2; j++) {
if (a[m / 2][j] != a[m / 2][n - 1 - j]) {
diff++;
} else {
cnt1 += a[m / 2][j] * 2;
}
}
}
if (n % 2) {
// 统计正中间这一列
for (int i = 0; i < m / 2; i++) {
if (a[i][n / 2] != a[m - 1 - i][n / 2]) {
diff++;
} else {
cnt1 += a[i][n / 2] * 2;
}
}
}
return ans + (diff ? diff : cnt1 % 4);
}
};
825. 适龄的朋友
思路:滑动窗口
根据题意,x 向 y 发送好友请求,只需满足 x=y 且\(0.5 * ages[x]+7<ages[y]≤ages[x]\)
注意,只要满足了 \(ages[y]≤ages[x]\),题目的第三个条件一定为假。由于 n 很大而 ages[i]≤120,我们可以用一个长为 121 的 cnt 数组统计每个年龄的人数。枚举年龄
ageX,我们需要知道:可以发送好友请求的最小年龄ageY是多少。
年龄在区间 [ageY,ageX] 中的人数。由于 ageX 越大,ageY 也越大,可以用滑动窗口解决。如果你不了解滑动窗口。窗口内维护年龄在区间 [ageY,ageX] 中的人数
cntWindow。如果发现 cntWindow>0,说明存在可以发送好友请求的用户:当前这
cnt[ageX]个用户可以与cntWindow个用户发送好友请求,根据乘法原理,这有cnt[ageX]⋅cntWindow个。其中有cnt[ageX]个好友请求是自己发给自己的,不符合题目要求,要减去。所以把cnt[ageX]⋅cntWindow−cnt[ageX]加入答案。细节
ageY≤ 0.5 * ageX+7等价于ageY * 2≤ageX+14。由于当 ageX 增加 1 时,ageY 至多增加 1,所以滑动窗口的内层 while 循环至多循环一次,可以改成 if 语句。注:年龄可以从 15 开始枚举,但考虑到如果题目条件改了,就不适用了,所以简单起见,从 0 开始枚举。
class Solution {
public:
int numFriendRequests(vector<int>& ages) {
int cnt[121]{};
for (int age : ages) {
cnt[age]++;
}
int ans = 0, cnt_window = 0, age_y = 0;
for (int age_x = 0; age_x < 121; age_x++) {
cnt_window += cnt[age_x];
if (age_y * 2 <= age_x + 14) { // 不能发送好友请求,则窗口向右滑动
cnt_window -= cnt[age_y];
age_y++;
}
if (cnt_window > 0) { // 存在可以发送好友请求的用户
ans += cnt[age_x] * cnt_window - cnt[age_x];
}
}
return ans;
}
};
206. 反转链表
思路:递归、迭代法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *pre = nullptr ,*cur = head;
while(cur){
ListNode *tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head->next) return head;
ListNode* last = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return last;
}
};
92. 反转链表 II
思路:在翻转链表I的基础上
翻转l-r部分肯定要记下
l的前一个节点和r的后一个节点。同时由于可能从头开始翻转,定义虚拟头结点dummyHead,令p0 = dummyHead,走l-1步后来到l的前一个节点,此时进行r - l 次翻转即可翻转l-r部分,且最终pre停在翻转后的头结点,cur指向r的后一个节点,此时p0->next的链还没断,只需将p0->next->next指向cur,再将p0->next指向pre。
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummyHead = new ListNode(0,head);
ListNode* p0 = dummyHead;
//p0从dummyHead走l - 1步来到待翻转部分的前一个节点
for (int i = 0; i < left - 1; i++) {
p0 = p0->next;
}
ListNode* pre = nullptr;
ListNode* cur = p0->next;
//翻转r - l 次,最后pre停在反转后的头结点上,cur停在下一个节点。
for (int i = 0; i < right - left + 1; i++) {
ListNode* nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
//此时已经l->r翻转完毕,l->next = nullptr但是p0->next并没有断
p0->next->next = cur;
p0->next = pre;
return dummyHead->next;
}
};
每日一题:661. 图片平滑器
思路:第一眼觉得似曾相识,可以扩充矩阵为(m + 2)* (n + 2),在外面加一圈0然后再算,不过边界条件还是很复杂,没判断出来。
法一:笨方法直接四重循环遍历
法二:前缀和
根据求前缀和数组公式:
sum[i + 1][j + 1] = sum[i][j + 1] + sum[i + 1][j] - sum[i][j] + img[i][j];
Sum(矩阵和) = s[x2][y2] - s[x2][y1- 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]对于某个
ans[i][j]而言,我们可以直接计算出其所在 item 的左上角(a,b)=(i−1,j−1)以及其右下角(c,d)=(i+1,j+1),同时为了防止超出原矩阵,我们需要将 (a,b) 与 (c,d) 对边界分别取 max 和 min。以上
x1 = a , y1 = b , x2 = c , y2 = d代入公式:Sum(矩阵和) = s[c][d] - s[c][b - 1] - s[a - 1][d] + s[a - 1][b - 1]当有了合法的 (a,b) 和 (c,d) 后,我们可以直接计算出 item 的单元格数量(所包含的行列乘积)及 item 的单元格之和(前缀和查询),从而算得
ans[i][j]。因为sum数组从1开始,结果ans从0开始,故所有值加1.
int cnt = sum[c + 1][d + 1] - sum[a][d + 1] - sum[c + 1][b] + sum[a][b];
class Solution {
public:
vector<vector<int>> imageSmoother(vector<vector<int>>& img) {
int m = img.size(), n = img[0].size();
vector<vector<int>> ret(m, vector<int>(n));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int num = 0, sum = 0;
for (int x = i - 1; x <= i + 1; x++) {
for (int y = j - 1; y <= j + 1; y++) {
if (x >= 0 && x < m && y >= 0 && y < n) {
num++;
sum += img[x][y];
}
}
}
ret[i][j] = sum / num;
}
}
return ret;
}
};
class Solution {
public:
vector<vector<int>> imageSmoother(vector<vector<int>>& img) {
int m = img.size();
int n = img[0].size();
vector<vector<int>> sum(m + 2 , vector<int>(n + 2 , 0));
for (int i = 0; i < m ; i++) {
for (int j = 0; j < n ; j++) {
sum[i + 1][j + 1] = sum[i][j + 1] + sum[i + 1][j] - sum[i][j] + img[i][j];
}
}
vector<vector<int>> res(m , vector<int>(n , 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int a = max(0 , i - 1);//左
int b = max(0 , j - 1);//上
int c = min(m - 1, i + 1);//下
int d = min(n - 1 , j + 1);//右
int area = (c - a + 1) * (d - b + 1);//计算面积,除数
int cnt = sum[c + 1][d + 1] - sum[a][d + 1] - sum[c + 1][b] + sum[a][b];
res[i][j] = cnt / area;
}
}
return res;
}
};






浙公网安备 33010602011771号