今天更新一场周赛, 信息量有点大, 大家可以收藏起来慢慢看 🤣
这场周赛总体难度适中, 难度上升曲线比较平滑, 值得一试, 同学们可以根据自己实际情况阅读~
[1436] 旅行终点站
题目难度: 简单
题目描述
给你一份旅游线路图,该线路图中的旅行线路用数组 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”
题目思考
- 题目虽然花里胡哨, 但似乎跟图没有关系?
- 是不是可以单纯的统计下城市?
解决方案
思路
- 使用两个集合, 表示所有城市和是起点的城市, 因为题目保证只有一个终点站, 所以其差集一定就是那个城市
复杂度
- 时间复杂度 O(N): 需要遍历所有城市
- 空间复杂度 O(N): 需要使用两个集合
代码
Python 3
class Solution:
def destCity(self, paths: List[List[str]]) -> str:
# 两个集合求差集, 一个是全部节点, 一个是有出度的节点
al, out = set(), set()
for p in paths:
al.add(p[0])
al.add(p[1])
out.add(p[0])
return (al - out).pop()
C++
class Solution {
public:
string destCity(vector<vector<string>>& paths) {
unordered_set<string> all, out;
for (auto& entry : paths) {
all.insert(entry[0]);
all.insert(entry[1]);
out.insert(entry[0]);
}
for (auto& s : all) {
if (out.find(s) == out.end()) {
return s;
}
}
return {};
}
};
[1437] 是否所有 1 都至少相隔 k 个元素
题目难度: 中等
题目描述
给你一个由若干 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 个元素。
示例 3
输入
nums = [1,1,1,1,1], k = 0
输出
true
题目思考
- 题目很直白, 依次遍历可行吗?
解决方案
思路
- 记录上一个 1 的下标, 然后逐个遍历, 遇到 1 之后检查间隔, 同时更新 1 的下标
复杂度
- 时间复杂度 O(N): 需要遍历所有元素
- 空间复杂度 O(1): 只使用了几个变量
代码
Python 3
class Solution:
def kLengthApart(self, nums: List[int], k: int) -> bool:
# 记录上一个1的下标即可, 注意下标之差要大于k+1而不是k, 因为是间隔k
last1 = None
for i, n in enumerate(nums):
if n == 1:
if last1 is None or i - last1 >= k + 1:
last1 = i
else:
return False
return True
C++
class Solution {
public:
bool kLengthApart(vector<int>& nums, int k) {
int last1 = -1;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i]) {
if (last1 >= 0 && i - last1 < k + 1) {
return false;
}
last1 = i;
}
}
return true;
}
};
[1438] 绝对差不超过限制的最长连续子数组
题目难度: 中等
题目描述
给你一个整数数组 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
思路
- 滑动窗口+优先队列(最大最小堆)
- 某区间任意两个元素之间的绝对差最大值等价于该区间最大值-最小值
- 所以只需要维护两个优先队列, 一个存当前区间的最大值+下标(最大堆), 一个存最小值+下标(最小堆)即可
- 存下标的目的是窗口左边界往右滑动时移除无效元素
复杂度
- 时间复杂度 O(N*log(N)): 每个元素只会遍历两遍, 然后堆本身 push 和 pop 需要 logN 的复杂度
- 空间复杂度 O(N): 使用了两个堆
代码
Python 3
import heapq
class Solution:
def longestSubarray(self, nums: List[int], limit: int) -> int:
l, r = 0, 0
mx, mn = [], []
res = 0
while r < len(nums):
heapq.heappush(mx, (-nums[r], r))
heapq.heappush(mn, (nums[r], r))
while -mx[0][0] - mn[0][0] > limit:
# 加上右边界后不满足要求了, 左边界一直往右移动, 直到继续满足要求
# 同时需要更新两个堆, 如果堆顶元素的下标小于等于当前左边界的话
while mx and mx[0][1] <= l:
heapq.heappop(mx)
while mn and mn[0][1] <= l:
heapq.heappop(mn)
l += 1
res = max(res, r - l + 1)
r += 1
return res
C++
class Solution {
public:
int longestSubarray(vector<int>& nums, int limit) {
int res = 0;
multimap<int, int, less<int>> incr;
multimap<int, int, greater<int>> desc;
for (int l = 0, r = 0; r < nums.size(); ++r) {
incr.emplace(nums[r], r);
desc.emplace(nums[r], r);
while(desc.begin()->first - incr.begin()->first > limit) {
while (!incr.empty() && incr.begin()->second <= l) {
incr.erase(incr.begin());
}
while (!desc.empty() && desc.begin()->second <= l) {
desc.erase(desc.begin());
}
++l;
}
res = max(res, r - l + 1);
}
return res;
}
};
方案 2
思路
- 回顾方案 1, 两个堆存在的意义只是为了拿到它们的最大值和最小值, 那么是不是可以用单调双向队列来替代它呢, 这样入队出队的复杂度就变成 O(1)了, 相比方案 1 会快不少
- 在滑动窗口的基础上, 只需要维护两个双端队列, 一个递增一个递减
- 新的元素放在队列末尾, 然后需要左边界移动的时候只用检查队列开头即可
- 这里额外带来一个好处是不再需要存下标了, 因为队列最左边的就是最老的, 移动左边界时直接比较左边的值即可
复杂度
- 时间复杂度 O(N): 每个元素只需要遍历四次, 两次入队两次出队
- 空间复杂度 O(N): 两个双端队列
代码
Python 3
from collections import deque
class Solution:
def longestSubarray(self, nums: List[int], limit: int) -> int:
l, r = 0, 0
inc, desc = deque(), deque()
res = 0
while r < len(nums):
while inc and inc[-1] > nums[r]:
inc.pop()
inc.append(nums[r])
while desc and desc[-1] < nums[r]:
desc.pop()
desc.append(nums[r])
while desc[0] - inc[0] > limit:
# 加上当前右边界后不满足要求了, 左边界一直往右移动, 直到继续满足要求
# 同时需要更新两个队列, 如果队列头恰好是当前左边界的话
if inc[0] == nums[l]:
inc.popleft()
if desc[0] == nums[l]:
desc.popleft()
l += 1
res = max(res, r - l + 1)
r += 1
return res
C++
class Solution {
public:
int longestSubarray(vector<int>& nums, int limit) {
int res = 0;
deque<int> incr, desc;
for (int l = 0, r = 0; r < nums.size(); ++r) {
while (!incr.empty() && incr.back() > nums[r]) {
incr.pop_back();
}
incr.push_back(nums[r]);
while (!desc.empty() && desc.back() < nums[r]) {
desc.pop_back();
}
desc.push_back(nums[r]);
while (desc.front() - incr.front() > limit) {
if (incr.front() == nums[l]) {
incr.pop_front();
}
if (desc.front() == nums[l]) {
desc.pop_front();
}
++l;
}
res = max(res, r - l + 1);
}
return res;
}
};
[1439] 有序矩阵中的第 k 个最小数组和
题目难度: 困难
这道题的基础版本是[373] 查找和最小的 K 对数字, 如果感觉这道题有难度的话可以先从那道题入手
题目描述
给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。
你可以从每一行中选出 1 个元素形成一个数组。返回所有可能数组中的第 k 个 最小 数组和。
- m == mat.length
- n == mat.length[i]
- 1 <= m, n <= 40
- 1 <= k <= min(200, n ^ m)
- 1 <= mat[i][j] <= 5000
- mat[i] 是一个非递减数组
题目样例
示例 1
输入
mat = [[1,3,11],[2,4,6]], k = 5
输出
7
解释
从每一行中选出一个元素,前 k 个和最小的数组分别是:
[1,2], [1,4], [3,2], [3,4], [1,6]。其中第 5 个的和是 7 。
示例 2
输入
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 。
示例 3
输入
mat = [[1,3,11],[2,4,6]], k = 9
输出
17
题目思考
- 第一个和毫无疑问是每一行的第一个元素相加所得
- 那第二个和呢, 它的候选项有哪些?
- 是否可以将矩阵看成一个图来处理, 使用一些图的算法?
解决方案
思路
- BFS+优先队列/小顶堆存(sm, 下标元组)元组
- BFS 是为了遍历下个可能的候选项, 需要配合 visit 集合使用
- 使用一个下标元组, 初始化每一行的列下标都为 0
- 然后每次循环时遍历所有的行, 检查该行的列号+1 后的下标元组是否已经在 visit 集合中
- 没在的话将其新的和(可以根据当前和+新加入的元素-移除的元素得到), 以及下标元组加入优先队列中
- 在的话就说明它已经被加入优先队列了, 忽略它避免重复计算
- 循环 k-1 次后, 此时堆顶的 sm 值即为所求
复杂度
- 时间复杂度 O(kmlog(km)): 总共 k 次遍历, 然后每次遍历需要遍历所有行(m), 堆操作的复杂度为 log(km)
- 空间复杂度 O(km^2): 堆中每个元素大小为 m, 然后堆元素的个数为 km
代码
Python 3
class Solution:
def kthSmallest(self, mat: List[List[int]], k: int) -> int:
rows, cols = len(mat), len(mat[0])
sm = sum(mat[r][0] for r in range(rows))
startIndices = (0, ) * rows
q = [(sm, startIndices)]
v = {startIndices}
for i in range(2, k + 1):
cursm, indices = heapq.heappop(q)
for r in range(rows):
if indices[r] + 1 < len(mat[r]):
newCol = indices[r] + 1
newIndices = indices[:r] + (newCol, ) + indices[r + 1:]
if newIndices not in v:
newsm = cursm + mat[r][newCol] - mat[r][indices[r]]
v.add(newIndices)
heapq.heappush(q, (newsm, newIndices))
return q[0][0]
C++
class Solution {
public:
int kthSmallest(const vector<vector<int>>& mat, int k) {
int m = mat.size();
int n = mat[0].size();
multimap<int, vector<int>> q;
set<vector<int>> visited;
int sum = 0;
for (int i = 0; i < m; ++i) {
sum += mat[i][0];
}
q.emplace(sum, vector<int>(m, 0));
visited.insert(vector<int>(m, 0));
for (int i = 2; i <= k; ++i) {
auto sum = q.begin()->first;
auto indices = q.begin()->second;
q.erase(q.begin());
for (int r = 0; r < m; ++r) {
if (++indices[r] < n) {
if (visited.find(indices) == visited.end()) {
int newSum = sum + mat[r][indices[r]] - mat[r][indices[r]-1];
q.emplace(newSum, indices);
visited.insert(indices);
}
}
--indices[r];
}
}
return q.begin()->first;
}
};
我的公众号: 每日精选算法题, 欢迎大家扫码关注~😊