仅允许i与i+2位置交换的数组排序及交换次数统计方案问询
问题描述
需要对一个整数数组排序,但仅允许执行索引i处元素与i+2处元素交换的操作,传统排序算法(如快排、归并)因依赖相邻交换无法直接使用。
约束条件
- 数组长度为N
- 数组元素取值范围为0到N-1
- 算法需支持长度达100,000的数组
核心疑问
- 如何在该交换约束下完成数组完全排序?是否有适配的现有算法/策略?
- 如何统计排序所需的精确交换次数?
当前实现(C++)
#include <iostream> #include <algorithm> using namespace std; // 冒泡排序并统计交换次数 int bubble_sort_with_swap_count(int arr[], int n) { int swap_count = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); swap_count++; } } } return swap_count; } // 按约束排序并统计步骤 void sort_special_with_steps(int arr[], int n, int &total_swaps) { // 分离偶数索引和奇数索引元素 int even_elements[n / 2 + 1], odd_elements[n / 2 + 1]; int even_idx = 0, odd_idx = 0; for (int i = 0; i < n; i += 2) { even_elements[even_idx++] = arr[i]; } for (int i = 1; i < n; i += 2) { odd_elements[odd_idx++] = arr[i]; } // 分别排序两个子序列并统计交换次数 int even_swaps = bubble_sort_with_swap_count(even_elements, even_idx); int odd_swaps = bubble_sort_with_swap_count(odd_elements, odd_idx); // 合并回原数组 even_idx = 0; odd_idx = 0; for (int i = 0; i < n; i++) { if (i % 2 == 0) { arr[i] = even_elements[even_idx++]; } else { arr[i] = odd_elements[odd_idx++]; } } total_swaps = even_swaps + odd_swaps; } int main() { int arr[] = {4, 1, 3, 2, 5}; int n = sizeof(arr) / sizeof(arr[0]); int total_swaps = 0; sort_special_with_steps(arr, n, total_swaps); cout << "Sorted array: "; for (int i = 0; i < n; i++) { cout << arr[i] << " "; } cout << endl; cout << "Number of steps: " << total_swaps << endl; return 0; }
现存问题
当前实现速度不足,了解到可用树结构优化但未成功实现。
测试案例及预期输出
- N=5,arr={2,0,4,3,1} → 总交换次数=-1(无法排序)
- N=6,arr={2,3,0,5,4,1} → 总交换次数=3(可排序)
- N=200,arr={0,1,...,85,115,87,...,114,86,116,...,199} → 总交换次数=-1(无法排序)
解决方案
一、先判断是否可排序
允许的交换操作(i和i+2)只会在同奇偶性的索引之间移动元素:偶数位元素永远只能在偶数位之间转移,奇数位同理。因此仅当原数组偶数位元素集合与排序后数组的偶数位元素集合完全一致,且奇数位元素集合也匹配时,数组才能被排序。
判断步骤:
- 生成目标排序数组
[0,1,2,...,N-1] - 分别提取原数组和目标数组的偶数位、奇数位元素集合
- 将原数组的两个子集合排序后,与目标数组的对应子集合对比:若完全匹配则可排序,否则返回-1
比如测试案例N=5,目标数组偶数位元素是{0,2,4},原数组偶数位元素是{2,4,1},两者不匹配,因此无法排序。
二、排序策略与效率优化
你的核心思路(拆分偶/奇子序列分别排序后合并)是正确的,但冒泡排序的O(k²)时间复杂度无法支撑10万级数组。需要替换为O(k log k)的方案,并通过逆序数统计精确计算交换次数:
关键逻辑:交换次数=子序列逆序数之和
子序列内部的每个逆序对,对应原数组中一次合法的i与i+2交换操作;子序列排序所需的总交换次数,等于该序列的逆序数。因此两个子序列的逆序数之和,就是整个数组排序的精确交换次数。
高效统计逆序数(树状数组实现)
树状数组(Fenwick Tree)可以在O(k log k)时间内完成逆序数统计,适合处理大规模数据:
- 从子序列末尾向前遍历每个元素
- 查询树状数组中已插入的、小于当前元素的数量,累加得到逆序数
- 将当前元素插入树状数组,更新计数
三、优化后的代码示例(C++)
#include <iostream> #include <vector> #include <algorithm> #include <numeric> using namespace std; // 树状数组实现 class FenwickTree { private: vector<int> tree; public: FenwickTree(int size) : tree(size + 2, 0) {} // 预留空间避免越界 void update(int idx, int delta) { idx++; // 适配元素从0开始的索引 while (idx < tree.size()) { tree[idx] += delta; idx += idx & -idx; } } int query(int idx) { idx++; // 适配元素从0开始的索引 int sum = 0; while (idx > 0) { sum += tree[idx]; idx -= idx & -idx; } return sum; } }; // 统计序列的逆序数 long long count_inversions(const vector<int>& seq) { if (seq.empty()) return 0; int max_val = *max_element(seq.begin(), seq.end()); FenwickTree ft(max_val); long long inversions = 0; // 从后往前遍历统计逆序对 for (auto it = seq.rbegin(); it != seq.rend(); ++it) { inversions += ft.query(*it - 1); ft.update(*it, 1); } return inversions; } // 执行排序并返回交换次数,-1表示不可排序 long long sort_special(vector<int>& arr) { int n = arr.size(); vector<int> sorted_arr(n); iota(sorted_arr.begin(), sorted_arr.end(), 0); // 生成目标数组 // 分离原数组的偶/奇子序列 vector<int> even_orig, odd_orig; for (int i = 0; i < n; ++i) { (i % 2 == 0) ? even_orig.push_back(arr[i]) : odd_orig.push_back(arr[i]); } // 分离目标数组的偶/奇子序列 vector<int> even_target, odd_target; for (int i = 0; i < n; ++i) { (i % 2 == 0) ? even_target.push_back(sorted_arr[i]) : odd_target.push_back(sorted_arr[i]); } // 判断是否可排序 vector<int> even_sorted = even_orig; vector<int> odd_sorted = odd_orig; sort(even_sorted.begin(), even_sorted.end()); sort(odd_sorted.begin(), odd_sorted.end()); if (even_sorted != even_target || odd_sorted != odd_target) { return -1; } // 统计逆序数(交换次数) long long even_inv = count_inversions(even_orig); long long odd_inv = count_inversions(odd_orig); // 合并排序后的子序列回原数组 sort(even_orig.begin(), even_orig.end()); sort(odd_orig.begin(), odd_orig.end()); int e_idx = 0, o_idx = 0; for (int i = 0; i < n; ++i) { arr[i] = (i % 2 == 0) ? even_orig[e_idx++] : odd_orig[o_idx++]; } return even_inv + odd_inv; } int main() { // 测试案例1:无法排序 vector<int> arr1 = {2,0,4,3,1}; cout << "测试案例1交换次数:" << sort_special(arr1) << endl; // 输出-1 // 测试案例2:可排序 vector<int> arr2 = {2,3,0,5,4,1}; cout << "测试案例2交换次数:" << sort_special(arr2) << endl; // 输出3 // 测试案例3:无法排序 vector<int> arr3(200); iota(arr3.begin(), arr3.end(), 0); swap(arr3[86], arr3[115]); cout << "测试案例3交换次数:" << sort_special(arr3) << endl; // 输出-1 return 0; }
四、代码说明
- 可排序判断:通过对比排序后的原数组子序列与目标子序列,确认是否存在排序可能
- 逆序数统计:树状数组实现O(k log k)复杂度的逆序数计算,满足10万级数组的性能需求
- 排序合并:分别排序偶/奇子序列后合并回原数组,完成符合约束的排序
- 交换次数:两个子序列的逆序数之和即为精确交换次数,与合法操作完全对应
内容的提问来源于stack exchange,提问作者Patric
相关产品推荐
相关产品推荐

