一、题目
1、题目描述
给你一个大小为
m x n
的二维整数网格grid
和一个整数x
。每一次操作,你可以对grid
中的任一元素 加x
或 减x
。单值网格 是全部元素都相等的网格。返回使网格化为单值网格所需的 最小 操作数。如果不能,返回-1
。
样例输入:grid = [[2,4],[6,8]], x = 2
样例输出:4
2、基础框架
- C语言 版本给出的基础框架代码如下:
int minOperations(int** grid, int gridSize, int* gridColSize, int x){
}
3、原题链接
二、解题报告
1、思路分析
(
1
)
(1)
(1) 把所有的数字都先塞到一个数组中,然后对数字进行递增排序。于是问题转变成了求一个 target
,所有数和 target
差除上 x
的和最小。
(
2
)
(2)
(2) 所有数和 target
差必须是x
的倍数,所以所有数必须和target
模x
同余。如果一旦出现不同余,返回-1
。
(
3
)
(3)
(3) 由于target
的值在1到10000之间,所以可以预先筛选出来所有和每个数同余的target
的值。
(
4
)
(4)
(4) 通过样例可得,target
满足极值性质,也就是随着target
的从小到大,返回的答案 会先变小,再变大。所以可以利用三分枚举target
判断可行性。
(
5
)
(5)
(5) 对于一个有极值的函数
y
=
f
(
x
)
y = f(x)
y=f(x),
x
x
x 的区间范围是
[
l
,
r
]
[l, r]
[l,r],那么对于
l
m
i
d
=
2
l
+
r
3
lmid = \frac {2l+r}{3}
lmid=32l+r,
r
m
i
d
=
l
+
2
r
3
rmid = \frac {l+2r}{3}
rmid=3l+2r。
如果
f
(
l
m
i
d
)
≤
f
(
r
m
i
d
)
f(lmid) \le f(rmid)
f(lmid)≤f(rmid),说明极值不可能在
[
l
,
l
m
i
d
]
[l, lmid]
[l,lmid] 之间,所以
r
=
r
m
i
d
−
1
r = rmid - 1
r=rmid−1;
如果
f
(
l
m
i
d
)
>
f
(
r
m
i
d
)
f(lmid) > f(rmid)
f(lmid)>f(rmid),说明极值不可能在
[
r
m
i
d
,
r
]
[rmid, r]
[rmid,r] 之间,所以
l
=
l
m
i
d
+
1
l = lmid + 1
l=lmid+1;
这就是三分,三分的过程中,不断更新答案,最后返回那个答案。
2、时间复杂度
最坏时间复杂度 O ( n l o g 3 n ) O(nlog_3n) O(nlog3n) 。
3、代码详解
int cmp(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
int getSum(int *ans, int ansSize, int target, int x) {
int i;
int ret = 0;
for(i = 0; i < ansSize; ++i) {
ret += abs( target - ans[i] ) / x;
}
return ret;
}
int min(int a, int b) {
if(a == -1) return b;
if(b == -1) return a;
return a < b ? a : b;
}
int minOperations(int** grid, int gridSize, int* gridColSize, int x){
int m = gridSize;
int n = gridColSize[0];
int size = m * n;
int i, j;
int can[10010], canSize = 0;
int l, r, mid;
int *ans = (int *)malloc( sizeof(int) * size );
size = 0;
for(i = 0; i < m; ++i) {
for(j = 0; j < n; ++j) {
ans[size++] = grid[i][j];
}
}
qsort(ans, size, sizeof(int), cmp);
for(i = 1; i < size; ++i) {
if(ans[i]%x != ans[i-1]%x) {
return -1;
}
}
// + + + + + + + - - - - - - - - -
// + + + + - - - - - - - - - - - -
// + + - - - - - - - - - - - - - -
for(i = 0; i <= 10000; ++i) {
if(i % x == ans[0] % x) {
can[canSize++] = i;
}
}
l = 0;
r = canSize - 1;
int ret = -1;
while(l <= r) {
int lmid = (2*l + r) / 3;
int rmid = (l + r*2) / 3;
int lsum = getSum(ans, size, can[lmid], x);
int rsum = getSum(ans, size, can[rmid], x);
if(lsum < rsum) {
r = rmid - 1;
}else {
l = lmid + 1;
}
ret = min(ret, lsum);
ret = min(ret, rsum);
}
return ret;
}
/*
1、先给抽象到一个数组中
2、排序
3、a b c d e f g
假设 所有数变成 target 能够使得步数最小
a + x * a0 = target
b + x * b0 = target
...
g - x * g0 = target
1 2 3 4 5
1 -> 0 + 1 + 2 + 3 + 4 = 10
2 -> 1 + 0 + 1 + 2 + 3 = 7
3 -> 2 + 1 + 0 + 1 + 2 = 6
4 -> 3 + 2 + 1 + 0 + 1 = 7
5 -> 4 + 3 + 2 + 1 + 0 = 10
*/
三、本题小知识
一定还有更加简单的方案。
四、加群须知
相信看我文章的大多数都是「 大学生 」,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,如果你还是「 大一 」,那么太好了,你拥有大把时间,当然你可以选择「 刷剧 」,然而,「 学好算法 」,三年后的你自然「 不能同日而语 」。
那么这里,我整理了「 几十个基础算法 」 的分类,点击开启:
如果链接被屏蔽,或者有权限问题,可以私聊作者解决。
大致题集一览:
为了让这件事情变得有趣,以及「 照顾初学者 」,目前题目只开放最简单的算法 「 枚举系列 」 (包括:线性枚举、双指针、前缀和、二分枚举、三分枚举),当有 一半成员刷完 「 枚举系列 」 的所有题以后,会开放下个章节,等这套题全部刷完,你还在群里,那么你就会成为「 夜深人静写算法 」专家团 的一员。
不要小看这个专家团,三年之后,你将会是别人 望尘莫及 的存在。如果要加入,可以联系我,考虑到大家都是学生, 没有「 主要经济来源 」,在你成为神的路上,「 不会索取任何 」。
🔥联系作者,或者扫作者主页二维码加群,加入刷题行列吧🔥
🔥让天下没有难学的算法🔥
C语言免费动漫教程,和我一起打卡! 🌞《光天化日学C语言》🌞
让你养成九天持续刷题的习惯 🔥《九日集训》🔥
入门级C语言真题汇总 🧡《C语言入门100例》🧡
组团学习,抱团生长 🌌《算法零基础100讲》🌌
几张动图学会一种数据结构 🌳《画解数据结构》🌳
竞赛选手金典图文教程 💜《夜深人静写算法》💜