一:移动零(力扣283)
void moveZeroes(vector<int>& nums) { //申请一个辅助数组,存放非零元素,然后再赋值给原数组 vector<int> aux; for(int i=0;i<nums.size();i++){ if(nums[i]) aux.push_back(nums[i]);//借用向量的push_back函数 } for(int i=0;i<aux.size();i++){ nums[i]=aux[i]; } //补0 for(int i=aux.size();i<nums.size();i++){ nums[i]=0; } }
很简单的一个思路:
申请额外数组存储原数组非零元素(利用push_back),然后赋值回给原数组,最后在末位编变成0
法二:
void moveZeroes(vector<int>& nums) { /* 原地实现该功能,不申请额外空间: 定义一个索引k,指向数组第一个元素,i用于遍历数组,如果是非零元素把值赋给k位置。 k更新到下一个位置,直至遍历结束,从k开始往后补0 */ int k=0;//nums中[0,k)存储非零元素 //for循环,不断把非零元素依次赋值给k for(int i=0;i<nums.size();i++) if(nums[i]){//nums[i]不为0 nums[k++]=nums[i];//别忘了移动k索引 } //补零 for(int i=k;i<nums.size();i++) nums[i]=0; }
法三:零与非零的交换
void moveZeroes(vector<int>& nums) { int k=0;//nums中[0,k)存储非零元素 for(int i=0;i<nums.size();i++) if(nums[i]){ swap(nums[i],nums[k]); k++; } }
与法二相比,由于直接交换,少了重新赋值为0操作
优化细节:
避免自己和自己交换,可以考虑i与k的大小关系
if(i!=k) swap(nums[k++],nums[i]);
else k++;
二:挑选颜色
void sortColors(vector<int>& nums) { /* 因为元素只有0,1,2三种可能,所以采用计数排序思想: 分别统计出:0的个数,1的个数以及2的个数,赋值即可 */ int count[3]={0}; for(int i=0;i<nums.size();i++){ assert(nums[i]>=0&&nums[i]<=2); count[nums[i]]++;//数值直接对应频次 } //定义一个不断累计的索引 int index=0; for(int i=0;i<count[0];i++) nums[index++]=0; for(int i=0;i<count[1];i++) nums[index++]=1; for(int i=0;i<count[2];i++) nums[index++]=2; }
几个代码小细节:
- 因为只有0,1,2且想累计出现个数,直接以元素值计算频次
- 由于从头一次按照个数赋值0,1,2;所以直接建立一个不断累计的index
其实可以把三路快排的思想移到这
这是最后想达到的效果:
其实关键在于对遍历索引i的元素处理:
分以下三种情况:
如果元素为1,i右移
如果元素是2,和two前一个元素交换,i不动,two--
如果元素是1,和zero后元素交换,zero++,i++
代码实现
#include<iostream> #include<vector> using namespace std; class soluction{ public: void sortColors(vector<int>& nums){ /* 功能实现: [0,zero]全是0----也就是zero初始化为-1 [two,n-1]全是1---也就是two初始化为n(假设nums长度为n) 索引i遍历数组,遇到1右移,控制(zero,two)范围全是1 */ int zero=-1; int two=nums.size(); int i=0;//从头遍历数组 while( i<two ){//遍历结束条件 if(nums[i]==1) i++; else if(nums[i]==2){ two--; swap(nums[two],nums[i]); } else{//nums[i]==0 zero++; swap(nums[i],nums[zero]); i++; } } } }; int main(){ int arr[]={1,0,2,0,2,1,1,2,0}; vector<int> vec(arr,arr+sizeof(arr)/sizeof(int)); soluction().sortColors(vec); for(vector<int>::iterator it=vec.begin();it!=vec.end();it++) cout<<(*it)<<" "; }
快速排序两种实现
#include<iostream> #include<vector> using namespace std; int partition(vector<int>& a,int L,int R){ /* 索引i记录小于基准元素的最后一个位置,初始化为基准元素位置 索引j用于遍历 j遇到的元素≥基准元素---往后遍历 j遇到的元素<基准元素---交换i后位置和当前位置,i++ */ int e=a[L];//以最左边元素为基准元素 int i=L; int j=L+1; for(;j<=R;j++){ if(a[j]<e){ swap(a[++i],a[j]); } } swap(a[L],a[i]); return i; } void quickSortHelper(vector<int>& a,int L,int R){ if(L>=R) return;//递归结束 int p=partition(a,L,R); quickSortHelper(a,L,p-1); quickSortHelper(a,p+1,R); } void quickSort(vector<int>& a){//一定要加& quickSortHelper(a,0,a.size()-1); } int main(){ vector<int> arr={1,22,3,55,66,7,2,11,1,3,4}; quickSort(arr); vector<int>::iterator it; for(it=arr.begin();it!=arr.end();it++){ cout<<(*it)<<" "; } return 0; }
注意要在vector加上*,否则原向量不改变
/* 双指针法: i不断右移,使得其之前的元素均≤基准元素 j不断左移,使得其之后的元素均≥基准元素 如果i,j均停止,交换两者所指元素,然后i右移,j左移 最后----把j位置,即小于基准元素最后一个元素和基准元素交换 */ int e=a[L]; int i=L+1,j=R;//i,j指向除基准元素的两头 while(1){ while(i<=j&&a[i]<=e) i++; while(i<=j&&a[j]>=e) j--; if(i>j) break; swap(a[i++],a[j--]); } swap(a[L],a[j]); return j; }
三:寻找第k个大的元素
这里要用到一个快速排序分区的性质:
每次快排后,都可以确定该元素在顺序中的正确位置。
也就是说:排序后,倒数第一个最大,倒数第二个第二大。。。。
只要快排的索引落在了该位置,该位置就可以确定第几大或第几小
int partition(vector<int>& nums,int l,int r){ int e=nums[l]; int i=l; int j=l+1; for(;j<=r;j++){ if(nums[j]<e) swap(nums[j],nums[++i]); } swap(nums[l],nums[i]); return i; } int findKthLargest(vector<int>& nums, int k) { int target=nums.size()-k; int l=0; int r=nums.size()-1; while(1){//条件:L<=R,但是该题一定有解,无需判断 int p=partition(nums,l,r); if(p==target) return nums[p]; else if(p>target) r=p-1; else l=p+1; } }