Q1、转换数组
1、题目描述
给你一个整数数组 nums
,它表示一个循环数组。请你遵循以下规则创建一个大小 相同 的新数组 result
:
对于每个下标 i
(其中 0 <= i < nums.length
),独立执行以下操作:
- 如果
nums[i] > 0
:从下标i
开始,向 右 移动nums[i]
步,在循环数组中落脚的下标对应的值赋给result[i]
。 - 如果
nums[i] < 0
:从下标i
开始,向 左 移动abs(nums[i])
步,在循环数组中落脚的下标对应的值赋给result[i]
。 - 如果
nums[i] == 0
:将nums[i]
的值赋给result[i]
。
返回新数组 result
。
**注意:**由于 nums
是循环数组,向右移动超过最后一个元素时将回到开头,向左移动超过第一个元素时将回到末尾。
2、解题思路
- 循环数组的处理:
- 循环数组的关键是取模运算。当数组的大小为
n
时,对于任何一个下标i
和步数k
,可以通过(i + k) % n
来获得向右移动k
步后的目标下标,或者通过(i - k + n) % n
来获得向左移动k
步后的目标下标。
- 循环数组的关键是取模运算。当数组的大小为
- 问题分解:
- 对于每个下标 i ,根据 nums[i] 的值来决定如何计算 result[i]:
- 如果
nums[i] == 0
,直接将result[i]
设置为nums[i]
。 - 如果
nums[i] > 0
,我们需要向右移动nums[i]
步,计算目标下标并取值。 - 如果
nums[i] < 0
,我们需要向左移动abs(nums[i])
步,同样计算目标下标并取值。
- 如果
- 对于每个下标 i ,根据 nums[i] 的值来决定如何计算 result[i]:
- 细节处理:
- 通过对数组长度
n
取模,确保即使移动的步数超过数组长度,也能正确处理循环效果。
- 通过对数组长度
3、代码实现
class Solution {
public:
vector<int> constructTransformedArray(vector<int>& nums) {
int n = nums.size();
vector<int> result(n);
for (int i = 0; i < n; ++i) {
if (nums[i] == 0) {
result[i] = nums[i];
} else {
int targetIndex = (i + nums[i] % n + n) % n;
result[i] = nums[targetIndex];
}
}
return result;
}
};
4、复杂度分析
时间复杂度:O(n)
,其中 n
是输入数组 nums
的大小。我们只遍历了一次 nums
数组,进行常数时间的操作,因此时间复杂度是线性的。
空间复杂度:O(n)
,我们创建了一个新的数组 result
来存储变换后的结果,空间复杂度为 O(n)
。
Q2、用点构造面积最大的矩形 Ⅰ
1、题目描述
给你一个数组 points
,其中 points[i] = [xi, yi]
表示无限平面上一点的坐标。
你的任务是找出满足以下条件的矩形可能的 最大 面积:
- 矩形的四个顶点必须是数组中的 四个 点。
- 矩形的内部或边界上 不能 包含任何其他点。
- 矩形的边与坐标轴 平行 。
返回可以获得的 最大面积 ,如果无法形成这样的矩形,则返回 -1。
2、解题思路
- 矩形的基本条件:
- 矩形的四个顶点必须在给定的点集
points
中。 - 矩形的边必须与坐标轴平行。
- 矩形的内部或边界不能有其他点。
- 矩形的四个顶点必须在给定的点集
- 矩形的顶点特性:
- 假设有四个顶点
(x1, y1)
,(x2, y2)
,(x3, y3)
,(x4, y4)
,其中(x1, y1)
和(x2, y2)
是对角线的两个顶点。根据矩形的性质,其他两个顶点可以通过交换 x 或 y 坐标来确定,具体是(x1, y2)
和(x2, y1)
。 - 因此,对于任意两点
(x1, y1)
和(x2, y2)
,我们可以计算出另外两个点(x1, y2)
和(x2, y1)
,然后检查这两个点是否在points
中。如果这两个点都存在,那么这四个点就能形成一个矩形。
- 假设有四个顶点
- 验证矩形的合法性:
- 矩形的边界必须没有其他点。我们需要确保除了矩形的四个顶点外,矩形内的所有点都不包含在
points
中。为了提高效率,我们可以使用哈希集unordered_set
来存储点坐标,这样可以在常数时间内查询某个点是否存在。
- 矩形的边界必须没有其他点。我们需要确保除了矩形的四个顶点外,矩形内的所有点都不包含在
- 计算矩形面积:
- 对于两个对角点
(x1, y1)
和(x2, y2)
,矩形的面积可以通过abs(x2 - x1) * abs(y2 - y1)
来计算。
- 对于两个对角点
解题步骤
- 预处理数据:
- 使用哈希集合
unordered_set
存储所有点的坐标,方便后续查询。
- 使用哈希集合
- 双重遍历所有点对:
- 对于任意两个点
(x1, y1)
和(x2, y2)
,如果x1 != x2
且y1 != y2
,则可以构成一个矩形的两个对角点。计算另外两个顶点(x1, y2)
和(x2, y1)
,并检查它们是否存在于点集合中。
- 对于任意两个点
- 验证矩形:
- 对于找到的矩形,检查矩形的内部和边界是否只包含矩形的四个顶点,不包含其他点。如果符合条件,则计算矩形的面积。
- 更新最大矩形面积:
- 在遍历过程中,始终记录当前找到的最大矩形面积。
- 返回结果:
- 如果找到了符合条件的矩形,则返回最大面积;否则返回
-1
。
- 如果找到了符合条件的矩形,则返回最大面积;否则返回
3、代码实现
class Solution {
public:
int maxRectangleArea(vector<vector<int>>& points) {
int n = points.size();
if (n < 4) {
return -1; // 如果点数少于4个, 无法形成矩形
}
// 哈希集合用于快速查询点是否存在
unordered_set<string> pointSet;
for (const auto& p : points) {
pointSet.insert(encode(p[0], p[1])); // 将每个点编码存入哈希集
}
int maxArea = -1; // 最大矩形面积, 初始为-1, 表示未找到矩形
// 双重循环,枚举所有点对
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
int x1 = points[i][0], y1 = points[i][1];
int x2 = points[j][0], y2 = points[j][1];
// 如果两个点在水平或垂直方向上重合,跳过
if (x1 != x2 && y1 != y2) {
string p3 = encode(x1, y2); // 第三个点
string p4 = encode(x2, y1); // 第四个点
// 检查是否有这两个点
if (pointSet.count(p3) && pointSet.count(p4)) {
// 检查矩形的内部和边界是否为空
if (isValidRectangle(x1, y1, x2, y2, pointSet)) {
// 计算矩形面积
int area = abs(x2 - x1) * abs(y2 - y1);
maxArea = max(maxArea, area); // 更新最大面积
}
}
}
}
}
return maxArea; // 返回最大矩形面积, 如果没有矩形, 返回-1
}
// 将坐标编码成字符串, 便于存入哈希集
string encode(int x, int y) {
return to_string(x) + "," + to_string(y);
}
// 检查矩形的内部和边界是否为空
bool isValidRectangle(int x1, int y1, int x2, int y2, const unordered_set<string>& pointSet) {
int minX = min(x1, x2), maxX = max(x1, x2);
int minY = min(y1, y2), maxY = max(y1, y2);
// 遍历矩形内部和边界的所有点
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
string p = encode(x, y);
if (pointSet.count(p)) {
// 如果不是四个顶点之一,则返回 false
if (!((x == x1 && y == y1) || (x == x1 && y == y2) ||
(x == x2 && y == y1) || (x == x2 && y == y2))) {
return false;
}
}
}
}
return true;
}
};
4、时间复杂度分析
- 哈希集合插入:我们将所有点存储到哈希集合中,时间复杂度是
O(n)
,其中n
是点的数量。 - 双重循环:我们枚举所有点对,每次枚举的时间复杂度是
O(n^2)
。 - 验证矩形:对于每一对点,我们需要检查矩形的每个点,这需要
O(n)
的时间。
因此,总的时间复杂度为 O(n^3)
,适合点数较少的情况。
Q3、长度可被 K 整除的子数组的最大元素和
1、题目描述
给你一个整数数组 nums
和一个整数 k
。
返回 nums
中一个 非空子数组 的 最大 和,要求该子数组的长度可以 被 k
整除 。
子数组 是数组中一个连续的、非空的元素序列。
2、解题思路
-
前缀和(Prefix Sum): 前缀和是一种常见的技巧,它可以在
O(1)
时间复杂度内计算任意区间的和。我们构建一个prefixSum
数组,其中prefixSum[i]
表示从数组nums[0]
到nums[i-1]
的和。这样,任意子数组的和可以通过prefixSum
数组的差值计算得到。 -
余数技巧: 为了确保子数组的长度是
k
的倍数,我们可以利用前缀和的余数。 -
最小前缀和维护: 为了高效地计算符合条件的子数组和,我们维护一个大小为
k
的数组minPrefixSum
,用来记录每个余数下最小的前缀和。对于每个位置j
,计算当前前缀和prefixSum[j]
与对应余数的最小前缀和的差值,得到符合条件的子数组和。 -
最大和更新: 每次遍历时,计算当前前缀和与最小前缀和的差值,更新最大和。
具体实现步骤:
- 计算前缀和:
- 初始化一个大小为
n + 1
的数组prefixSum
,其中prefixSum[i]
存储nums[0]
到nums[i-1]
的和。此时,prefixSum[0] = 0
。
- 初始化一个大小为
- 初始化最小前缀和数组:
- 创建一个大小为
k
的数组minPrefixSum
,用来记录每个余数下的最小前缀和。初始化时,将所有值设置为一个非常大的数,LLONG_MAX / 2
,这样可以避免溢出。
- 创建一个大小为
- 遍历前缀和数组:
- 遍历
prefixSum
数组,对于每个prefixSum[j]
,计算它的余数j % k
。 - 更新最大和
maxSubarraySum
为当前前缀和与最小前缀和的差值(如果差值大于当前的最大和)。 - 更新最小前缀和数组
minPrefixSum
,保持每个余数下最小的前缀和。
- 遍历
- 返回结果:
- 返回计算得到的最大和。
3、代码实现
class Solution {
public:
long long maxSubarraySum(vector<int>& nums, int k) {
int n = nums.size();
vector<long long> prefixSum(n + 1, 0); // 前缀和数组
// 计算前缀和数组
for (int i = 1; i <= n; ++i) {
prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
}
// 初始化最小前缀和数组
vector<long long> minPrefixSum(k, LLONG_MAX / 2);
long long ret = LLONG_MIN;
// 遍历所有的前缀和,更新最小前缀和数组,并计算可能的最大子数组和
for (int i = 0; i <= n; ++i) {
// 当前前缀和的余数
int remainder = i % k;
ret = max(ret, prefixSum[i] - minPrefixSum[remainder]);
minPrefixSum[remainder] = min(minPrefixSum[remainder], prefixSum[i]);
}
return ret;
}
};
4、复杂度
时间复杂度:
- 计算前缀和数组的时间复杂度是
O(n)
。 - 遍历前缀和数组并更新最小前缀和数组的时间复杂度也是
O(n)
。 - 因此,总时间复杂度是
O(n)
,其中n
是数组nums
的长度。
空间复杂度:
- 我们使用了两个数组
prefixSum
和minPrefixSum
,它们的大小分别为n + 1
和k
。 - 因此,空间复杂度是
O(n + k)
。
Q4、用点构造面积最大的矩形 Ⅱ
1、题目描述
在无限平面上有 n 个点。给定两个整数数组 xCoord
和 yCoord
,其中 (xCoord[i], yCoord[i])
表示第 i
个点的坐标。
你的任务是找出满足以下条件的矩形可能的 最大 面积:
- 矩形的四个顶点必须是数组中的 四个 点。
- 矩形的内部或边界上 不能 包含任何其他点。
- 矩形的边与坐标轴 平行 。
返回可以获得的 最大面积 ,如果无法形成这样的矩形,则返回 -1。
2、解题思路
1、数据预处理
我们需要记录点集的各种关系,用于后续矩形的验证:
- 按列分组:使用
x_map
记录同一列的所有点的纵坐标。 - 按行分组:使用
y_map
记录同一行的所有点的横坐标。 - 计算正下方的点:对于每个点,记录它正下方的点,用
below
存储。 - 计算正左方的点:对于每个点,记录它正左方的点,用
left
存储。
2、矩形验证
假设矩形的右上角为 (x2, y2)
,左下角为 (x1, y1)
,需要验证以下条件:
- 矩形左下角
(x1, y1)
存在,且在(x2, y2)
的左边。 - 矩形左上角
(x1, y2)
存在,且在(x2, y2)
的左边。 - 矩形右下角
(x2, y1)
存在,且在(x2, y2)
的下边。
如果以上条件都满足,就将矩形的顶点记录下来,并存储矩形的面积。
3、离散化
为了高效处理点的范围查询,我们需要对坐标进行离散化:
- 按行和列分别对坐标排序并映射到离散值。
- 通过二分查找快速定位坐标的离散值。
4、树状数组支持的离线查询
通过树状数组支持的离线查询,可以高效计算矩形内的点数量:
- 将每个矩形的验证条件转化为点的区间和查询。
- 使用树状数组动态维护当前坐标系中的点数量。
通过以上步骤,我们可以逐步处理所有矩形,并得到满足条件的最大面积。
3、代码实现
// 树状数组类,用于高效维护区间和
class Fenwick {
vector<int> tree;
public:
Fenwick(int n) : tree(n + 1, 0) {}
// 单点更新,增加一个点
void add(int i) {
while (i < tree.size()) {
tree[i] += 1;
i += i & -i;
}
}
// 查询前缀和 [1, i]
int pre(int i) {
int res = 0;
while (i > 0) {
res += tree[i];
i &= i - 1;
}
return res;
}
// 查询区间和 [l, r]
int query(int l, int r) { return pre(r) - pre(l - 1); }
};
class Solution {
public:
long long maxRectangleArea(vector<int>& xCoord, vector<int>& yCoord) {
// 使用 x_ma 记录同一列的所有点的纵坐标。使用 y_map 记录同一行的所有点的横坐标。
map<int, vector<int>> x_map, y_map;
// 用 below 存储每个点它正下方的点, 用 left 存储每个点它正左方的点
map<pair<int, int>, int> below, left;
// 预处理 x_map 和 y_map
for (int i = 0; i < xCoord.size(); ++i) {
x_map[xCoord[i]].push_back(yCoord[i]);
y_map[yCoord[i]].push_back(xCoord[i]);
}
// 预处理 below, 对于每个点, 记录它正下方的点
for (auto& [x, ys] : x_map) {
sort(ys.begin(), ys.end());
for (int j = 0; j + 1 < ys.size(); ++j) {
below[{x, ys[j + 1]}] = ys[j];
}
}
// 预处理 left, 对于每个点, 记录它正左方的点
for (auto& [y, xs] : y_map) {
sort(xs.begin(), xs.end());
for (int j = 0; j + 1 < xs.size(); ++j) {
left[{xs[j + 1], y}] = xs[j];
}
}
// 离散化坐标
vector<int> xs, ys;
for (const auto& [x, _] : x_map) {
xs.push_back(x);
}
for (const auto& [y, _] : y_map) {
ys.push_back(y);
}
sort(xs.begin(), xs.end());
sort(ys.begin(), ys.end());
auto discretize = [&](int val, const vector<int>& coords) {
return lower_bound(coords.begin(), coords.end(), val) - coords.begin();
};
// 收集矩形的询问
vector<tuple<int, int, int, int, long long>> queries;
for (auto& [x2, list_y] : x_map) {
for (int j = 0; j + 1 < list_y.size(); ++j) {
int y1 = list_y[j], y2 = list_y[j + 1];
int x1 = left.count({x2, y2}) ? left[{x2, y2}] : -1;
if (x1 != -1 && left.count({x2, y1}) && left[{x2, y1}] == x1 &&
below.count({x1, y2}) && below[{x1, y2}] == y1) {
queries.emplace_back(discretize(x1, xs), discretize(x2, xs), discretize(y1, ys), discretize(y2, ys), (long long)(x2 - x1) * (y2 - y1));
}
}
}
// 离线处理
vector<vector<tuple<int, int, int, int>>> grouped_queries(xs.size());
for (int i = 0; i < queries.size(); ++i) {
auto [x1, x2, y1, y2, area] = queries[i];
if (x1 > 0) {
grouped_queries[x1 - 1].emplace_back(i, -1, y1, y2);
}
grouped_queries[x2].emplace_back(i, 1, y1, y2);
}
vector<int> res(queries.size(), 0);
Fenwick tree(ys.size());
for (int i = 0; i < xs.size(); ++i) {
for (int y : x_map[xs[i]]) {
tree.add(discretize(y, ys) + 1);
}
for (auto [qid, sign, y1, y2] : grouped_queries[i]) {
res[qid] += sign * tree.query(y1 + 1, y2 + 1);
}
}
// 计算最大面积
long long ans = -1;
for (int i = 0; i < queries.size(); ++i) {
if (res[i] == 4) {
ans = max(ans, get<4>(queries[i]));
}
}
return ans;
}
};
4、复杂度
- 预处理复杂度:
x_map
和y_map
填充:O(n)。below
和left
预处理:O(nlogn)。
- 矩形验证:对每个点处理范围查询,复杂度为 O(k),其中 kkk 是点的数量。
- 离线查询:使用树状数组动态维护,复杂度为 O(qlogn),其中 q 是矩形数。
总复杂度:O(nlogn+qlogn)。