【1】53. 最大子数组和
日期:12.06
2.类型:线段树,动态规划,分治
3.方法一:动态规划(一次题解)
对于数组中的每个位置,计算以该位置元素结尾的最大子数组和,然后取所有位置中的最大值作为结果。
动态规划转移方程:f(i)=max{f(i−1)+nums[i],nums[i]}
关键代码:
cpp
for(const auto &x:nums){
pre=max(pre+x,x);
maxNum=max(pre,maxNum);
}
4.方法二:分治法(半解)
核心数据结构:
lSum :以区间左端点开始的最大子数组和
rSum :以区间右端点结束的最大子数组和
mSum :区间的最大子数组和(不限制位置)
iSum :区间的总和
合并状态(核心逻辑):
cpp
Status pushUp(Status l, Status r) {
int iSum = l.iSum + r.iSum; // 总和 = 左区间和 + 右区间和
int lSum = max(l.lSum, l.iSum + r.lSum); // 要么是左区间的lSum,要么跨越到右区间
int rSum = max(r.rSum, r.iSum + l.rSum); // 要么是右区间的rSum,要么跨越到左区间
int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum); // 最大子数组可能完全在左、完全在右、或跨越中点
return (Status) {lSum, rSum, mSum, iSum};
}
关键代码:
cpp
// 递归获取区间[l, r]的状态
Status get(vector<int> &a, int l, int r){
// 基本情况:区间只有一个元素
if(l==r){
return (Status) {a[l], a[l], a[l], a[l]};
}
// 计算中点
int m=(l+r)>>1;
// 递归获取左右子区间的状态
Status lSub=get(a,l,m);
Status rSub=get(a,m+1,r);
// 合并两个子区间的状态
return pushUp(lSub, rSub);
}
【2】190. 颠倒二进制位
日期:12.07
2.类型:分治,位运算
3.方法一:逐位颠倒(官方题解)
逐位操作详解:
- 取出最低位:
n & 1:
& 1操作可以取出一个数的最低位(第0位)
如果最低位是1,结果为1;如果最低位是0,结果为0
- 左移到正确位置:
<< (31 - i)
原数的第0位(最低位)应该成为反转后的第31位(最高位)
原数的第1位应该成为反转后的第30位
原数的第31位应该成为反转后的第0位
所以,原数的第i位应该左移(31 - i)位
- 设置到结果中:
rev |= ...
|=是按位或赋值操作
将左移后的位设置到rev的对应位置
因为rev初始为0,所以直接或操作即可
- 右移原数:
n >>= 1
将n右移一位,丢弃已处理的最低位
下次循环处理下一个位
关键代码:
cpp
// 循环32次,处理32位
for(int i=0;i<32&&n>0;++i){
// 1. 取出n的最低位:n & 1
// 2. 将这个位左移到反转后的位置:(31 - i)
// 3. 通过按位或(|)操作设置到rev的对应位置
rev |= (n & 1) << (31 - i);
n >>= 1;
}
【3】191. 位1的个数
日期:12.08
2.类型:分治,位运算
3.方法一:循环检查二进制位(一次题解)
- 创建位掩码:
1 << i
1的二进制:00000000000000000000000000000001
1 << i:将1左移i位,创建一个只在第i位为1的掩码
- 检查特定位:
n & (1 << i)
按位与操作&:只有两个位都为1时,结果位才为1
如果n的第i位为1,那么n & (1 << i)的结果非0
如果n的第i位为0,那么n & (1 << i)的结果为0
- 条件判断:
if (n & (1 << i))
如果表达式结果为非0,进入if语句块,计数器加1
如果表达式结果为0,跳过if语句块
关键代码:
cpp
for(int i=0;i<32;i++){
// (1<<i):创建一个只在第i位为1的掩码
// n&(1<<i):如果n的第i位为1,结果非0;否则为0
if(n&(1<<i)){
ret++; // 如果第i位为1,计数器加1
}
}
【4】215. 数组中的第K个最大元素 - 力扣(LeetCode)
日期:12.09
2.类型:分治,位运算
3.方法一:基于快速排序的选择方法(官方题解)
分解: 将数组 a[l⋯r] 「划分」成两个子数组 a[l⋯q−1]、a[q+1⋯r],使得 a[l⋯q−1] 中的每个元素小于等于 a[q],且 a[q] 小于等于 a[q+1⋯r] 中的每个元素。其中,计算下标 q 也是「划分」过程的一部分。
解决: 通过递归调用快速排序,对子数组 a[l⋯q−1] 和 a[q+1⋯r] 进行排序。
合并: 因为子数组都是原址排序的,所以不需要进行合并操作,a[l⋯r] 已经有序。
上文中提到的 「划分」 过程是:从子数组 a[l⋯r] 中选择任意一个元素 x 作为主元,调整子数组的元素使得左边的元素都小于等于它,右边的元素都大于等于它, x 的最终位置就是 q。
关键代码:
cpp
if (l==r)
return nums[k];
int partition=nums[l];
int i=l-1,j=r+1;
// Hoare分区方案
while(i<j){
// 从左向右找到第一个≥基准的元素
do i++; while(nums[i]<partition);
// 从右向左找到第一个≤基准的元素
do j--; while(nums[j]>partition);
// 如果i<j,交换这两个元素
if(i<j)
swap(nums[i], nums[j]);
}
// 递归处理包含第k个元素的那一半
if(k<=j)
return quickselect(nums,l,j,k);
else
return quickselect(nums,j+1,r,k);
}
【5】324. 摆动排序 II
日期:12.10
2.类型:分治,排序
3.方法一:排序(一次题解)
先对数组排序
将数组分成两半:前半部分(较小的一半)和后半部分(较大的一半)
从后向前分别取前半部分和后半部分的元素,交叉放入结果数组中
关键代码:
cpp
int n=nums.size();
vector<int> arr=nums;
sort(arr.begin(),arr.end());
// 计算分界点:将数组分成两半
int x=(n+1)/2;
// 交叉放置:前半部分放偶数位,后半部分放奇数位
// 从后向前取,避免相同元素相邻
for (int i=0,j=x-1,k=n-1;i<n;i+=2,j--,k--){
// 偶数位置:放前半部分的元素
nums[i]=arr[j];
// 奇数位置:放后半部分的元素
if(i+1<n){
nums[i+1]= arr[k];
}
}
【6】372. 超级次方
日期:12.11
2.类型:分治,排序
3.方法一:倒序遍历(半解)

关键代码:
cpp
int pow(int x,int n){
int res=1;
x%=MOD; // 先取模,确保x在模范围内
while(n){
// 如果n是奇数,将当前x乘入结果
if(n%2){
// 使用long防止乘法溢出
res=(long) res*x%MOD;
}
x=(long) x*x%MOD;
n/=2;
}
return res;
}
日期:12.12
2.类型:分治,排序
3.方法一:排序(一次题解)
计算每个点到原点的距离(平方,避免开方运算)
按照距离从小到大排序
返回前K个点
关键代码:
cpp
sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>& v) {
return u[0]*u[0]+u[1]*u[1]<v[0]*v[0]+v[1]*v[1];
});
return {points.begin(), points.begin() + k};
日期:12.13
2.类型:分治,前缀和,矩阵
3.方法一:二维前缀和 + 排序(半解)
二维前缀异或:类似二维前缀和,但使用异或运算
动态规划公式:pre[i][j] = pre[i-1][j] ^ pre[i][j-1] ^ pre[i-1][j-1] ^ matrix[i-1][j-1]
收集所有值并排序:找出第K大的值
关键代码:
cpp
//计算每个位置的前缀异或
for(int i=1;i<=m;++i){
for(int j=1;j<=n;++j){
pre[i][j]=pre[i-1][j] ^ // 上方矩形
pre[i][j - 1] ^ // 左方矩形
pre[i - 1][j - 1] ^ // 左上方矩形
matrix[i - 1][j - 1];// 当前元素
results.push_back(pre[i][j]);
}
}
日期:12.14
2.类型:分治,字符串
3.方法一:自定义排序(半解)
数字字符串的比较规则:
首先比较长度:长度越长的数字越大
长度相同则按字典序比较:由于都是数字字符,字典序与数值序一致
按降序排列,取第K大的元素
关键代码:
cpp
auto cmp=[](const string& s1, const string& s2) -> bool {
// 首先比较字符串长度
if(s1.size()>s2.size()){
return true; // s1长度更大,所以数值更大
}
else if(s1.size()<s2.size()){
return false; // s1长度更小,所以数值更小
}
else {
// 对于数字字符串,字典序大的数值也大
return s1 > s2;
}
};
日期:12.15
2.类型:分治,排序
3.方法一:直接排序(半解)
对于每个查询:
根据 trim 参数,比较每个数字的最后 trim 位
使用稳定排序,当数字相同时保持原有顺序
排序后取第 k 小的数字对应的原索引
关键代码:
cpp
for(int i=0;i<queries.size();++i){
auto &q=queries[i]; // 当前查询 [k, trim]
iota(idx, idx + n, 0);
//按照裁剪后的数字排序
stable_sort(idx, idx + n, [&](int a, int b){
auto &s=nums[a],&t=nums[b];
// 比较最后 trim 位
for(int j=m-q[1];j<m;++j){
if(s[j]!=t[j]){
return s[j]<t[j];
}
}
return false;
});
ans[i]=idx[q[0]-1];
}