题意整理:
题目给出个没有重复元素的数组a,需要要将数组内第n大的数字和第m大的数交换位置,返回交换后的数组
方法一:排序+替换
核心思想:
可以将原数组复制一份,将复制得到的数组进行排序(从大到小),然后取出第n大和第m大的数的数值,再在原数组中进行查找并替换即可。因为题目保证的数组中没有重复元素,所以可以保证算法的正确性
核心代码:
class Solution { public: vector<int> sovle(vector<int>& a, int n, int m) { vector<int>res(a);//用于排序 sort(res.begin(), res.end(), greater<int>()); //取得排序后对应的需要交互的元素的值 int r1 = res[n - 1]; int r2 = res[m - 1]; for(int& i : a) { //查找对应元素进行交换 if(i == r1) { i = r2; } else if(i == r2) { i = r1; } } return a; } };
复杂度分析:
时间复杂度:。对数组排序的开销为,替换过程中的开销为,故总开销为
空间复杂度:,既用于排序的临时数组的开销
方法二:部分排序(快排思想)+替换
核心思想:
方法一是直接对整个数组排序,开销为,而实际上只是需要获取位置n以及位置m上的数值即可,故可以利用快排的思想,不对整个数组排序,而只是对数组的一部分排序。
快排算法:快速排序是对冒泡排序的优化,它的思想就是每次选定一个枢纽值,然后对区间进行一次遍历,使得小于枢纽值的数位于其左侧,大于枢纽值的数位于其右侧,再对其两次元素进行递归。
而在这里,可以对递归环节进行优化,既然题目只需要获取一个排序后的点的值,所以在确保一侧元素都对另一侧元素存在有序性(左侧都小于右侧,右侧都大于左侧)时,只需要对一侧进行递归,既如果所需值在左侧,既递归左侧,所需值在右侧,既只递归右侧。
注意的点:快速排序存在最坏情况,此时退化为冒泡排序,为防止其退化,枢纽值的选取不能够固定,一般有两种方法,既随机取枢纽值,以及三值取中法,STL中的sort采用的就是三值取中法,此处也采用三值取中法。
三值取中法首先对数组左值,右值和中间值进行调整,保证左,中,右三值的顺序性,然后选择中值作为枢纽,同时将枢纽归位到右值左侧,然后从左值右侧和枢纽值左侧开始处理,处理完毕后将枢纽值归为即可
下图简单的说明了三值取中法的第一轮排序过程
原数组 3 5 1 2 7 4 8
三值取中调整后 2 5 1 3 7 4 8 (确保左值,中值,右值的顺序性)
枢纽值(3)移动 2 5 1 4 7 3 8 (4与3互换位置)
处理中间(5-7) 2 1 5 4 7 3 8
枢纽值归位 2 1 3 4 7 5 8
核心代码:
class Solution { public: int medium(vector<int>& nums, int left, int right) { //三值取中 int mid = (left + right) / 2; if(nums[left] > nums[right]) swap(nums[left], nums[right]); if(nums[left] > nums[mid]) swap(nums[left], nums[mid]); if(nums[mid] > nums[right]) swap(nums[mid], nums[right]); swap(nums[mid], nums[right - 1]);//将枢纽放到末尾 return nums[right - 1]; } void quickSort(vector<int>& nums, int left, int right, int k) { //单侧快排 if(left >= right) return; int p = medium(nums, left, right); int i = left, j = right - 1; while(i < j) { while(nums[++i] < p); while(nums[--j] > p); if(i < j) swap(nums[i], nums[j]); } swap(nums[i], nums[right - 1]);//枢纽归位 //只递归需要的一侧 if(i < k) quickSort(nums, i + 1, right, k); else if(i > k) quickSort(nums, left, i - 1, k); } int help(vector<int>& nums, int k) { quickSort(nums, 0, nums.size() - 1, nums.size() - k); return nums[nums.size() - k]; } vector<int> sovle(vector<int>& a, int n, int m) { vector<int> res(a); //取得排序后对应的需要交互的元素的值 int r1 = help(res, n); int r2 = help(res, m); for(int& i : a) { //查找对应元素进行交换 if(i == r1) { i = r2; } else if(i == r2) { i = r1; } } return a; } };
复杂度分析:
时间复杂度:,部分排序的开销期望值为,详细证明很复杂,可以参考算法导论9.2节,而替换过程的开销同样为
空间复杂度:,既排序用的临时数组的开销