寻找满足M组A元素大于B元素的最短前缀子数组(含复杂度要求)
优化方案:符合O(N logN logM)复杂度的实现
核心思路
原方案的瓶颈在于二分查找的每一步都需要重新构建堆并遍历所有元素计算配对数,导致总复杂度达到O(N log²N)。优化的关键是:
- 利用贪心验证的充要条件:无需计算完整配对数,只需验证是否存在至少M个有效配对。
- 高效维护前缀有序集合:用AVL树(或C++的
std::multiset,支持重复元素)预处理前缀排序数组,避免每次二分都重新排序。 - 提前终止验证逻辑:一旦确认能凑够M个配对,立即停止计算,减少不必要的操作。
优化代码实现(基于AVL树思想的std::multiset)
#include <iostream> #include <vector> #include <set> #include <iterator> using namespace std; // 预处理前缀有序集合,每个前缀对应前L个元素的有序结构 vector<multiset<int>> buildPrefixSorted(const vector<int>& arr) { int N = arr.size(); vector<multiset<int>> prefix(N + 1); for (int i = 0; i < N; ++i) { prefix[i+1] = prefix[i]; prefix[i+1].insert(arr[i]); } return prefix; } int minLenOfSubarrays(const vector<int>& aArr, const vector<int>& bArr, int M) { int N = aArr.size(); if (M == 0) return 0; if (N < M) return -1; // 无法满足条件的边界情况 // 预处理A、B的前缀有序集合 auto aPrefix = buildPrefixSorted(aArr); auto bPrefix = buildPrefixSorted(bArr); int left = M, right = N; int answer = N; while (left <= right) { int mid = left + (right - left) / 2; const auto& aSorted = aPrefix[mid]; const auto& bSorted = bPrefix[mid]; // 验证核心:取A的最大M个元素与B的最小M个元素逐个配对 auto aIt = next(aSorted.begin(), mid - M); auto bIt = bSorted.begin(); bool valid = true; for (int i = 0; i < M; ++i) { if (*aIt <= *bIt) { valid = false; break; } ++aIt; ++bIt; } if (valid) { answer = mid; right = mid - 1; // 尝试更小的长度 } else { left = mid + 1; // 需要更长的前缀 } } return answer; } int main() { vector<int> aArr = {2,4,10,6,1,11}; vector<int> bArr = {3,5,8,9,7,12}; int M = 3; cout << minLenOfSubarrays(aArr, bArr, M) << endl; return 0; }
复杂度分析
- 预处理阶段:每个元素插入
multiset的时间为O(logL)(L为当前前缀长度),总时间O(N logN)。 - 二分阶段:共O(logN)次迭代,每次验证仅需遍历M个元素,时间O(M),总时间O(M logN)。
- 总体复杂度:O(N logN + M logN),当M≤N时等价于O(N logN),优于目标复杂度O(N logN logM);若M接近N,也能满足要求。
堆实现的优化版本(符合作业要求)
如果必须用堆实现,可通过线性时间堆构建+提前终止配对逻辑优化:
#include <iostream> #include <vector> #include <queue> #include <algorithm> using namespace std; bool hasEnoughPairs(const vector<int>& a, const vector<int>& b, int mid, int M) { vector<int> aHeap(a.begin(), a.begin() + mid); vector<int> bHeap(b.begin(), b.begin() + mid); // 线性时间构建堆,替代逐个push的O(mid log mid) make_heap(aHeap.begin(), aHeap.end()); // A的最大堆 make_heap(bHeap.begin(), bHeap.end(), greater<int>()); // B的最小堆 int count = 0; while (count < M && !aHeap.empty() && !bHeap.empty()) { // 取出A中最大元素 pop_heap(aHeap.begin(), aHeap.end()); int maxA = aHeap.back(); aHeap.pop_back(); if (maxA > bHeap.front()) { count++; // 取出B中最小元素 pop_heap(bHeap.begin(), bHeap.end(), greater<int>()); bHeap.pop_back(); } } return count >= M; } int minLenOfSubarrays(const vector<int>& aArr, const vector<int>& bArr, int M) { int N = aArr.size(); if (M == 0) return 0; if (N < M) return -1; int left = M, right = N; int answer = N; while (left <= right) { int mid = left + (right - left) / 2; if (hasEnoughPairs(aArr, bArr, mid, M)) { answer = mid; right = mid - 1; } else { left = mid + 1; } } return answer; } int main() { vector<int> aArr = {2,4,10,6,1,11}; vector<int> bArr = {3,5,8,9,7,12}; int M = 3; cout << minLenOfSubarrays(aArr, bArr, M) << endl; return 0; }
堆版本复杂度分析
- 二分阶段:O(logN)次迭代。
- 每次迭代:堆构建时间O(mid),配对过程最多M次堆操作(每次O(log mid)),总时间O(mid + M log mid)。
- 总体复杂度:O(N logN + M logN logN),当M远小于N时(如M=O(logN)),复杂度可降至O(N logN),满足目标要求。
内容的提问来源于stack exchange,提问作者Mert
相关产品推荐
相关产品推荐

