周赛传送门
周六加班了,十一点多才下班,没来及参加。。
2006. 差的绝对值为 K 的数对数目
思路: 暴力枚举
时间复杂度:
枚举所有的数对,判断其差值是否符合要求。
class Solution {
public:
int countKDifference(vector<int>& nums, int k) {
int anw = 0;
for (int i = 0; i < nums.size(); i++) {
for (int j = i+1; j < nums.size(); j++) {
if (abs(nums[i] - nums[j]) == k) {
anw ++;
}
}
}
return anw;
}
};
2007. 从双倍数组中还原原数组
思路:双指针
时间复杂度:
根据定义,双倍数组必然满足下列条件:
- 有偶数个元素。
- 设 为其中最小的元素,则 必在 中。
- 删除 和 后,仍然是一个双倍数组。
因此,可先将 升序排序,然后不断的删除其中的最小值 以及 ,直到 为空。
如果 变为空,则说明是双倍数组,则删除过程中的 组成了 。反之,则说明 不是双倍数组。
class Solution {
public:
vector<int> findOriginalArray(vector<int>& changed) {
// 先判断就
if (changed.size()&1) {
return vector<int>();
}
// 排序
sort(changed.begin(), changed.end());
vector<int> anw; // 用于存放 original
anw.reserve(changed.size()/2);
// 双指针:i 用于寻找删除过程中的最小值 v; j 用于寻找 2v
// changed[i] = -1,则说明该位置的元素被删除了,反之则尚未删除。
for (int i = 0, j = 0; i < changed.size(); i++) {
if (changed[i] != -1) {
// v 是递增的,2v 也肯定是递增的,且 2v >= v。所以 j > i。
j = max(i+1, j);
while (j < changed.size() && changed[j] < changed[i]*2) {
j++;
}
// 没找到 2v,不是双倍数组
if (j == changed.size() || changed[j] != changed[i]*2) {
return vector<int>();
}
// 保存一下答案。
anw.push_back(changed[i]);
// 标记删除
changed[j] = -1;
}
}
return anw;
}
};
2008. 出租车的最大盈利
方法一
思路:枚举rides,区间查询
时间复杂度:, 为乘客数量, 为地点数量。
设有一维数组 , 表示从位置 到达 处的最大盈利。
首先将 按照终点升序排列。然后从前向后依次遍历每个 ,则有:
则最终答案为:
其中 可借助线段树,RMQ 等实现。下面给出一个基于线段树的实现。
class Solution {
public:
int64_t st[100001*4] = {0}; // 存储线段树的数组
// 尝试将位置 goal 更新为 val
void update(int root, int l, int r, int goal, int64_t val) {
// st[root] 记录了 区间 [l,r] 中的最大值
// l == r 表示到达叶子节点,且递归过程保证,l == r == goal。
if (l == r) {
// 可能重复更新,保留最大值
st[root] = max(st[root], val);
return;
}
// 判读一下 goal 在左子树对应的区间[l,mid]中,还是右子树对应的区间 [mid+1, r]中。更新对应子树即可。
int mid = (l+r)>>1;
(goal <= mid) ? update(root<<1, l, mid, goal, val) : update(root<<1|1, mid+1, r, goal, val);
// [l,mid] 或者 [mid+1,r] 可能发生了更新,那就更新下 [l,r] 吧。
st[root] = max(st[root<<1], st[root<<1|1]);
}
// 查询 [l,r] 中的最大值。
int64_t query(int root, int l, int r, int goal) {
if (r == goal) {
return st[root];
}
int mid = (l+r)>>1;
if (goal <= mid) {
return query(root<<1, l, mid, goal);
}
return max(query(root<<1, l, mid, mid), query(root<<1|1, mid+1, r, goal));
}
long long maxTaxiEarnings(int n, vector<vector<int>>& rides) {
// 线段树的三个变量:左边界,有边界,根节点编号
const int L = 1, R = 100000, root = 1;
// 将 rides 按照 end 升序排序
sort(rides.begin(), rides.end(), [](const auto &l, const auto &r) {
return l[0] < r[0];
});
// 遍历 rides
for(const auto &r : rides) {
// 找出 [1, r.start] 中的最大收益
int64_t val = r[1]-r[0]+r[2] + query(root, L, R, r[0]);
// 更新一下 r.end 处的最大收益
update(root, L, R, r[1], val);
}
// 查询最大收益
return query(root, L, R, R);
}
};
方法二
思路:枚举位置
时间复杂度:
设有一维数组 , 表示从位置 到达 处的最大盈利。
如果没有以位置 为终点的乘客,则 。
如果有一个或多个乘客,则有两种策略:
- 不接:
- 接一个最赚的:
则最终答案为 。
借助哈希表,可通过 的预处理,知道每个位置处有哪些乘客。然后,只需从前向后枚举位置,枚举过程中按照伤处策略计算即可。
class Solution {
public:
long long maxTaxiEarnings(int n, vector<vector<int>>& rides) {
unordered_map<int, vector<int>> pos;
for (int i = 0; i < rides.size(); i++) {
pos[rides[i][1]].emplace_back(i);
}
const int m = 1e5;
int64_t dp[m+1] = {0};
for (int i = 1; i <= m; i++) {
dp[i] = max(dp[i], dp[i-1]);
if (pos.count(i) > 0) {
for (auto p : pos[i]) {
int start = rides[p][0];
int earn = rides[p][1] - rides[p][0] + rides[p][2];
dp[i] = max(dp[start]+earn, dp[i]);
}
}
}
return dp[m];
}
};
2009. 使数组连续的最少操作数
思路:排序,去重,枚举右边界
时间复杂度:
将输入的 nums 去重并升序排序,那么我们得到了一个严格递增的数组。
假设我们将 作为连续数组的最大值,则易得连续数组的最小值为 。 输入数字的个数。
因为 严格递增,不难统计出 中,值位于 内的元素数量,记为 。则以 作为连续数组的最大值的操作数为 。最终答案即为:
为了证明解法的正确性,只需证明「一定存在一种最优的连续数组是以某个 为最大元素的」。
连续数组的最大元素无非有两种来源:
- 情形一:原本就存在于 中。
- 情形二: 中没有,是通过某次操作得来的。
对于情形一,无需多言。
对于情形二,可将最大元素替换为最小值减一,此时仍然是连续数组且操作次数没变,所以可得到一种同样优的且符合情形一的最优解。
考虑到去重排序后的 是递增的,可以通过双指针计算出所有的 。
class Solution {
public:
int minOperations(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
auto eit = unique(nums.begin(), nums.end());
nums.erase(eit, nums.end());
int anw = n;
for (int l = 0, r = 0; r < nums.size(); r++) {
while(nums[l] < nums[r] - n + 1) {
l++;
}
anw = min(anw, n-(r-l+1));
}
return anw;
}
};