一.双指针
解决数组分块
1.移动零
void moveZeroes(vector<int>& nums)
{
int cur=0,dest=0;
while(cur<nums.size())
{
if(nums[cur])
{
swap(nums[cur],nums[dest]);
dest++;
}
cur++;
}
}
2.复写零
void duplicateZeros(vector<int>& arr)
{
//1.找到最后一个数
int cur=0,dest=-1,n=arr.size();
while(cur<n)
{
if(arr[cur])
dest++;
else
dest+=2;
if(dest>=n-1)
break;
else
cur++;
}
//2.处理边界情况 1 0 2 3 0 0 4 5
if(dest==n)
{
arr[n-1]=0;
cur--;
dest-=2;
}
//3.复写
while(cur>=0)
{
if(arr[cur])
{
arr[dest--]=arr[cur];
}
else
{
arr[dest--]=0;
arr[dest--]=0;
}
cur--;
}
}
3.快乐数
class Solution
{
public:
int Sum(int n)
{
int sum=0;
while(n>0)
{
int x=n%10;
sum=sum+x*x;
n=n/10;
}
return sum;
}
bool isHappy(int n)
{
int slow=n,fast=Sum(n); //这里的快慢指针并不是真正意义上的指针,而是用具体的数来充当
while(slow!=fast)
{
slow=Sum(slow);
fast=Sum(Sum(fast));
}
if(slow==1)
return true;
else
return false;
}
};
4.盛水最多的容器
class Solution
{
public:
int maxArea(vector<int>& height)
{
int left=0,right=height.size()-1,MAX=0;
while(left<right)
{
int v=min(height[left],height[right])*(right-left); //直接算出每次遍历的最大值,避免过多的暴力枚举
MAX=max(v,MAX);
//移动指针
if(height[left]>height[right])
right--;
else
left++;
}
return MAX;
}
};
5.有效三角形的个数
注意:当三角形的两个小边相加大于第三边 时,也说明可以构成三角形
class Solution
{
public:
int triangleNumber(vector<int>& nums)
{
//1.先排序
sort(nums.begin(),nums.end());
//2.利用双指针求解
int sum=0,n=nums.size();
for(int i=n-1;i>=2;i--) //固定住最大的数(c)
{
int left=0,right=i-1; //a就是left,b就是right
while(left<right)
{
if(nums[left]+nums[right]>nums[i]) //a+b>c 有right-left个三角形,整个b被用完了,所以right--
{
sum += (right-left);
right--;
}
else // a+b<=c 构不成三角形
{
left++;
}
}
}
return sum;
}
};
6.三数之和
class Solution
{
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
sort(nums.begin(),nums.end());
int n=nums.size();
vector<vector<int>> arr;
for(int i=0;i<n;) //固定a
{
if(nums[i]>0)break;
int left=i+1,right=n-1;
while(left<right)
{
int sum=nums[left]+nums[right];
if(sum+nums[i]==0)
{
arr.push_back({nums[i],nums[left],nums[right]});
left++;
right--;
//去重
while(left<right && nums[left]==nums[left-1])
//不能是nums[left]==nums[left+1],因为是不能跟前一个数重复,而不是不能跟后一个数重复
{
left++;
}
while(left<right && nums[right]==nums[right+1])
//不能是nums[right]==nums[right-1],因为是不能跟前一个数重复,而不是不能跟后一个数重复
{
right--;
}
}
else if(sum+nums[i]>0)
{
right--;
}
else if(sum+nums[i]<0)
{
left++;
}
}
//去重a
i++;
while(i<n && nums[i]==nums[i-1])
{
i++;
}
}
return arr;
}
};
7.四数之和
class Solution
{
public:
vector<vector<int>> fourSum(vector<int>& nums, int target)
{
sort(nums.begin(),nums.end());
int n=nums.size();
vector<vector<int>> arr;
for(int i=0;i<n;)
{
long long suma=target-nums[i];
for(int j=i+1;j<n;)
{
long long sumb=(long long)target-nums[i]-nums[j];
int left=j+1,right=n-1;
while(left<right)
{
if(nums[left]+nums[right]==sumb)
{
arr.push_back({nums[i],nums[j],nums[left],nums[right]});
left++;
right--;
//去重
while(left<right && nums[left]==nums[left-1])
{
left++;
}
while(left<right && nums[right]==nums[right+1])
{
right--;
}
}
else if(nums[left]+nums[right]>sumb)
{
right--;
}
else if(nums[left]+nums[right]<sumb)
{
left++;
}
}
//去重b
j++;
while(j<n-2 && nums[j]==nums[j-1])
{
j++;
}
}
//去重a
i++;
while(i<n-1 && nums[i]==nums[i-1])
{
i++;
}
}
return arr;
}
};
二.滑动窗口(同向双指针,先满足数组单调性(同正负累加))
1.长度最小的子数组
class Solution
{
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int n=nums.size(),sum=0;
int len=n;
int left=0,right=0;
for(left=0,right=0;right<n;right++)
{
sum += nums[right]; //进窗口
while(sum>=target) //判断
{
len=min(len,right-left+1); //更新结果
sum -= nums[left]; //出窗口
left++;
}
}
if(left==0)len=0;
return len;
}
};
2.无重复字符的最长字串
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
int hash[128]={0}; //使用数组来表示哈希表
int left=0,right=0,len=0;
int n=s.size();
while(right<n)
{
hash[s[right]]++; //进窗口
while(hash[s[right]] > 1) //判断
{
hash[s[left]]--; //出窗口
left++;
}
len=max(len,right-left+1);
right++;
}
return len;
}
};
3.最大连续1的个数Ⅲ
1004. 最大连续1的个数 III - 力扣(LeetCode)
class Solution
{
public:
int longestOnes(vector<int>& nums, int k)
{
int left = 0, right = 0, sum = 0, n = nums.size(), len = 0;
while(right < n) //进窗口
{
if (nums[right] == 0)
sum++;
while(sum > k) //判断
{
if(nums[left]==0) //出窗口
sum--;
left++;
}
len=max(len,right-left+1); //计算结果
right++;
}
return len;
}
};
4.将X减到0的最小操作数
1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)
class Solution
{
public:
int minOperations(vector<int>& nums, int x)
{
int s=0,left=0,right=0,n=nums.size(),len=0;
for(auto e:nums)
{
s += e;
}
int target=s-x;
if(target<0) return -1;
if(target==0) return n;
int sum=0;
while(right<n)
{
sum += nums[right]; //进窗口
right++;
while(sum>target) //判断
{
sum -= nums[left]; //出窗口
left++;
}
if(sum==target)
{
len=max(len,right-left); //更新结果
}
}
if(len==0)
return -1;
else
return n-len;
}
};
5.水果成篮
class Solution
{
public:
int totalFruit(vector<int>& fruits)
{
unordered_map<int,int> hash; //统计窗口内出现多少种水果,有几个
int left=0,right=0,n=fruits.size(),len=0;
while(right<n)
{
hash[fruits[right]]++;//进窗口
right++;
while(hash.size()>2) //判断,种类大于2
{
hash[fruits[left]]--; //出窗口
if(hash[fruits[left]]==0)
{
hash.erase(fruits[left]); //删除掉first为fruits[left]的数据
}
left++;
}
len=max(len,right-left);
}
return len;
}
};
6.找到字符串中所有字母异位词
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
class Solution
{
public:
vector<int> findAnagrams(string s, string p)
{
vector<int> ret;
int hash1[26]={0}; //统计字符串p中每个字符出现的次数
for(auto ch:p)
{
hash1[ch-'a']++;
}
int hash2[26]={0}; //统计窗口里中每个字符出现的次数
for(int left=0,right=0,count=0;right<s.size();right++)
{
hash2[s[right]-'a']++; //进窗口
if(hash2[s[right]-'a'] <= hash1[s[right]-'a'])count++; //count为有效字符个数
if(right-left+1 > p.size()) //判断
{
if(hash2[s[left]-'a'] <= hash1[s[left]-'a']) //要是有效字符滑出去了
{
count--;
}
hash2[s[left]-'a']--; //出窗口
left++;
}
//更新结果
if(count==p.size())
{
ret.push_back(left);
}
}
return ret;
}
};
7.串联所有单词的字串
class Solution
{
public:
vector<int> findSubstring(string s, vector<string>& words)
{
vector<int> ret;
unordered_map<string,int> hash1; //保存words里的单词和频率
for(auto& s:words) hash1[s]++;
int len=words[0].size(),m=words.size();
for(int i=0;i<len;i++) //执行len次滑动窗口
{
unordered_map<string,int> hash2;
for(int left=i,right=i,count=0;right + len <= s.size();right += len)
//right + len < s.size() 是为了保证下面单词入哈希表时不越界
{
//1.进窗口
string in=s.substr(right,len);
hash2[in]++;
if(hash2[in] <= hash1[in]) count++; //有效单词
//2.判断
if(right - left + 1 > len*m)
{
//3.出窗口
string out=s.substr(left,len);
if(hash2[out] <= hash1[out])
{
count--; //该出去的这个字符串,在hash2中次数比hash1少,说明是有效字符
}
hash2[out]--;
left += len;
}
//4.更新结果
if(count==m) ret.push_back(left);
}
}
return ret;
}
};
8.最小覆盖字串
class Solution
{
public:
string minWindow(string s, string t)
{
int hash1[128]={0}; //128的哈希表适用于英文字符串,hash1用来统计t中每个字符的频次
int kinds = 0; //kinds用来统计hash1中有效字符个数
for(auto ch:t)
{
if(hash1[ch]==0) //ch一定在字符串t里面
{
kinds++;
}
hash1[ch]++;
}
int hash2[128]={0}; //统计窗口内每个字符的频次
int minlen=INT_MAX , begin=-1; //因为不知道s和t谁大,所以令minlen=INT_MAX
for(int left=0,right=0,count=0;right<s.size();right++)
{
char in=s[right]; //进窗口
hash2[in]++;
if(hash1[in] == hash2[in]) count++; //当两个哈希表里的同一字母频次相等时,有效字符加一
while(count==kinds) //判断
{
if(right-left+1<minlen) //更新结果
{
minlen=right-left+1;
begin=left;
}
char out=s[left]; //出窗口
left++;
if(hash1[out]==hash2[out]) count--;
hash2[out]--;
}
}
if(begin==-1) return "";
else return s.substr(begin,minlen);
}
};
三.二分查找
1.二分查找
class Solution
{
public:
int search(vector<int>& nums, int target)
{
int left=0,right=nums.size()-1;
while(left<=right) //注意是小于等于
{
int mid=left+(right-left)/2; //如果是(left+right)/2 ,会有数据溢出的风险
if(nums[mid]<target)left=mid+1;
else if(nums[mid]>target)right=mid-1;
else if(nums[mid]==target) return mid;
}
return -1;
}
};
2.在排序数组中查找元素位置
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
class Solution
{
public:
vector<int> searchRange(vector<int>& nums, int target)
{
if(nums.size()==0) return {-1,-1};
int begin=0;
//1.二分求左端点
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<target) left=mid+1;
else right=mid;
} //最后left和right会指向同一点,该点为左端点
//判断是否有结果
if(nums[left] != target) return {-1,-1};
begin=left; //记录左端点
//2.二分求右端点
left=begin,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left+1)/2;
//"left+(right-left+1)/2" 与"left+(right-left)/2"仅在个数为偶数时结果不一样,一个算出左边的,一个算出右边的
if(nums[mid]>target) right=mid-1;
else left=mid;
}
return {begin,right};
}
};
左端点(mid=left+(right-left)/2)求出来mid靠左,所以不能有left=mid,不然会死循环,右端点相反。加一对着减法(right=mid-1)
3.x的平方根
class Solution
{
public:
int mySqrt(int x)
{
if(x<1) return 0;
int left=1,right=x;
while(left<right)
{
long long mid=left+(right-left+1)/2; //用long long防溢出
if(mid*mid>x) right=mid-1;
else left=mid; //如果是left=mid+1的话,可能会使left指针越过正确的x的位置
}
return left;
}
};
3.搜索插入位置
class Solution
{
public:
int searchInsert(vector<int>& nums, int target)
{
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<target)left=mid+1;
else right=mid;
}
if(left==nums.size()-1 && nums[left]<target) left++;
return left;
}
};
4.山脉数组的顶峰索引
只要能被分成两段性质不同的区间的,都可以用二分
class Solution
{
public:
int peakIndexInMountainArray(vector<int>& arr)
{
int left=0,right=arr.size()-1;
while(left<right)
{
int mid=left+(right-left+1)/2;
if(arr[mid]>=arr[mid-1]) left=mid;
else right=mid-1;
}
return left;
}
};
5.寻找峰值
class Solution
{
public:
int findPeakElement(vector<int>& nums)
{
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]>nums[mid+1]) right=mid;
else if(nums[mid]<nums[mid+1]) left=mid+1;
}
return left;
}
};
6.寻找旋转排序数组的最小值
153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
class Solution
{
public:
int findMin(vector<int>& nums)
{
int n=nums.size()-1;
int left=0,right=n;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]>nums[n]) left=mid+1;
else right=mid;
}
return nums[left];
}
};
四.前缀和 ——>用于快速求出数组中某一连续区间的和O(1)
1.前缀和模板题
#include<iostream>
#include<vector>
using namespace std;
int main()
{
//1.读入数据
int n,q;
cin>>n>>q;
vector<int> arr(n+1);
for(int i=1;i<=n;i++) cin>>arr[i];
//2.预处理出来一个前缀和数组
vector<long long> dp(n+1); //防止溢出
for(int i=1;i<=n;i++) dp[i]=dp[i-1]+arr[i];
//3.使用前缀和数组
int l=0,r=0;
while(q--)
{
cin>>l>>r;
cout<<dp[r]-dp[l-1]<<endl;
}
return 0;
}
2.二维前缀和
#include<iostream>
#include<vector>
using namespace std;
int main()
{
//1.读入数据
int n=0,m=0,q=0;
cin>>n>>m>>q;
vector<vector<int>> arr(n+1,vector<int>(m+1));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>arr[i][j];
//2.预处理前缀和矩阵
vector<vector<long long>> dp(n+1,vector<long long>(m+1)); //防止溢出
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][j]=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1];
//3.使用dp表
int x1=0,y1=0,x2=0,y2=0;
while(q--)
{
cin>>x1>>y1>>x2>>y2;
cout<<dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1]<<endl;
}
return 0;
}
3. 寻找数组的中心下标
class Solution
{
public:
int pivotIndex(vector<int>& nums)
{
int n=nums.size();
vector<int> f(n),g(n);
//1.预处理前缀和和后缀和数组
f[0]=0,g[n-1]=0;
for(int i=1;i<n;i++)
f[i]=f[i-1]+nums[i-1];
for(int i=n-2;i>=0;i--)
g[i]=g[i+1]+nums[i+1];
//2.使用数组
for(int i=0;i<n;i++)
{
if(f[i]==g[i]) return i;
}
return -1;
}
};
4.除自身以外数组的乘积
238. 除自身以外数组的乘积 - 力扣(LeetCode)
class Solution
{
public:
vector<int> productExceptSelf(vector<int>& nums)
{
int n=nums.size();
vector<int> f(n),g(n),answer;
//1.预处理前缀积和后缀积
f[0]=1,g[n-1]=1;
for(int i=1;i<n;i++)
f[i]=f[i-1]*nums[i-1];
for(int i=n-2;i>=0;i--)
g[i]=g[i+1]*nums[i+1];
//2.使用
for(int i=0;i<n;i++)
{
answer.push_back(f[i]*g[i]);
}
return answer;
}
};
5.和为k的子数组
class Solution
{
public:
int subarraySum(vector<int>& nums, int k)
{
unordered_map<int,int> hash; //统计前缀和x 和 出现的次数,把前缀和放哈希表里
hash[0]=1; //刚开始的时候,前缀和x肯定为0,出现一次
int sum=0,ret=0;
for(auto x:nums)
{
sum += x; //计算当前位置的前缀和x
if(hash.count(sum-k)) //若hash中有前缀和x的值为sum-k的
ret += hash[sum-k];
hash[sum]++; //因为sum出现过,所以sum次数加一
}
return ret;
}
};
6.和可被k整除的子数组
974. 和可被 K 整除的子数组 - 力扣(LeetCode)
class Solution
{
public:
int subarraysDivByK(vector<int>& nums, int k)
{
unordered_map<int,int> hash; //统计前缀和x的余数与次数
hash[0%k]=1; //0这个数的余数
int sum=0,ret=0;
for(auto x:nums)
{
sum += x; //算出当前的前缀和
int r=(sum%k+k)%k; //本来r=sum%k就行,但是现在这样写可以负数的余数变回正数(修正后的余数)
if(hash.count(r)) ret += hash[r]; //找有几个x前缀和的余数等于r(sum%k)
hash[r]++;
}
return ret;
}
};
7.连续数组
class Solution
{
public:
int findMaxLength(vector<int>& nums)
{
unordered_map<int,int> hash; //存的是前缀和x和他的下标j
hash[0]=-1; //默认有一个前缀和为0的情况
for(int i=0;i<nums.size();i++)
if(nums[i]==0) nums[i]=-1;
int sum=0,ret=0;
for(int i=0;i<nums.size();i++)
{
sum += nums[i]; //计算当前位置前缀和
if(hash.count(sum)) ret=max(ret,i-hash[sum]);
//如果存在前缀和x等于sum,说明找到了一段前缀和使j+1到i这个区间和为0,长度为i-(j+1)+1
else hash[sum]=i; //如果不存在,那么放入把值为sum的前缀和x存入哈希表,下标为i
}
return ret;
}
};
8.矩阵区域和
class Solution
{
public:
vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k)
{
int m=mat.size(),n=mat[0].size();
//1.预处理一个前缀和矩阵
vector<vector<int>> dp(m+1,vector<int>(n+1)); //dp的行列各加一,防止越界到-1
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mat[i-1][j-1];
//mat[i-1][j-1] ,而不是mat[i][j]
//2.使用前缀和矩阵
vector<vector<int>> ans(m,vector<int>(n)); //ans与mat同等规模篇
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
int x1=max(0,i-k)+1; //从mat到dp减了1,从dp变回ans还要再加回去
int y1=max(0,j-k)+1;
int x2=min(m-1,i+k)+1;
int y2=min(n-1,j+k)+1;
ans[i][j]=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1];
}
}
return ans;
}
};
五.位运算
1.基础运算
class Solution
{
public:
int hammingWeight(uint32_t n)
{
int sum=0;
for(int i=0;i<32;i++)
{
if(((n>>i)&1)==1)
sum++;
}
return sum;
}
};
class Solution
{
public:
int one_number(int x)
{
int sum=0;
for(int i=0;i<32;i++)
{
if(((x>>i)&1)==1) sum++;
}
return sum;
}
vector<int> countBits(int n)
{
vector<int> ans;
for(int i=0;i<=n;i++)
{
ans.push_back(one_number(i));
}
return ans;
}
};
class Solution
{
public:
int hammingDistance(int x, int y)
{
int m=x^y;
int sum=0;
for(int i=0;i<32;i++)
{
if(((m>>i)&1)==1)
sum++;
}
return sum;
}
};
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
int sum=0;
for(int i=0;i<nums.size();i++)
{
sum = sum ^ nums[i];
}
return sum;
}
};
260. 只出现一次的数字 III - 力扣(LeetCode)
class Solution
{
public:
vector<int> singleNumber(vector<int>& nums)
{
long long sum=0;
for(auto x:nums) sum ^= x;
//这个异或值必定不为0,所以要找的那两个数一定有某一位是1和0,所以分成两组,每组各有一个目标值
unsigned int mark = (unsigned int)(sum & (-sum)); //mark为sum里最右侧的1 (00000010)
vector<int> ans(2);
for(auto x:nums)
{
if((unsigned int)(x & mark) == 0) //如 10010101 & 00000010 ==0
ans[0] ^= x;
else
ans[1] ^= x;
}
return ans;
}
};
2.判断字符是否唯一
面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode)
class Solution
{
public:
bool isUnique(string astr)
{
if(astr.size()>26) return false;
int bitMap=0; //建立位图
for(auto ch:astr)
{
int i=ch-'a';
//判断字符是否出现过
if(((bitMap>>i)&1)==1) return false;
bitMap |= (1<<i); //把字符加入位图
}
return true;
}
};
3.丢失的数字
class Solution
{
public:
int missingNumber(vector<int>& nums)
{
int sum=0;
int n=nums.size();
for(int i=0;i<n;i++)
{
sum = sum ^ i ^ nums[i];
}
sum = sum ^ n;
return sum;
}
};
4.两整数之和
class Solution
{
public:
int getSum(int a, int b)
{
while(b)
{
int x=a^b; //算出无进位相加结果
b=(unsigned int)(a&b)<<1;
//算出进位,注意-1要转成unsigned int,-1的第一个1意义不明确
a=x;
}
return a;
}
};
5.只出现一次的数字Ⅱ
137. 只出现一次的数字 II - 力扣(LeetCode)
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
int ret=0;
for(int i=0;i<32;i++) //对结果的每一位分别进行修改
{
int sum=0;
for(auto x:nums)
if(((x>>i)&1)==1)
sum++;
if(sum%3==0) ret &= (~(1<<i)); //将第i位改成0
else ret |= (1<<i); //将第i位改成1
}
return ret;
}
};
6.消失的两个数字
面试题 17.19. 消失的两个数字 - 力扣(LeetCode)
class Solution
{
public:
vector<int> missingTwo(vector<int>& nums)
{
int sum=0;
int n=nums.size();
//1.将所有事数异或在一起
for(int i=0;i<n;i++)
sum=sum^nums[i]^(i+1);
sum=sum^(n+1)^(n+2);
//2.找到不同位
int mark=sum&(-sum);
//3.分两类来异或
vector<int> ans(2);
for(auto x:nums)
{
if((x&mark)==0) ans[0] ^= x;
else ans[1] ^= x;
}
for(int i=1;i<=n+2;i++)
{
if((i&mark)==0) ans[0] ^= i;
else ans[1] ^= i;
}
return ans;
}
};
六.模拟(比葫芦画瓢)
1.替换所有问号
class Solution
{
public:
string modifyString(string s)
{
int n=s.size();
for(int i=0;i<n;i++)
{
if(s[i]=='?')
{
//修改s[i]
for(char ch='a';ch<='z';ch++)
{
if(i==0 && ch!=s[i+1])
{
s[i]=ch;
break;
}
if(i==n-1 && ch!=s[i-1])
{
s[i]=ch;
break;
}
if(ch!=s[i+1] && ch!=s[i-1])
{
s[i]=ch;
break;
}
}
}
}
return s;
}
};
2.提莫攻击
class Solution
{
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration)
{
int ret=0;
for(int i=1;i<timeSeries.size();i++)
{
if(timeSeries[i]-timeSeries[i-1] >= duration) ret += duration;
else ret += timeSeries[i]-timeSeries[i-1];
}
return ret+duration;
}
};
3.Z字形变换
class Solution
{
public:
string convert(string s, int numRows)
{
string ret;
int n=s.size();
if(numRows==1) return s;
int d=2*numRows-2;
for(int i=0;i<numRows;i++)
{
if(i==0) //处理第一行
for(int k=0,j=i;j+k*d<n;k++)
ret.push_back(s[j+k*d]);
else if(i==numRows-1) //处理最后一行
for(int k=0,j=i;j+k*d<n;k++)
ret.push_back(s[j+k*d]);
else
{ //处理中间行
for(int k=0,j=i;j+k*d<n;k++)
{
ret.push_back(s[j+k*d]);
if((d-i+k*d)<n)
ret.push_back(s[d-i+k*d]);
}
}
}
return ret;
}
};
4. 外观数列
class Solution
{
public:
string countAndSay(int n)
{
string ret="1";
for(int i=1;i<n;i++) //描述n-1次
{
int left=0,right=0,count=0;
string ans;
for(left=0,right=0;right<ret.size();) //用双指针分组计数
{
while(right<ret.size() && ret[left]==ret[right]) right++;
count=right-left;
ans += to_string(count) + ret[left];
left=right;
}
ret=ans;
}
return ret;
}
};
5.数青蛙
class Solution
{
public:
int minNumberOfFrogs(string croakOfFrogs)
{
string t="croak";
int n=t.size();
vector<int> hash(n); //数组哈希表
unordered_map<char,int> index; //存[x,x这个字符对应的下标]
for(int i=0;i<n;i++) index[t[i]]=i; //一般都是通过下标找数据,这回反过来了
for(auto ch:croakOfFrogs)
{
if(ch=='c')
{
if(hash[n-1] != 0) hash[n-1]--;
hash[0]++;
}
else
{
int i=index[ch];
if(hash[i-1]==0) return -1;
hash[i-1]--;
hash[i]++;
}
}
for(int i=0;i<=n-2;i++)
if(hash[i] != 0) return-1; //表不合法
return hash[n-1];
}
};
七.分治
1.颜色分类
class Solution
{
public:
void sortColors(vector<int>& nums)
{
int n=nums.size();
int left=-1,right=n,i=0;
while(i!=right)
{
if(nums[i]==1) i++;
else if(nums[i]==0)
{
left++;
swap(nums[left],nums[i]);
i++;
}
else if(nums[i]==2)
{
right--;
swap(nums[right],nums[i]);
}
}
}
};
2.排序数组(数组分块,快排)
class Solution
{
public:
int getRandom(vector<int>& nums,int left, int right)
{
int r=rand();
return nums[r%(right-left+1)+left];
}
void my_qsort(vector<int>& nums,int l,int r)
{
if(l>=r) return; //递归结束条件
//数组分三块
int key=getRandom(nums,l,r); //根据这个随机数把数组分三块 小于key,等于key,大于key
int i=l,left=i-1,right=r+1; //left在区间最左侧外面一个,right在最右侧
while(i<right)
{
if(nums[i]<key)
{
left++;
swap(nums[left],nums[i]);
i++;
}
else if(nums[i]==key) i++;
else
{
right--;
swap(nums[right],nums[i]);
}
}
//分完之后: [l,left],[left+1,right-1],[right,r]
//接下来再排左右两边
my_qsort(nums,l,left); //排左边
my_qsort(nums,right,r); //排右边
}
vector<int> sortArray(vector<int>& nums)
{
srand(time(NULL)); //种下时间种子
my_qsort(nums,0,nums.size()-1);
return nums;
}
};
3.数组中的第k个最大元素(快速选择算法)
215. 数组中的第K个最大元素 - 力扣(LeetCode)
class Solution
{
public:
int getRandom(vector<int>& nums,int left,int right)
{
int r=rand();
return nums[r%(right-left+1)+left];
}
int my_qsort(vector<int>& nums,int l,int r,int k)
{
if(l==r) return nums[l]; //结束条件
//1.随机选择基准元素
int key=getRandom(nums,l,r);
//2.根据基准元素将数组分三块
int i=l,left=l-1,right=r+1;
while(i<right)
{
if(nums[i]<key)
{
left++;
swap(nums[left],nums[i]);
i++;
}
else if(nums[i]==key) i++;
else
{
right--;
swap(nums[right],nums[i]);
}
}
//分成[l,left] [left+1,right-1] [right,r]
//3.分情况讨论
int c=r-right+1; //b,c为区间的元素个数
int b=right-left-1;
if(c>=k) return my_qsort(nums,right,r,k);
else if(b+c>=k) return key; //不在c中,只能在b里了
else return my_qsort(nums,l,left,k-b-c); //只能去a中找第k-b-c大的了
}
int findKthLargest(vector<int>& nums, int k)
{
srand(time(NULL));
return my_qsort(nums,0,nums.size()-1,k);
}
};
4.最小的K个数(快速选择算法)
面试题 17.14. 最小K个数 - 力扣(LeetCode)
class Solution
{
public:
int getRandom(vector<int>& arr,int left,int right)
{
return arr[rand()%(right-left+1)+left];
}
void my_qsort(vector<int>& arr,int l,int r,int k)
{
if(l>=r) return;
//1.数组分三块
int key=getRandom(arr,l,r);
int i=l,left=l-1,right=r+1;
while(i<right)
{
if(arr[i]<key)
{
left++;
swap(arr[left],arr[i]);
i++;
}
else if(arr[i]==key) i++;
else
{
right--;
swap(arr[right],arr[i]);
}
}
//[l,left],[left+1,right-1],[right,r]
//2.分情况讨论
int a=left-l+1,b=right-left-1;
if(a>=k) my_qsort(arr,l,left,k); //只需要给左边排序就行(只排第一块)
else if(a+b>=k) return ; //k>a && a+b>=k,已经排好了,不用再排了,
else my_qsort(arr,right,r,k-a-b); //只需要去右边在排k-a-b个就行
}
vector<int> smallestK(vector<int>& arr, int k)
{
srand(time(NULL));
my_qsort(arr,0,arr.size()-1,k);
return {arr.begin(),arr.begin()+k};
}
};
5.排序数组(归并排序)
class Solution
{
public:
vector<int> tmp; //使用全局变量构造辅助数组,节省空间
void mergeSort(vector<int>& nums,int left,int right)
{
if(left>=right) return ;
//1.选择中间点划分区间
int mid=(right-left)/2+left;
//[left,mid] [mid+1,right]
//2.排序左右区间
mergeSort(nums,left,mid);
mergeSort(nums,mid+1,right);
//3.合并两个有序数组
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid && cur2<=right)
{
tmp[i++]=(nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++]);
}
//处理没有遍历完的数组
while(cur1<=mid) tmp[i++]=nums[cur1++];
while(cur2<=right) tmp[i++]=nums[cur2++];
//4.还原
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;
}
};
6.逆序对个数
LCR 170. 交易逆序对的总数 - 力扣(LeetCode)
class Solution
{
int tmp[50001];
public:
int mergesort(vector<int>& nums,int left,int right)
{
if(left>=right) return 0;
int ret=0;
//1.找中间点,将数组分为两部分 [left,mid][mid+1,right]
int mid=(right-left)/2+left;
//2.求左边个数+排序 求右边个数+排序
ret += mergesort(nums,left,mid);
ret += mergesort(nums,mid+1,right);
//3.再求一左一右的个数 (递归的主逻辑) ,还要排序
int cur1=left,cur2=mid+1,i=0;
while((cur1<=mid) && (cur2<=right))
{
if(nums[cur1] > nums[cur2])
{
ret += mid-cur1+1;
tmp[i++]=nums[cur2++]; //别忘了还得排序,计算个数不过是在排序中进行的
}
else
{
tmp[i++]=nums[cur1++];
}
}
//4.处理剩下的元素
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 mergesort(record,0,record.size()-1);
}
};
7.计算右侧小于当前元素的个数
315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)
class Solution
{
public:
vector<int> ret;
vector<int> index; //记录nums中当前元素的原始下标
int tmpNums[500001]; //两个辅助数组
int tmpIndex[500001];
void mergesort(vector<int>& nums,int left,int right)
{
if(left>=right) return;
//1.根据中间元素,分成两块 [left,mid] [mid+1,right]
int mid=(right-left)/2+left;
//2.先处理左右两边
mergesort(nums,left,mid);
mergesort(nums,mid+1,right);
//3.处理主逻辑(一左一右)
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid && cur2<=right)
{
if(nums[cur1]>nums[cur2])
{
ret[index[cur1]] += right-cur2+1; //index[cur1]就是cur1的下标
tmpNums[i]=nums[cur1];
tmpIndex[i++]=index[cur1++]; //元素和对应的下标都要放到辅助数组里面去
}
else
{
tmpNums[i]=nums[cur2];
tmpIndex[i++]=index[cur2++];
}
}
//4.处理剩下的排序
while(cur1<=mid)
{
tmpNums[i]=nums[cur1];
tmpIndex[i++]=index[cur1++];
}
while(cur2<=right)
{
tmpNums[i]=nums[cur2];
tmpIndex[i++]=index[cur2++];
}
//5.还原
for(int j=left;j<=right;j++)
{
nums[j]=tmpNums[j-left];
index[j]=tmpIndex[j-left];
}
}
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,n-1);
return ret;
}
};
8.翻转对
class Solution
{
public:
int tmp[50001];
int mergesort(vector<int>& nums,int left,int right)
{
if(left>=right) return 0;
int ret=0;
//1.先根据中间元素划分区间 [left,mid] [mid+1,right]
int mid=(right-left)/2+left;
//2.计算左右翻转对的数量
ret += mergesort(nums,left,mid);
ret += mergesort(nums,mid+1,right);
//3.计算翻转个数的主逻辑
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid) //固定cur1看cur2
{
while(cur2<=right && nums[cur1]/2.0<=nums[cur2]) cur2++; //*2可能会溢出,用/2.0就可以了
if(cur2 > right) break;
ret += right-cur2+1; //此时nums[cur1]>2*nums[cur2]
cur1++;
}
//4.合并两个有序数组(归并排序的主逻辑) ---排降序
cur1=left,cur2=mid+1,i=0;
while(cur1<=mid && cur2<=right)
tmp[i++]=(nums[cur1]>=nums[cur2] ? nums[cur1++] : nums[cur2++]);
while(cur1<=mid) tmp[i++]=nums[cur1++];
while(cur2<=right) tmp[i++]=nums[cur2++];
//5.还原
for(int j=left;j<=right;j++)
{
nums[j]=tmp[j-left];
}
return ret;
}
int reversePairs(vector<int>& nums)
{
return mergesort(nums,0,nums.size()-1);
}
};