LeetCode weekly contest 196 第196期周赛
1· 5452. 判断能否形成等差数列
给你一个数字数组 arr 。
如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数列就称为 等差数列 。
如果可以重新排列数组形成等差数列,请返回 true ;否则,返回 false 。
示例 1:
输入:arr = [3,5,1]
输出:true
解释:对数组重新排序得到 [1,3,5] 或者 [5,3,1] ,任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。
示例 2:
输入:arr = [1,2,4]
输出:false
解释:无法通过重新排序得到等差数列。
2 <= arr.length <= 1000
-10^6 <= arr[i] <= 10^6
解析
第一题按照惯例是签到题,数据范围也不存在超时的可能,直接排序检查相邻元素的差是否相同即可
bool canMakeArithmeticProgression(vector<int>& arr) {
sort(arr.begin(), arr.end());
if (arr.size() == 2) return true;
int tmp = arr[1] - arr[0];
for (int i = 2; i < arr.size(); ++i) {
if (arr[i] - arr[i - 1] != tmp) return false;
}
return true;
}
2· 5453. 所有蚂蚁掉下来前的最后一刻
有一块木板,长度为 n 个 单位 。一些蚂蚁在木板上移动,每只蚂蚁都以 每秒一个单位 的速度移动。其中,一部分蚂蚁向 左 移动,其他蚂蚁向 右 移动。
当两只向 不同 方向移动的蚂蚁在某个点相遇时,它们会同时改变移动方向并继续移动。假设更改方向不会花费任何额外时间。
而当蚂蚁在某一时刻 t 到达木板的一端时,它立即从木板上掉下来。
给你一个整数 n 和两个整数数组 left 以及 right 。两个数组分别标识向左或者向右移动的蚂蚁在 t = 0 时的位置。请你返回最后一只蚂蚁从木板上掉下来的时刻。
解析
这道题更有一种脑筋急转弯的感觉。题目给定一块木板上面哟一些蚂蚁,要么向左要么向右在走,相遇的话双方调转方向继续走,直到走到边缘(0或n的位置)从木板上下来,求最后一只掉下来的时间。
本题乍一看挺复杂,是不是要分析每个时刻蚂蚁的状态,看看蚂蚁何时相遇反向这些的,但细细一想其中另有玄机。
不妨假设蚂蚁1和2相对而行,1向左,2向右,在位置x处相遇了,按照题设,二者应该各自掉头继续走,也就是从x处1向右走,2向左走。但如果我们不区分这些蚂蚁呢。相遇前一只向左一只向右走到了x处,相遇后一只向左一只向右继续从x接着走,这和掉不掉头根本没区别啊。
不妨假设蚂蚁不会掉头,他们都是虚空生物,相遇时穿过彼此的身体继续走,可以发现这种情况蚂蚁任一时刻的分布以及行动方向跟题设完全没区别。
有朋友可能对此疑惑,会不会有两只蚂蚁,其中一只走了很长的路遇到另一只,被迫掉头,所以用的时间比我们假设的更长。实际上蚂蚁速度是一致的,两只蚂蚁相遇点一定在二者起始位置的中点。
所以想明白这个,题目就很简单了,不整那些花里胡哨的,向左的蚂蚁排着队从右边掉下去,向右的从最左边掉下去,只要看着两拨蚂蚁最后掉下去的那只走多远就够了。
int getLastMoment(int n, vector<int>& left, vector<int>& right) {
int targetL = 0, targetR = n;
sort(left.begin(), left.end());
sort(right.begin(), right.end());
if (left.empty() && right.empty()) return 0;
int l = INT_MIN, r = INT_MIN;
if (!left.empty()) {
int tmp = left.back();
l = tmp - targetL;
}
if (!right.empty()) {
int tmp = right[0];
r = targetR - tmp;
}
return max(l, r);
}
分别排序(不排序也可以)找到向左的蚂蚁最右边那只到0的距离,向右的蚂蚁最左边那只到n的距离,二者取最大值即可(蚂蚁速度是1,所以距离和时间在数值上一致)
3· 5454. 统计全 1 子矩形
给你一个只包含 0 和 1 的 rows * columns 矩阵 mat ,请你返回有多少个 子矩形 的元素全部都是 1 。
解析
本题和经典的01矩形中全是1的最大子矩形面积有点像,让人不由得惊呼需要构造一个累积1的子矩形。
其实我一开始认为本题可以有二维dp(n^2)做,想了半天也没做出来,本题评论区有大佬是这么做的,我还没想通,暂时不放这种解法了(大概意思是以i,j为右下角的全1矩形数量,可以有i,j-1/i-1,j/i-1,j-1等等推导)
本题暴力解法也是通过把以每个点作为右下角的矩形数量加起来,显然以(i,j)作为右下角(左上角最远到(0,0))的矩形数量有i*j个,遍历所有点复杂度就是
O
(
N
∗
N
∗
M
∗
M
)
O(N*N*M*M)
O(N∗N∗M∗M)这样,这里可以优化掉一项。
通过构造left矩阵,这里矩阵
l
e
f
t
[
i
]
[
j
]
left[i][j]
left[i][j]表示第
i
i
i行第
j
j
j列元素向左延伸累积有多少个1,
一下面矩阵为例:
0
1
1
1
0
1
1
1
1
1
1
0
\begin{matrix} 0 & 1 & 1& 1 \\ 0 & 1 & {\color{red}1} &1\\ 1 & 1 & {\color{blue}1}&0 \end{matrix}
001111111110
构造left如下:
0
1
2
0
0
1
2
3
1
2
3
0
\begin{matrix} 0&1&2&0\\ 0&1& {\color{red}2}&3\\ 1&2& {\color{blue}3}&0 \end{matrix}
001112223030
也就是当前元素是1,如果左边还有元素,那么就是他左边元素left值加1,否则就是1;当前是0,left值就是0.
left的作用就在于,当我们想知道一个元素作为右下角的矩形数量,需要向上和向左扩展,比如考虑当前行向左最多能构成有几个矩形,就查一下当前向左有几个连续的1即可,向上扩展多行的情况,要考虑这些行最少的向左延伸1的数量。每向上扩展一行,就计算这个行数、当前元素右下角能向左延伸几个1,这个数是几,就说明以这个元素左右下角且当前行数的矩阵数量有几个,累加即可。
以上述矩阵标红的位置为例,我想以他作为右下角元素,想看看有多少全1 的矩形。
首先这个元素left值为2,说明以他为右下角只向左扩展(1行的矩形)有两个,接下来向上扩展一行,考虑用红色元素为右下角,行数为2的矩形数量,考察Left[i-1][j](上面的元素)发现值也是2,也就是2行的情况也是2个,再向上扩展没有了,所以红色元素为右下角的全1矩形是4个,显然我们直接数的话1x1,1x2,2x1,2x2各一个总数为4;
在考虑蓝色元素为右下角的全1矩形数量,只有一行的情况,看他自己的left,3个,向上一行,上面元素left只有2个了,这是我们要取最小的,因为上一行最左边是0构不成2x3的全1矩形,所以是2,再上一行,还是2,总数是7.1x1,1x2,1x3,2x1,3x1,2x2,3x2
这种思路在查找以某元素为右下角时,只需要查看这个位置向上各个位置即可,不需要按列也遍历。
int numSubmat(vector<vector<int>>& mat) {
int m = mat.size(), n = mat[0].size();
vector<vector<int>> left(m, vector<int>(n,0));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (mat[i][j] == 1) {
if (j == 0) left[i][j] = 1;
else left[i][j] = left[i][j - 1] + 1;
}
else left[i][j] = 0;
}
}
int ans = 0, minx;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
minx = INT_MAX;
for (int k = i; k >= 0; --k) {
minx = min(left[k][j], minx);
ans += minx;
}
}
}
return ans;
}
4· 5455. 最多 K 次交换相邻数位后得到的最小整数
给你一个字符串 num 和一个整数 k 。其中,num 表示一个很大的整数,字符串中的每个字符依次对应整数上的各个 数位 。
你可以交换这个整数相邻数位的数字 最多 k 次。
请你返回你能得到的最小整数,并以字符串形式返回。
解析
本题实际上算是经典问题改了一下下。原题是交换相邻数位最多k次,能得到最大的数。这里改成了最小,同时数据范围更改了一下。
本题可以用贪心思路求解。遇到一个元素,位置为i,向右查找(不超过k的距离)找到可达范围内最小的那个,位置为minPos,一个一个依次交换过来,消耗minPos-i次交换次数。实际上有点类似冒泡排序,只不过交换次数有上限,到了就得停。
这里要注意,本题有一个比较坑的地方,按照上述思路是
O
(
N
2
)
O(N^2)
O(N2)的复杂度,而输入数字位数长度最长是30000,那就是9*10^8量级,会超时;k最大是
1
0
9
10^9
109。我超时了好几次,最后发现,两个最大数出现在一个case的时候,k恰好可以保证num所有元素两两交换一次,那就是能实现完全排序后的结果。所以,本题开始时候要加上判断k是否支持全部元素的两两交换,如果支持,直接排序返回就行了,这样就不会超时。
string minInteger(string num, int k) {
int len = num.size();
if (k > len* (len - 1) / 2) {
sort(num.begin(), num.end());
return num;
}
int i = 0, j, minPos;
bool isfind;
char minC;
for (i = 0; i < len - 1 && k; ++i) {
minC = num[i];
if (minC == '0') continue;
minPos = i;
isfind = false;
for (j = i + 1; j < i + 1 + k && j < len; ++j) {
if (num[j] < minC) {
minC = num[j];
minPos = j;
isfind = true;
}
}
if (isfind) {
for (int q = minPos; q > i; --q) {
char tmp = num[q];
num[q] = num[q - 1];
num[q - 1] = tmp;
}
k = k - minPos + i;
}
}
return num;
}
如上述代码所示,先判断能不能直接排序,能的话排序返回,不能就继续。
遍历除最后一个元素以外的前面元素,或k大于0(仍有交换次数)
记录当前元素,从当前元素后一个开始,在不越界且不超过能使用的交换次数范围内,找一个最小元素,记录其位置,然后在i 和 minPos之间两两交换,吧最小值换到i位置上,减去消耗的交换次数,直到k用尽(k没用完就遍历完的情况就是上述直接排序)返回此时的字符串。
本题重点是贪心的思路,尽可能把尽量小的元素换到尽量高的位上去。