Leetcode 第 122 场双周赛题解
- Leetcode 第 122 场双周赛题解
- 题目1:3010. 将数组分成最小总代价的子数组 I
- 思路
- 代码
- 复杂度分析
- 题目2:3011. 判断一个数组是否可以变为有序
- 思路
- 代码
- 复杂度分析
- 题目3:3012. 通过操作使数组长度最小
- 思路
- 代码
- 复杂度分析
- 题目4:3013. 将数组分成最小总代价的子数组 II
- 思路
- 代码
- 复杂度分析
Leetcode 第 122 场双周赛题解
题目1:3010. 将数组分成最小总代价的子数组 I
思路
给你一个长度为 n 的整数数组 nums 。
一个数组的代价是它的第一个元素。比方说,[1,2,3] 的代价是 1 ,[3,4,1] 的代价是 3 。
你需要将 nums 分成 3 个连续且没有交集的子数组。
请你返回这些子数组的最小代价总和 。
第一个代价一定是 nums[0],后面两个代价是 nums[1,…,n] 的最小值和次小值。
三个代价的和就是答案。
代码
/*
* @lc app=leetcode.cn id=3010 lang=cpp
*
* [3010] 将数组分成最小总代价的子数组 I
*/
// @lc code=start
class Solution
{
public:
int minimumCost(vector<int> &nums)
{
// 特判
if (nums.size() == 3)
return accumulate(nums.begin(), nums.end(), 0);
int n = nums.size();
// 第一个代价一定是 nums[0]
int cost = *nums.begin();
nums.erase(nums.begin());
// 后面两个代价是 nums[1,...,n] 的最小值和次小值
auto it = min_element(nums.begin(), nums.end());
cost += *it;
nums.erase(it);
it = min_element(nums.begin(), nums.end());
cost += *it;
return cost;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的元素个数。
空间复杂度:O(1)。
题目2:3011. 判断一个数组是否可以变为有序
思路
分组循环。
适用场景:按照题目要求,数组会被分割成若干组,每一组的判断/处理逻辑是相同的。
核心思想:
- 外层循环负责遍历组之前的准备工作(记录开始位置),和遍历组之后的工作(排序)。
- 内层循环负责遍历组,找出这一组最远在哪结束。
这个写法的好处是,各个逻辑块分工明确,也不需要特判最后一组(易错点)。
用分组循环找到一个组, 对这个组排序,最后判断整个数组是否有序。
代码
/*
* @lc app=leetcode.cn id=3011 lang=cpp
*
* [3011] 判断一个数组是否可以变为有序
*/
// @lc code=start
class Solution
{
public:
bool canSortArray(vector<int> &nums)
{
int n = nums.size();
int i = 0;
// 分组循环
while (i < n) // 外层循环
{
int ones = countOne(nums[i]);
int start = i;
// 内层循环
while (i < n && countOne(nums[i]) == ones)
i++;
// 循环结束后 nums[start,...,i) 是一个区间
sort(nums.begin() + start, nums.begin() + i);
}
return is_sorted(nums.begin(), nums.end());
}
// 辅函数 - 计算 x 在二进制下数位为 1 的数目
int countOne(int x)
{
int count = 0;
while (x)
{
count += x % 2;
x /= 2;
}
return count;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(nlogn),其中 n 是数组 nums 的元素个数。
空间复杂度:O(1)。
题目3:3012. 通过操作使数组长度最小
思路
代码
/*
* @lc app=leetcode.cn id=3012 lang=cpp
*
* [3012] 通过操作使数组长度最小
*/
// @lc code=start
class Solution
{
public:
int minimumArrayLength(vector<int> &nums)
{
int min_x = *min_element(nums.begin(), nums.end());
for (int &num : nums)
{
if (num % min_x)
return 1;
}
return (ranges::count(nums, min_x) + 1) / 2;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的元素个数。
空间复杂度:O(1)。
题目4:3013. 将数组分成最小总代价的子数组 II
思路
堆 + 滑动窗口。
题解:两个有序集合维护前 k-1 小(Python/Java/C++/Go)
代码
/*
* @lc app=leetcode.cn id=3013 lang=cpp
*
* [3013] 将数组分成最小总代价的子数组 II
*/
// @lc code=start
class Solution
{
public:
long long minimumCost(vector<int> &nums, int k, int dist)
{
k--;
long long sum = accumulate(nums.begin(), nums.begin() + dist + 2, 0LL);
multiset<int> L(nums.begin() + 1, nums.begin() + dist + 2), R;
auto L2R = [&]()
{
int x = *L.rbegin();
sum -= x;
L.erase(L.find(x));
R.insert(x);
};
auto R2L = [&]()
{
int x = *R.begin();
sum += x;
R.erase(R.find(x));
L.insert(x);
};
while (L.size() > k)
L2R();
long long ans = sum;
for (int i = dist + 2; i < nums.size(); i++)
{
// 移除 out
int out = nums[i - dist - 1];
auto it = L.find(out);
if (it != L.end())
{
sum -= out;
L.erase(it);
}
else
R.erase(R.find(out));
// 添加 in
int in = nums[i];
if (in < *L.rbegin())
{
sum += in;
L.insert(in);
}
else
R.insert(in);
// 维护大小
if (L.size() == k - 1)
R2L();
else if (L.size() == k + 1)
L2R();
ans = min(ans, sum);
}
return ans;
}
};
// @lc code=end
复杂度分析
时间复杂度:O(nlog(dist)),其中 n 为数组 nums 的元素个数。
空间复杂度:O(dist)。