差点又要掉分了,还好最后几分钟的时候,绝杀 AK。干巴爹!!!
第一题:思路 + 模拟暴力。
第二题:线性扫描。
第三题:双指针(滑动窗口) + 优先队列。
第四题:暴力每一行最小 k 个 + 优先队列 或者 直接二分数组和值。
详细题解如下。
1.旅行终点站
AC代码(C++)
2. 是否所有 1 都至少相隔 k 个元素
AC代码(C++)
3.绝对差不超过限制的最长连续子数组
AC代码(C++)
4.有序矩阵中的第 k 个最小数组和
AC代码(方法一 暴力 + 优先队列 C++)
AC代码(方法二 二分 C++)
LeetCode第187场周赛地址:
https://leetcode-cn.com/contest/weekly-contest-187/
https://leetcode-cn.com/problems/destination-city/
给你一份旅游线路图,该线路图中的旅行线路用数组 paths 表示,其中 paths[i] = [cityAi, cityBi] 表示该线路将会从 cityAi 直接前往 cityBi 。请你找出这次旅行的终点站,即没有任何可以通往其他城市的线路的城市。
题目数据保证线路图会形成一条不存在循环的线路,因此只会有一个旅行终点站。
示例 1:
输入:paths = [["London","New York"],["New York","Lima"],["Lima","Sao Paulo"]] 输出:"Sao Paulo" 解释:从 "London" 出发,最后抵达终点站 "Sao Paulo" 。本次旅行的路线是 "London" -> "New York" -> "Lima" -> "Sao Paulo" 。
示例 2:
输入:paths = [["B","C"],["D","B"],["C","A"]] 输出:"A" 解释:所有可能的线路是: "D" -> "B" -> "C" -> "A". "B" -> "C" -> "A". "C" -> "A". "A". 显然,旅行终点站是 "A" 。
示例 3:
输入:paths = [["A","Z"]] 输出:"Z"
提示:
1 <= paths.length <= 100
paths[i].length == 2
1 <= cityAi.length, cityBi.length <= 10
cityAi != cityBi
所有字符串均由大小写英文字母和空格字符组成。
根据题意,我们知道终点就是,一个城市无法到达其他城市。
因此,我们统计每一个城市可以是否可以到达其他城市
那么我们可以使用 map,记录一个城市,可以到达其他城市的数量(可以是重复达到,没关系,只要达到其他城市即可)
那么这样子,我们还需要统计总共有几个不同城市(为了后面可以枚举所有城市,是否可以到达其他城市),那么这个可以用 set
class Solution {
public:
string destCity(vector>& paths) {
unordered_map mp;
unordered_set diffCitys;
mp.clear();
diffCitys.clear();
// 统计,每一个城市,可以达到其他城市的数量(无法达到,那就是 0),同时统计不同城市的数量
for(auto p : paths)
{
++mp[p[0]];
diffCitys.insert(p[0]);
diffCitys.insert(p[1]);
}
// 枚举每一个城市,当一个城市无法到达其他城市,那么这个城市就是 终点
for(auto c : diffCitys)
{
if(mp[c] == 0) return c;
}
return "";
}
};
https://leetcode-cn.com/problems/check-if-all-1s-are-at-least-length-k-places-away/
给你一个由若干
0
和1
组成的数组nums
以及整数k
。如果所有1
都至少相隔k
个元素,则返回True
;否则,返回False
。示例 1:
【示例有图,具体可以看链接】 输入:nums = [1,0,0,0,1,0,0,1], k = 2 输出:true 解释:每个 1 都至少相隔 2 个元素。
示例 2:
【示例有图,具体可以查看链接】 输入:nums = [1,0,0,1,0,1], k = 2 输出:false 解释:第二个 1 和第三个 1 之间只隔了 1 个元素。
提示:
1 <= nums.length <= 10^5
0 <= k <= nums.length
nums[i]
的值为0
或1
根据题意,我们只要每一次遇到 1 ,那么就计算其与上一个 1 相差的间隔。
那么这样子,我们就需要去记录上一个 1 的位置,这样子就可以实现 线性扫描
比如 示例 1 的
[1,0,0,0,1,0,0,1]
先找到第一个 1 的下标 0
然后线性扫描,第二个 1 下标是 4 (此时上一个 1 的下标是 0)
然后此时往后继续扫描,而 上一个 1 的位置进行更新 为 4
第三个 1 的下标是 7(上一个 1 的下标是 4)
class Solution {
public:
bool kLengthApart(vector& nums, int k) {
int last = -1; // 记录上一个 1 的位置
int n = nums.size();
for(int i = 0; i < n; ++i)
{
if(nums[i] == 1)
{
last = i;
break;
}
}
for(int i = last + 1;i < n; ++i)
{
if(nums[i] == 1)
{
if(i - last - 1 < k) return false;
last = i; // 更新
}
}
return true;
}
};
https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/
给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。
如果不存在满足条件的子数组,则返回 0 。
示例 1:
输入:nums = [8,2,4,7], limit = 4 输出:2 解释:所有子数组如下: [8] 最大绝对差 |8-8| = 0 <= 4. [8,2] 最大绝对差 |8-2| = 6 > 4. [8,2,4] 最大绝对差 |8-2| = 6 > 4. [8,2,4,7] 最大绝对差 |8-2| = 6 > 4. [2] 最大绝对差 |2-2| = 0 <= 4. [2,4] 最大绝对差 |2-4| = 2 <= 4. [2,4,7] 最大绝对差 |2-7| = 5 > 4. [4] 最大绝对差 |4-4| = 0 <= 4. [4,7] 最大绝对差 |4-7| = 3 <= 4. [7] 最大绝对差 |7-7| = 0 <= 4. 因此,满足题意的最长子数组的长度为 2 。
示例 2:
输入:nums = [10,1,2,4,7,2], limit = 5 输出:4 解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。
示例 3:
输入:nums = [4,2,2,2,4,4,2,2], limit = 0 输出:3
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
0 <= limit <= 10^9
根据题意,我们先对 示例 1 进行分析
nums = [8,2,4,7], limit = 4
一开始范围是 [8],满足
然后 想继续扩大 [8, 2],发现加入 2 后就不满足了
因此,范围要变小 [2]
接着,继续扩大 [2, 4]
[2, 4, 7] 也就不满足了,那就就想着减少
[4, 7]
所以我们发现,其实相当于一个双指针,l 和 r 圈出了一个范围 [l, r],那么一开始,我们只想要 r 不断的变大,变大到 [l, r] 不再满足 时,r 就定了。然后 l 增大(因为 [l, r] 已经是最大了,同时 r 不能再扩大,那么就考虑 l 变大,区间变小,那么 后面 r 可以考虑继续变大)
所以发现,l 和 r 是单调增加,也就是遍历一次数组
那么,当 [l, r] 的时候,我们要知道 这个区间内的最大值和最小值
我考虑使用 优先队列 来记录
也就是,每一次 r 增大,我们就往优先队列中放数
当 l 增大,考虑其中的 最大值 和 最小值的 下标一定要再 [l, r] 这个范围里。
因此,优先队列中存的是一个 二元数,(val, index),下标 index 对应的 值 val。(其中,最大和最小是按 val 来排的)
时间复杂度是 O(n * logn)
#define pii pair
#define mk(x, y) make_pair(x, y)
class Solution {
public:
int longestSubarray(vector& nums, int limit) {
priority_queue, less > mx; // 放最大,因此要最大值在队列头
priority_queue, greater > mi;
int ans = 0;
int n = nums.size();
int r = 0;
mx.push(mk(nums[0], 0));
mi.push(mk(nums[0], 0));
for(int i = 0;i < n; ++i) // 遍历 [i, r],遍历 左端点
{
// 要取出,最大值 和 最小值,一定要保证下标在这个范围里,不在范围里,就去掉
while(!mx.empty() && mx.top().second < i) mx.pop();
while(!mi.empty() && mi.top().second < i) mi.pop();
// 如果队列都去完了,说明 [i, r] 中 i == r,那么此时是单独 i 这个下标的数应该在队列中
if(mx.empty()) mx.push(mk(nums[i], i));
if(mi.empty()) mi.push(mk(nums[i], i));
int mxVal = mx.top().first, miVal = mi.top().first;
while(r + 1 < n) // 尝试 尽可能把 r 增大
{
mxVal = max(mxVal, nums[r + 1]);
miVal = min(miVal, nums[r + 1]);
if(mxVal - miVal > limit) break;
mx.push(mk(nums[r + 1], r + 1));
mi.push(mk(nums[r + 1], r + 1));
++r;
}
// cout << i << " " << r << endl;
ans = max(ans, r - i + 1);
}
return ans;
}
};
https://leetcode-cn.com/problems/find-the-kth-smallest-sum-of-a-matrix-with-sorted-rows/
给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。
你可以从每一行中选出 1 个元素形成一个数组。返回所有可能数组中的第 k 个 最小 数组和。
示例 1:
输入:mat = [[1,3,11],[2,4,6]], k = 5 输出:7 解释:从每一行中选出一个元素,前 k 个和最小的数组分别是: [1,2], [1,4], [3,2], [3,4], [1,6]。其中第 5 个的和是 7 。
示例 3:
输入:mat = [[1,10,10],[1,4,5],[2,3,6]], k = 7 输出:9 解释:从每一行中选出一个元素,前 k 个和最小的数组分别是: [1,1,2], [1,1,3], [1,4,2], [1,4,3], [1,1,6], [1,5,2], [1,5,3]。其中第 7 个的和是 9 。
提示:
m == mat.length
n == mat.length[i]
1 <= m, n <= 40
1 <= k <= min(200, n ^ m)
1 <= mat[i][j] <= 5000
mat[i] 是一个非递减数组
方法一、暴力 + 优先队列
我们一开始想到的是,暴力方法,也就是考虑每一行所有可能的累加后的数
问题是,这样子可能到了最后一行,就最大情况 总共有 40 ^ 40 个 数
但是我们发现,从数据范围来看, k 的最大值 为 200
那么,其实我们每一行只考虑 保留 最小的 k 个,那么下一行中,总共有 m * k 个数,又再取出 其中 k 个。
这样子我们每一行,最多考虑 m * k 个数,然后取出 最小 k 个 来进行保存即可
因此,我们利用一个 优先队列(也就是 最大堆),队列头 是 最大值,此时已经上一行 有了 最小的 k 个数。
那么我们 不断的取 k 个,和 这一行组合,那么总共有 m * k 个数
当 不够 k 个时,我们一直 放进队列。当超过 k 个时,我们 和 队列头进行比较,如果 比队列头要小,说明这个数才是 最小的 k 个中一个,因此队列头 出队,这个数进队。
时间复杂度是,O(n * m * k * logk),我们要枚举每一个数,然后 队列中最多 k 个数,因此总的时间复杂度,根据数据范围,不会超时
方法二、二分
我们枚举 数组和 的值,最大范围和最小范围 为 [第一列所有和, 最后一列所有和]
那么我们二分,得到 mid 后,去判断,这个 mid 值,在比 mid 小的有几个数
从而不断的减少范围
最后直接是得到 第 k 小 的那个 数组和 值。
class Solution {
public:
int kthSmallest(vector>& mat, int k) {
priority_queue last, res; // (最大堆),队列头是最大值
int n = mat.size(), m = mat[0].size();
for(int i = 0;i < min(m, k); ++i) last.push(mat[0][i]); // 一开始,把 第一行 的前 k 个,放进队列
for(int i = 1;i < n; ++i)
{
int len = 0; // 记录此时 队列中的个数
while(!last.empty()) // 把上一行的每一个队列都出来
{
int cur = last.top();
last.pop();
for(int j = 0;j < m; ++j)
{
if(len < k)
{
res.push(cur + mat[i][j]);
++len;
}
else
{
if(res.top() > cur + mat[i][j])
{
res.pop();
res.push(cur + mat[i][j]);
}
}
}
}
while(!res.empty()) // 此时 last 是空,res 是有,但是我们last 是上一行的结果,因此把 res 的转移放到 last 中
{
int cur = res.top();
res.pop();
last.push(cur);
}
}
return last.top(); // 最后是 最小 k 个中的最大值,也就是 队列头
}
};
class Solution {
public:
vector>temp;
int m,n;
int kthSmallest(vector>& mat, int k) {
temp=mat;
m=mat.size(),n=mat[0].size();
int left=0,right=0;
for(int i=0;i>1;
int num=1;
dfs(mid,0,init,num,k);
if(num>=k) right=mid;
else left=mid+1;
}
return left;
}
void dfs(int mid,int index,int sum,int& num,int k){
if(sum>mid||index==m||num>k) return;
dfs(mid,index+1,sum,num,k);
for(int i=1;i