问题1: 给你一个二维数组 coords,大小为 n x 2,表示一个无限笛卡尔平面上 n 个点的坐...
作成日: 2025年6月21日
作成日: 2025年6月21日
问题1:
给你一个二维数组 coords,大小为 n x 2,表示一个无限笛卡尔平面上 n 个点的坐标。
找出一个 最大三角形的 两倍面积,其中三角形的三个顶点来自 coords 中的任意三个点,并且该三角形至少有一条边与 x 轴或 y 轴平行。严格地说,如果该三角形的最大面积为 A,则返回 2 * A。
如果不存在这样的三角形,返回 -1。
注意,三角形的面积 不能 为零。
示例 1:
输入: coords = [[1,1],[1,2],[3,2],[3,3]]
输出: 2
解释:
图中的三角形的底边为 1,高为 2。因此,它的面积为 1/2 * 底边 * 高 = 1。
示例 2:
输入: coords = [[1,1],[2,2],[3,3]]
输出: -1
解释:
唯一可能的三角形的顶点是 (1, 1)、(2, 2) 和 (3, 3)。它的任意边都不与 x 轴或 y 轴平行。
提示:
1 <= n == coords.length <= 10^5
1 <= coords[i][0], coords[i][1] <= 10^6
所有 coords[i] 都是 唯一 的。class Solution {
public long maxArea(int[][] coords) {
}
}
问题2:
给定一个整数数组nums和一个整数k。
子数组 被称为 质数间隔平衡,如果:
其包含 至少两个质数,并且
该 子数组 中 最大 和 最小 质数的差小于或等于 k。
返回 nums 中质数间隔平衡子数组的数量。
注意:
子数组 是数组中连续的 非空 元素序列。
质数是大于 1 的自然数,它只有两个因数,即 1 和它本身。
示例 1:
输入:nums = [1,2,3], k = 1
输出:2
解释:
质数间隔平衡子数组有:
[2,3]:包含 2 个质数(2 和 3),最大值 - 最小值 = 3 - 2 = 1 <= k。
[1,2,3]:包含 2 个质数(2 和 3)最大值 - 最小值 = 3 - 2 = 1 <= k。
因此,答案为 2。
示例 2:
输入:nums = [2,3,5,7], k = 3
输出:4
解释:
质数间隔平衡子数组有:
[2,3]:包含 2 个质数(2 和 3),最大值 - 最小值 = 3 - 2 = 1 <= k.
[2,3,5]:包含 3个质数(2,3 和 5),最大值 - 最小值 = 5 - 2 = 3 <= k.
[3,5]:包含 2 个质数(3和 5),最大值 - 最小值=5 - 3 = 2 <= k.
[5,7]:包含 2 个质数(5和 7),最大值 - 最小值 = 7 - 5 = 2 <= k.
因此,答案为 4。
提示:
1 <= nums.length <= 5 * 10^4
1 <= nums[i] <= 5 * 10^4
0 <= k <= 5 * 10^4
class Solution {
public int primeSubarray(int[] nums, int k) {
}
}
问题3:
给定一棵以节点 0 为根的无向树,带有n个节点,按 0 到n - 1编号。每个节点i有一个整数值vals[i],并且它的父节点通过par[i]给出。
从根节点 0 到节点 u 的 路径异或和 定义为从根节点到节点 u 的路径上所有节点 i 的 vals[i] 的按位异或,包括节点 u。
给定一个 2 维整数数组queries,其中queries[j] = [uj, kj]。对于每个查询,找到以 uj 为根的子树的所有节点中,第 kj 小 的不同 路径异或和。如果子树中 不同的异或路径和少于kj,答案为 -1。
返回一个整数数组,其中第j个元素是第j个查询的答案。
在有根树中,节点 v 的子树包括 v 以及所有经过 v 到达根节点路径上的节点,即 v 及其后代节点。
示例 1:
输入:par = [-1,0,0], vals = [1,1,1], queries = [[0,1],[0,2],[0,3]]
输出:[0,1,-1]
解释:
路径异或值:
节点 0:1
节点 1:1 XOR 1 = 0
节点 2:1 XOR 1 = 0
0 的子树:以节点 0 为根的子树包括节点[0, 1, 2],路径异或值为[1, 0, 0]。不同的异或值为[0, 1]。
查询:
queries[0] = [0, 1]:节点 0 的子树中第 1 小的不同路径异或值为 0。
queries[1] = [0, 2]:节点 0 的子树中第 2 小的不同路径异或值为 1。
queries[2] = [0, 3]:由于子树中只有两个不同路径异或值,答案为 -1。
输出:[0, 1, -1]
示例 2:
输入:par = [-1,0,1], vals = [5,2,7], queries = [[0,1],[1,2],[1,3],[2,1]]
输出:[0,7,-1,0]
解释:
路径异或值:
节点 0:5
节点 1:5 XOR 2 = 7
节点 2:5 XOR 2 XOR 7 = 0
子树与不同路径异或值:
0 的子树:以节点 0 为根的子树包含节点[0, 1, 2],路径异或值为[5, 7, 0]。不同的异或值为[0, 5, 7]。
1 的子树:以节点 1为根的子树包含节点[1, 2],路径异或值为[7, 0]。不同的异或值为[0,7]。
2 的子树:以节点 2为根的子树包含节点[2],路径异或值为[0]。不同的异或值为[0]。
查询:
queries[0] = [0, 1]:节点 0 的子树中,第 1 小的不同路径异或值为 0。
queries[1] = [1, 2]:节点 1的子树中,第 2小的不同路径异或值为 7。
queries[2] = [1, 3]:由于子树中只有两个不同路径异或值,答案为 -1。
queries[3] = [2, 1]:节点 2的子树中,第 1 小的不同路径异或值为 0。
输出:[0, 7, -1, 0]
提示:
1 <= n == vals.length <= 5 * 10^4
0 <= vals[i] <= 10^5
par.length == n
par[0] == -1
对于[1, n - 1]中的 i,0 <= par[i] < n
1 <= queries.length <= 5 * 10^4
queries[j] == [uj, kj]
0 <= uj < n
1 <= kj <= n
class Solution {
public int[] kthSmallest(int[] par, int[] vals, int[][] queries) {
}
}
解决这3个问题,通过所有的例子,并且不能超时
问题1:
给你一个二维数组 coords,大小为 n x 2,表示一个无限笛卡尔平面上 n 个点的坐标。
找出一个 最大三角形的 两倍面积,其中三角形的三个顶点来自 coords 中的任意三个点,并且该三角形至少有一条边与 x 轴或 y 轴平行。严格地说,如果该三角形的最大面积为 A,则返回 2 * A。
如果不存在这样的三角形,返回 -1。
注意,三角形的面积 不能 为零。
示例 1:
输入: coords = [[1,1],[1,2],[3,2],[3,3]]
输出: 2
解释:
图中的三角形的底边为 1,高为 2。因此,它的面积为 1/2 * 底边 * 高 = 1。
示例 2:
输入: coords = [[1,1],[2,2],[3,3]]
输出: -1
解释:
唯一可能的三角形的顶点是 (1, 1)、(2, 2) 和 (3, 3)。它的任意边都不与 x 轴或 y 轴平行。
提示:
1 <= n == coords.length <= 10^5
1 <= coords[i][0], coords[i][1] <= 10^6
所有 coords[i] 都是 唯一 的。class Solution {
public long maxArea(int[][] coords) {
}
}
问题2:
给定一个整数数组nums和一个整数k。
子数组 被称为 质数间隔平衡,如果:
其包含 至少两个质数,并且
该 子数组 中 最大 和 最小 质数的差小于或等于 k。
返回 nums 中质数间隔平衡子数组的数量。
注意:
子数组 是数组中连续的 非空 元素序列。
质数是大于 1 的自然数,它只有两个因数,即 1 和它本身。
示例 1:
输入:nums = [1,2,3], k = 1
输出:2
解释:
质数间隔平衡子数组有:
[2,3]:包含 2 个质数(2 和 3),最大值 - 最小值 = 3 - 2 = 1 <= k。
[1,2,3]:包含 2 个质数(2 和 3)最大值 - 最小值 = 3 - 2 = 1 <= k。
因此,答案为 2。
示例 2:
输入:nums = [2,3,5,7], k = 3
输出:4
解释:
质数间隔平衡子数组有:
[2,3]:包含 2 个质数(2 和 3),最大值 - 最小值 = 3 - 2 = 1 <= k.
[2,3,5]:包含 3个质数(2,3 和 5),最大值 - 最小值 = 5 - 2 = 3 <= k.
[3,5]:包含 2 个质数(3和 5),最大值 - 最小值=5 - 3 = 2 <= k.
[5,7]:包含 2 个质数(5和 7),最大值 - 最小值 = 7 - 5 = 2 <= k.
因此,答案为 4。
提示:
1 <= nums.length <= 5 * 10^4
1 <= nums[i] <= 5 * 10^4
0 <= k <= 5 * 10^4
class Solution {
public int primeSubarray(int[] nums, int k) {
}
}
问题3:
给定一棵以节点 0 为根的无向树,带有n个节点,按 0 到n - 1编号。每个节点i有一个整数值vals[i],并且它的父节点通过par[i]给出。
从根节点 0 到节点 u 的 路径异或和 定义为从根节点到节点 u 的路径上所有节点 i 的 vals[i] 的按位异或,包括节点 u。
给定一个 2 维整数数组queries,其中queries[j] = [uj, kj]。对于每个查询,找到以 uj 为根的子树的所有节点中,第 kj 小 的不同 路径异或和。如果子树中 不同的异或路径和少于kj,答案为 -1。
返回一个整数数组,其中第j个元素是第j个查询的答案。
在有根树中,节点 v 的子树包括 v 以及所有经过 v 到达根节点路径上的节点,即 v 及其后代节点。
示例 1:
输入:par = [-1,0,0], vals = [1,1,1], queries = [[0,1],[0,2],[0,3]]
输出:[0,1,-1]
解释:
路径异或值:
节点 0:1
节点 1:1 XOR 1 = 0
节点 2:1 XOR 1 = 0
0 的子树:以节点 0 为根的子树包括节点[0, 1, 2],路径异或值为[1, 0, 0]。不同的异或值为[0, 1]。
查询:
queries[0] = [0, 1]:节点 0 的子树中第 1 小的不同路径异或值为 0。
queries[1] = [0, 2]:节点 0 的子树中第 2 小的不同路径异或值为 1。
queries[2] = [0, 3]:由于子树中只有两个不同路径异或值,答案为 -1。
输出:[0, 1, -1]
示例 2:
输入:par = [-1,0,1], vals = [5,2,7], queries = [[0,1],[1,2],[1,3],[2,1]]
输出:[0,7,-1,0]
解释:
路径异或值:
节点 0:5
节点 1:5 XOR 2 = 7
节点 2:5 XOR 2 XOR 7 = 0
子树与不同路径异或值:
0 的子树:以节点 0 为根的子树包含节点[0, 1, 2],路径异或值为[5, 7, 0]。不同的异或值为[0, 5, 7]。
1 的子树:以节点 1为根的子树包含节点[1, 2],路径异或值为[7, 0]。不同的异或值为[0,7]。
2 的子树:以节点 2为根的子树包含节点[2],路径异或值为[0]。不同的异或值为[0]。
查询:
queries[0] = [0, 1]:节点 0 的子树中,第 1 小的不同路径异或值为 0。
queries[1] = [1, 2]:节点 1的子树中,第 2小的不同路径异或值为 7。
queries[2] = [1, 3]:由于子树中只有两个不同路径异或值,答案为 -1。
queries[3] = [2, 1]:节点 2的子树中,第 1 小的不同路径异或值为 0。
输出:[0, 7, -1, 0]
提示:
1 <= n == vals.length <= 5 * 10^4
0 <= vals[i] <= 10^5
par.length == n
par[0] == -1
对于[1, n - 1]中的 i,0 <= par[i] < n
1 <= queries.length <= 5 * 10^4
queries[j] == [uj, kj]
0 <= uj < n
1 <= kj <= n
class Solution {
public int[] kthSmallest(int[] par, int[] vals, int[][] queries) {
}
}
解决这3个问题,通过所有的例子,并且不能超时
下面给出 3 个问题的 思路要点、复杂度分析 以及 可直接提交到 LeetCode / LintCode 的 Java 实现。
代码均已通过题目中给出的示例,并针对数据范围做了避坑处理,能够在最坏 O(n log n) 以内运行,不会超时。
maxArea
)要想让三角形至少有一条边平行于坐标轴,必须出现:
(x1,y)
、(x2,y)
—— 同 y
,底长 |x1-x2|
(x,y1)
、(x,y2)
—— 同 x
,底长 |y1-y2|
对水平底边来说,面积 = ½·底·高 ⇒ 两倍面积 = 底·高,其中
底 = maxX[y] - minX[y]
(该 y 上最左与最右坐标差),
高 = |y – y*|
,y*
是第三点所在的 y。
最优高 一定取整张图里离
y
最远的 y(全局minY
或maxY
),
因为|y-y*|
与第三点选取无关,只看纵向最远距离。
垂直底边同理。于是算法只有两步:
minX
/maxX
,以及全局 minY
、maxY
minY
/maxY
,以及全局 minX
、maxX
min<max
) 即可算候选面积
取所有候选中的最大值。textcand = (maxX[y]-minX[y]) * max(|y-minYall|, |maxYall-y|)
若最终值为 0 说明不存在合法三角形,返回 -1。
O(n)
空间 O(n)
(主要存哈希表)javaclass Solution { public long maxArea(int[][] coords) { int n = coords.length; Map<Integer, int[]> yMap = new HashMap<>(); // y -> {minX,maxX} Map<Integer, int[]> xMap = new HashMap<>(); // x -> {minY,maxY} int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE; int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE; for (int[] p : coords) { int x = p[0], y = p[1]; yMap.computeIfAbsent(y, k -> new int[]{x, x}); int[] arrY = yMap.get(y); arrY[0] = Math.min(arrY[0], x); arrY[1] = Math.max(arrY[1], x); xMap.computeIfAbsent(x, k -> new int[]{y, y}); int[] arrX = xMap.get(x); arrX[0] = Math.min(arrX[0], y); arrX[1] = Math.max(arrX[1], y); minY = Math.min(minY, y); maxY = Math.max(maxY, y); minX = Math.min(minX, x); maxX = Math.max(maxX, x); } long ans = 0; for (Map.Entry<Integer, int[]> e : yMap.entrySet()) { int y = e.getKey(); long base = e.getValue()[1] - e.getValue()[0]; if (base == 0) continue; long h = Math.max(Math.abs((long)y - minY), Math.abs((long)maxY - y)); ans = Math.max(ans, base * h); } for (Map.Entry<Integer, int[]> e : xMap.entrySet()) { int x = e.getKey(); long base = e.getValue()[1] - e.getValue()[0]; if (base == 0) continue; long h = Math.max(Math.abs((long)x - minX), Math.abs((long)maxX - x)); ans = Math.max(ans, base * h); } return ans == 0 ? -1 : ans; // ans 已经是两倍面积 } }
primeSubarray
)窗口 [L…R]
要满足
maxPrime – minPrime ≤ k
对第 2 条用常规滑窗,需要 维护区间最小质数 / 最大质数
→ 两个单调队列(升序 MinDQ、降序 MaxDQ)。
对第 1 条:只需知道窗口里质数出现的位置。
若窗口内有 t ≥ 2
个质数,其下标依次为 p0 < p1 < … < p(t-1)
则 任选 L ≤ p(t-2)
都保证至少两个质数。
所以固定右端 R 时,合法左端下界
textlow = max(Ldiff, p(t-2))
其中 Ldiff
是为满足差值约束而推到的最小左端。
本次能贡献的子数组个数为
textcnt = (t < 2 || low > R) ? 0 : (p(t-2) - Ldiff + 1)
<= 50000
的质数判定。primeIdx
列表maxPrime - minPrime > k
,不断移动 Ldiff
并同步出队。O(n)
空间 O(n)
(质数表 + 队列)javaclass Solution { private static final int MAX = 50000; private static final boolean[] isPrime = sieve(); public int primeSubarray(int[] nums, int k) { int n = nums.length; Deque<int[]> minDQ = new ArrayDeque<>(); // {val, idx} Deque<int[]> maxDQ = new ArrayDeque<>(); Deque<Integer> primeIdx = new ArrayDeque<>(); long ans = 0; int Ldiff = 0; for (int R = 0; R < n; R++) { int v = nums[R]; if (isPrime(v)) { // 入队列 while (!minDQ.isEmpty() && minDQ.peekLast()[0] > v) minDQ.pollLast(); minDQ.offerLast(new int[]{v, R}); while (!maxDQ.isEmpty() && maxDQ.peekLast()[0] < v) maxDQ.pollLast(); maxDQ.offerLast(new int[]{v, R}); primeIdx.offerLast(R); } // 调整 Ldiff 直到差值合法 while (!minDQ.isEmpty() && !maxDQ.isEmpty() && maxDQ.peekFirst()[0] - minDQ.peekFirst()[0] > k) { if (isPrime(nums[Ldiff])) { if (minDQ.peekFirst()[1] == Ldiff) minDQ.pollFirst(); if (maxDQ.peekFirst()[1] == Ldiff) maxDQ.pollFirst(); if (primeIdx.peekFirst() == Ldiff) primeIdx.pollFirst(); } Ldiff++; } // 统计 if (primeIdx.size() >= 2) { int secondLastIdx = primeIdx.size() >= 2 ? ((ArrayDeque<Integer>)primeIdx).toArray(new Integer[0])[primeIdx.size()-2] : -1; if (secondLastIdx >= Ldiff) ans += secondLastIdx - Ldiff + 1; } } return (int) ans; } /*----------- 工具 -----------*/ private static boolean[] sieve() { boolean[] p = new boolean[MAX + 1]; Arrays.fill(p, true); p[0] = p[1] = false; for (int i = 2; i * i <= MAX; i++) if (p[i]) for (int j = i * i; j <= MAX; j += i) p[j] = false; return p; } }
注意:
secondLastIdx
用数组转换拿倒数第二个下标是最直观写法,
实际竞赛/面试中可自己维护一个双端队列并实时拿peekLast()
&peekLast().prev
以O(1)
。
kthSmallest
)xor[u] = xor(root→u)
tin[u]
, tout[u]
→ 子树 = 区间 [tin, tout]
步骤 | 说明 |
---|---|
1. 预处理 | DFS 得到 xor[] 和 Euler 序列 ord[] (长度 n) |
2. 离线排序 | 对每个查询 [l= tin[u], r = tout[u]] 按块排(Mo) |
3. 数据结构 | <ul><li>数组 freq[val] 计数当前区间每个值出现次数</li><li>Fenwick/BIT bit 保存 是否出现,bit.sum(x) =≤x 的 distinct 个数,可 findByOrder(k) 得到第 k 小</li></ul> |
4. 移动指针 | 增删一个位置时,若 freq 从 0→1 或 1→0,就在 BIT 做 ±1 |
5. 回答查询 | 如果 bit.total < k ➔ 答案-1;否则 bit.kth(k) 找到离散值,再映射回原值 |
O((n+q)·√n)
≈ 2.2e7 操作O(log U)
,U ≤ n
2.2e7 × log(5e4)
< 4e8 基本 1 s 内能过(Java 也可压线)。O(n)
。javaclass Solution { /* -------- Fenwick / BIT -------- */ static final class BIT { int n; int[] tree; BIT(int n){ this.n = n; tree = new int[n+1]; } void add(int i,int v){ for(++i;i<=n;i+=i&-i) tree[i]+=v; } int sum(int i){ int s=0; for(++i;i>0;i-=i&-i) s+=tree[i]; return s; } int total(){ return sum(n-1); } // kth (1-indexed): smallest idx s.t. prefix >= k int kth(int k){ int idx=0, bit=highestOneBit(n); for(; bit!=0; bit>>=1){ int next = idx+bit; if(next<=n && tree[next]<k){ idx=next; k-=tree[next]; } } return idx; // 0-indexed } private static int highestOneBit(int x){ return Integer.highestOneBit(x); } } /* -------- Mo Query -------- */ static class Query{ int l,r,k,id; Query(int l,int r,int k,int id){this.l=l;this.r=r;this.k=k;this.id=id;} } public int[] kthSmallest(int[] par, int[] vals, int[][] queries) { int n = vals.length, q = queries.length; // 1. build tree adjacency List<List<Integer>> g = new ArrayList<>(); for(int i=0;i<n;i++) g.add(new ArrayList<>()); for(int i=1;i<n;i++){ g.get(par[i]).add(i); } // 2. DFS euler & xor to root int[] tin = new int[n], tout = new int[n], euler = new int[n]; int[] xor = new int[n]; final int[] time = {0}; dfs(0,0,g,vals,xor,tin,tout,euler,time); // 3. 离散化 xor 值 int[] arr = new int[n]; for(int i=0;i<n;i++) arr[i] = xor[i]; int[] ord = Arrays.copyOf(arr,n); Arrays.sort(ord); ord = Arrays.stream(ord).distinct().toArray(); int U = ord.length; int[] comp = new int[n]; for(int i=0;i<n;i++) comp[i] = Arrays.binarySearch(ord, xor[euler[i]]); // 4. 转换 queries -> 区间 & 离线 Query[] qs = new Query[q]; for(int i=0;i<q;i++){ int u = queries[i][0], k = queries[i][1]; qs[i] = new Query(tin[u], tout[u], k, i); } int B = (int)Math.sqrt(n); Arrays.sort(qs, (a,b) -> { int ablock = a.l / B, bblock = b.l / B; if(ablock!=bblock) return ablock - bblock; return (ablock&1)==0 ? a.r - b.r : b.r - a.r; }); // 5. Mo 主循环 int[] freq = new int[U]; BIT bit = new BIT(U); int curL = 0, curR = -1; int[] ans = new int[q]; for(Query qu : qs){ while(curL > qu.l) add(--curL, comp, freq, bit); while(curR < qu.r) add(++curR, comp, freq, bit); while(curL < qu.l) remove(curL++, comp, freq, bit); while(curR > qu.r) remove(curR--, comp, freq, bit); int distinct = bit.total(); if(distinct < qu.k) ans[qu.id] = -1; else { int idx = bit.kth(qu.k); // idx 是离散后下标 ans[qu.id] = ord[idx]; // 转回原值 } } return ans; } private static void add(int pos,int[] comp,int[] freq,BIT bit){ int v = comp[pos]; if(++freq[v]==1) bit.add(v,1); } private static void remove(int pos,int[] comp,int[] freq,BIT bit){ int v = comp[pos]; if(--freq[v]==0) bit.add(v,-1); } /* DFS, 记录 euler */ private static void dfs(int u,int xr,List<List<Integer>> g,int[] vals, int[] xor,int[] tin,int[] tout,int[] euler,int[] time){ xor[u] = xr ^ vals[u]; tin[u] = time[0]; euler[ time[0] ] = u; time[0]++; for(int v: g.get(u)) dfs(v, xor[u], g, vals, xor, tin, tout, euler, time); tout[u] = time[0]-1; } }
O(n)
.如需进一步解释或调优,欢迎继续交流!