五、位运算
常见位运算总结
&:有0就是0;
|:有1就是1
^:相同为0,相异就是1/无进位相加
给定一个数n,确定它的二进制表示中的第x位是0还是1:二进制中权值最小的是第0位,所以int整型是从第0位到第31位。于是n>>x &1就可以了
将一个数n的二进制表示的第x位修改成为1: (1<<x) | n
将一个数n的二进制表示的第x位修改成为0:(~(1<<x)) & n
提取一个数n的二进制表示中最右侧的1:n&(-n),正数变成负数的二进制表示就是按位取法再加1。-n的本质是将最右侧的1的左边区域(不包括最右侧的1也就是当前位置)全部变成相反,其余的都不变。
干掉一个数n二进制表示中最右侧的1,也就是将这个1变成0:n&(n-1) ,n-1的本质是将最右侧的1右边的区域(包含1)全部变成相反。
异或:a^a=0;a^0=a;a^b^c=a^(b^c);交换律;采用无进位相加很容易证明
1.判断字符是否唯一
采用位图的思想:因为单独的一个整型变量就有32位比特位优化点:鸽巢原理,也就是如果字符串的长度超过26,必定存在重复字符。
class Solution { public:bool isUnique(string astr) {//利用鸽巢原理进行优化if(astr.size()>26) return false;int bitmap=0;for(auto ch:astr){//判断字符出现在哈希表中if((1<<(ch-'a')) & bitmap) return false;//将当前字符加入到位图中else bitmap+=(1<<(ch-'a'));//bitmap |= (1<<(ch-'a'))}return true;} };
2.丢失的数字
class Solution { public:int missingNumber(vector<int>& nums) {int n=nums.size()+1;int result=0;for(auto ch:nums) result^=ch;for(int i=0;i<n;i++) result^=i;return result;} };
3.两整数之和(巧妙)
利用异或的无进位相加,然后找到需要进位的地方。通过a&b可以找到需要进位的地方,因为只有1&1才能得到1,而这也是我们需要进位的地方。(a&b)<<1才是我们需要进位的位置。class Solution { public:int getSum(int a, int b) {while(b){int temp_a=a;a=temp_a^b;//先算出无进位相加的结果b=(temp_a&b)<<1;//算出进位}return a;} };
4.只出现一次的数字
class Solution { public:int singleNumber(vector<int>& nums) {int ret=0;for(int i=0;i<32;i++)//依次去修改ret中的每一位{int sum=0;//统计nums中第i比特位出现1的次数for(auto ch:nums)if(ch & (1<<i)) sum++;if(sum%3) ret |= (1<<i);//修改ret的第i比特位}return ret;} };
5.消失的两个数
class Solution { public:vector<int> missingTwo(vector<int>& nums) {vector<int> result;//将所有的数全部都异或到一起int tmp=0;int n=nums.size();for(auto ch:nums) tmp^=ch;for(int i=1;i<=n+2;i++) tmp^=i;//找到tmp中,比特位为1的那一位pos,在该比特位上a和b的比特值是不一样的。int pos;for(pos=0;pos<32;pos++) if((tmp>>pos)&1) break;//根据pos位的不同,划分成为两类来异或int a=0,b=0;for(auto ch:nums){if((ch>>pos)&1) a^=ch;else b^=ch;}for(int i=1;i<=n+2;i++){if((i>>pos)&1) a^=i;else b^=i;}return {a,b};} };
六、模拟算法
模拟算法就是依葫芦画瓢,思路比较简单,但是算法流程存在很多细节,将流程转换成为算法有比多要注意的细节。模拟题找优化一般都是通过找规律进行的。
6.替换所有的问号
class Solution { public:string modifyString(string s) {int n=s.size();//遍历字符串for(int i=0;i<n;i++){//找到?字符if(s[i]=='?'){//遍历26个字母替换该字符for(char ch='a';ch<='z';ch++){//找到符合要求的字符,下面的if条件是关键if((i==0 || ch!=s[i-1]) && (i==n-1 || ch!=s[i+1])){s[i]=ch;break;}}}}return s;} };
7.提莫攻击
class Solution { public:int findPoisonedDuration(vector<int>& timeSeries, int duration) {int result=0;for(int i=0;i<timeSeries.size()-1;i++){if(timeSeries[i+1]-timeSeries[i]>=duration) result+=duration;else result+=(timeSeries[i+1]-timeSeries[i]);}return result+duration;} };
8.Z字形变换
class Solution { public:string convert(string s, int numRows) {//处理边界情况if(numRows==1) return s;string result;int n=s.size();int d=numRows*2-2;//公差d//1.先处理第一行for(int i=0;i<n;i+=d){result+=s[i];//}//2.处理中间行for(int k=1;k<numRows-1;k++)//枚举每一行{for(int i=k,j=d-k;i<n || j<n;i+=d,j+=d)//注意不要将i<n || j<n写成了i<n && j<n{if(i<n) result+=s[i];if(j<n) result+=s[j];}}//3.最后处理最后一行for(int i=numRows-1;i<n;i+=d){result+=s[i];}return result;} };
9.外观数列
class Solution { public:string countAndSay(int n) {string result="1";if(n==1) return result;for(int i=1;i<n;i++)//翻译n次{string tmp;int len=result.size();//采用双指针来进行翻译for(int left=0,right=0;right<len;){while(right<len && result[left]==result[right]) right++;//当right=len-1的边界情况也可以正确处理tmp+=to_string(right-left);//to_string函数处理下标left和right的值不同的时候tmp+=result[left];left=right;}result=tmp;}return result;} };
10.数青蛙
上述总结就是代码的逻辑非常重要
class Solution { public:int minNumberOfFrogs(string croakOfFrogs) {string t="croak";int n=t.size();vector<int> hash(n);//用数组来模拟哈希表unordered_map<char,int> index;//存储t字符串每个字符char以及对应的下标intfor(int i=0;i<n;i++) index[t[i]]=i;//遍历字符串for(auto ch:croakOfFrogs){//1、如果ch不在字符串t的范围内if(index.count(ch)==0) return -1;//2、如果字符ch不是c并且前面并没有匹配的字符if(ch!=t[0] && hash[index[ch]-1]<1) return -1;//3、正常运作if(ch==t[0] && hash[n-1]<1) hash[0]++;else if(ch==t[0] && hash[n-1]>=1) hash[0]++,hash[n-1]--;else hash[index[ch]]++,hash[index[ch]-1]--;}for(int i=0;i<n-1;i++) if(hash[i]!=0) return -1;return hash[n-1];} };
七、分治
分治就是分而治之,将一个大问题转换成为若干个相同或者相似的子问题,直到划分到子问题可以快速解决为止。
10.颜色分类(快排关键)
class Solution { public:void sortColors(vector<int>& nums) {int n=nums.size();int left=-1,right=n;for(int i=0;i<right;)//条件是i<right不是i<n,这是一个易错点。{if(nums[i]==0) swap(nums[++left],nums[i++]);else if(nums[i]==1) i++;else swap(nums[--right],nums[i]);}} };
11.排序数组 (快排)
class Solution { public:int getrandom(vector<int>& nums,int left,int right){int r=rand();return nums[r % (right-left+1) + left];}void sortArray_help(vector<int>& nums,int l,int r){//定义递归出口if(l>=r) return;//随机方式选择基准元素int standar=getrandom(nums,l,r);int left=l-1;int right=r+1;//分成三块for(int i=l;i<right;){if(nums[i]<standar) swap(nums[++left],nums[i++]);else if(nums[i]==standar) i++;else swap(nums[--right],nums[i]); }sortArray_help(nums,l,left);sortArray_help(nums,right,r);}vector<int> sortArray(vector<int>& nums) {srand(time(NULL));//种下一棵随机数种子sortArray_help(nums,0,nums.size()-1);return nums;} };
12.数组中的第k个最大元素
class Solution { public:int getrandom(vector<int>&nums,int left,int right){int r=rand();return nums[r%(right-left+1)+left];}int qsort(vector<int>& nums,int l,int r,int k){if(l==r) return nums[l];//容易遗漏的点//1、随机选择基准元素int standard=getrandom(nums,l,r);//2、根据基准元素将数组分成三块int left=l-1;int right=r+1;int i=l;while(i<right){if(nums[i]<standard) swap(nums[++left],nums[i++]);else if(nums[i]==standard) i++;else swap(nums[--right],nums[i]);}//分情况讨论//下面的三个条件判断是关键if(r-right+1>=k) return qsort(nums,right,r,k);else if(r-left>=k) return standard;else return qsort(nums,l,left,k-r+left);}int findKthLargest(vector<int>& nums, int k) {srand(time(NULL));return qsort(nums,0,nums.size()-1,k);} };
13.最小k个数(未做)
14.归并排序(归并排序)
class Solution { public:vector<int> tmp;//辅助数组用来排序void mergesort(vector<int>& nums,int left,int right){if(right<=left) return;//1、选择中间点划分区间int mid=(left+right)/2; //2、将左右区间排序int left1=left;int right1=mid;int left2=mid+1;int right2=right;mergesort(nums,left1,right1);mergesort(nums,left2,right2);int i=0;//3、合并两个有序数组while(left1<=right1 && left2<=right2) tmp[i++]=nums[left1]<=nums[left2]?nums[left1++]:nums[left2++];//4、处理没有遍历完的数组while(left1<=right1) tmp[i++]=nums[left1++];while(left2<=right2) tmp[i++]=nums[left2++];//5、还原for(int i=left;i<=right;i++) nums[i]=tmp[i-left]; }vector<int> sortArray(vector<int>& nums) {tmp.resize(nums.size());mergesort(nums,0,nums.size()-1);return nums;} };
15.交易逆序对的总数(未做)
class Solution { public:int reversePairs_helper(vector<int>& nums,int left,int right){if(left>=right) return 0;int ret=0;//1、找中间点,将数组分成两部分int mid=(left+right)>>1;//2.左边的个数+排序+右边的个数+排序ret +=reversePairs_helper(nums,left,mid);ret +=reversePairs_helper(nums,mid+1,right);//3.一左一右的个数int cur1=left,cur2=mid+1,i=0;vector<int> tmp(right-left+2);while(cur1<=mid && cur2<=right){if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur1++];else{ret+= mid-cur1+1;tmp[i++]=nums[cur2++];}}while(cur1<=mid) tmp[i++]=nums[cur1++];while(cur2<=right) tmp[i++]=nums[cur2++];for(int j=left;j<=right;j++)nums[j]=tmp[j-left];return ret;}int reversePairs(vector<int>& record) {return reversePairs_helper(record,0,record.size()-1);} };
16.計算右側小於當前元素的個數
class Solution {vector<int> ret;vector<int> index;//记录nums当前元素的下标、int tmpNums[500010];int tmpIndex[500010]; public:vector<int> countSmaller(vector<int>& nums) {int n=nums.size();ret.resize(n);index.resize(n);for(int i=0;i<n;i++)index[i]=i;mergesort(nums,0,nums.size()-1);return ret;}void mergesort(vector<int>& nums,int left,int right) {if(left>=right) return;int mid=(left+right)>>1;mergesort(nums,left,mid);mergesort(nums,mid+1,right);int cur1=left,cur2=mid+1,i=0;while(cur1<=mid && cur2<=right){if(nums[cur1]>nums[cur2]){tmpNums[i]=nums[cur1];ret[index[cur1]]+=right-cur2+1;tmpIndex[i]=index[cur1];i++;cur1++;}else{tmpNums[i]=nums[cur2];tmpIndex[i]=index[cur2];i++;cur2++;}}while(cur1<=mid){tmpNums[i]=nums[cur1];tmpIndex[i]=index[cur1];i++;cur1++;}while(cur2<=right){tmpNums[i]=nums[cur2];tmpIndex[i]=index[cur2];i++;cur2++;}for(int j=left;j<=right;j++){nums[j]=tmpNums[j-left];index[j]=tmpIndex[j-left];}} };