704.二分查找:
由于数组是严格升序 (无重复或有重复均可,但有序),我们可以使用二分查找高效定位目标值。
核心思想:
-
设置两个指针
left和right,分别指向数组的起始和末尾。 -
每次取中间位置
mid = left + (right - left) / 2(避免整型溢出)。 -
比较
nums[mid]与target:- 若相等 → 找到目标,返回
mid; - 若
nums[mid] > target→ 目标在左半部分,令right = mid - 1; - 若
nums[mid] < target→ 目标在右半部分,令left = mid + 1;
- 若相等 → 找到目标,返回
-
当
left > right时,说明未找到,返回-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;
if(nums[mid]>target){
right = mid -1;
}else if(nums[mid]<target){
left = mid+1;
}else return mid;
}
return -1;
}
};
27.移除数组:
解题思路:双指针(快慢指针)
这是一个经典的 原地修改数组 问题,非常适合使用 双指针技巧。
核心思想:
-
使用两个指针:
- 快指针
fast:遍历整个数组,逐个检查每个元素。 - 慢指针
slow:指向"下一个应该保留的位置"。
- 快指针
-
当
nums[fast] != val时,说明这个元素需要保留,就把它复制到nums[slow],然后slow++。 -
最终,
slow的值就是新数组的长度。class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); ++fast) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};
977.有序数组的平方
解题思路:双指针(从两端向中间)
关键观察:
- 原数组是升序排列,但包含负数。
- 平方后,最大值一定出现在两端(最左或最右),因为绝对值最大的数在两端。
- 因此,我们可以使用双指针分别指向首尾 ,比较它们的平方,从结果数组的末尾开始填入较大者。
算法步骤:
- 创建一个长度为
n的结果数组result。 - 初始化:
- 左指针
i = 0 - 右指针
j = n - 1 - 填充位置
k = n - 1(从后往前填)
- 左指针
- 当
i <= j时循环:- 如果
nums[i]^2 <= nums[j]^2,说明右边更大,把nums[j]^2放入result[k],j-- - 否则,把
nums[i]^2放入result[k],i++ - 每次操作后
k--
- 如果
- 返回
result
💡 这种"逆向填充"保证了结果数组天然有序(从小到大),无需额外排序。
209. 长度最小的子数组
解题思路:滑动窗口(双指针)
为什么能用滑动窗口?
- 所有元素 > 0 → 窗口扩大时,和单调递增;窗口缩小时,和单调递减。
- 因此,一旦当前窗口和 ≥
s,就可以尝试 收缩左边界 来寻找更短的满足条件的子数组。
算法流程:
-
初始化:
i = 0:窗口左边界sum = 0:当前窗口和result = INT32_MAX:记录最小长度
-
右指针
j从 0 遍历到末尾:- 将
nums[j]加入窗口(sum += nums[j]) - 只要
sum >= s,就不断收缩左边界 (while循环):- 计算当前窗口长度
j - i + 1 - 更新
result - 减去
nums[i]并右移i
- 计算当前窗口长度
- 将
-
最终若
result仍为初始值,说明无解,返回0;否则返回resultclass Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0;
int sublen = 0;
int i = 0;
for(int j = 0; j < nums.size();j++){
sum += nums[j];
while(sum>=target){
sublen = j-i+1;
result = result < sublen ? result:sublen;
sum -=nums[i++];
}
}
return result == INT32_MAX?0:result;
}
};
59螺旋矩阵
给定一个正整数 n,生成一个 n × n 的矩阵,使得矩阵中的元素从 1 到 n² 按 顺时针螺旋顺序 填充。
核心观察:螺旋 = 一圈一圈地填
想象你在画一个正方形的"回"字:
- 先画最外层(第1圈),
- 再画里面一层(第2圈),
- 直到中心(如果 n 是奇数,中心剩一个格子)。
每一圈都由 四条边 组成:
- 上边:从左 → 右
- 右边:从上 → 下
- 下边:从右 → 左
- 左边:从下 → 上
✅ 关键技巧:每条边都采用"左闭右开"区间(即包含起点,不包含终点),这样四个角不会重复填写!
算法设计要点
1. 圈数
- 总共要循环
loop = n / 2圈。 - 例如:n=5 → loop=2(填两圈),中间 (2,2) 单独处理。
2. 每圈的起始位置
- 第1圈:从
(0, 0)开始 - 第2圈:从
(1, 1)开始 - 第k圈:从
(k-1, k-1)开始 → 用startx,starty记录
3. 每条边的边界控制
- 用
offset控制"右边界"或"下边界"。 - 第1圈:右边到
n - 1(即n - offset,offset=1) - 第2圈:右边到
n - 2(offset=2) - 所以每次循环后
offset++
4. 填充顺序(左闭右开)
上边:(startx, starty) → (startx, n - offset - 1)
右边:(startx, n - offset) → (n - offset - 1, n - offset)
下边:(n - offset, n - offset) → (n - offset, starty + 1)
左边:(n - offset, starty) → (startx + 1, starty)
5. 奇数中心处理
- 如果
n是奇数,最中心(mid, mid)没被填,最后填入count(此时 count == n²)
💡 为什么用"左闭右开"?
避免四个角被重复填写!
比如 n=3:
- 如果上边填
[0][0]到[0][2](包含3), - 右边又从
[0][2]开始填, - 那么
[0][2]就被写了两次!
而"左闭右开":
-
上边填
[0][0],[0][1](停在[0][2]前) -
右边从
[0][2]开始填 → 完美衔接,无重叠!vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int startx = 0, starty = 0; // 每圈起点
int loop = n / 2; // 圈数
int mid = n / 2; // 中心点(n为奇数时用)
int count = 1; // 当前要填的数字
int offset = 1; // 控制边界收缩while (loop--) { int i = startx, j = starty; // 1. 上行:左 → 右(左闭右开) for (; j < n - offset; j++) res[i][j] = count++; // 2. 右列:上 → 下(左闭右开) for (; i < n - offset; i++) res[i][j] = count++; // 3. 下行:右 → 左(左闭右开) for (; j > starty; j--) res[i][j] = count++; // 4. 左列:下 → 上(左闭右开) for (; i > startx; i--) res[i][j] = count++; // 进入内圈 startx++; starty++; offset++; } // 处理奇数中心 if (n % 2 == 1) res[mid][mid] = count; return res;}
58. 区间和
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n, a,b;
cin>>n;
vector<int>res(n,0);
vector<int>p(n,0);
int prevec = 0;
for(int i = 0; i<n ; i++){
cin>>res[i];
prevec += res[i];
p[i] = prevec;
}
while(cin>>a>>b){
int sum ;
if(a == 0) sum = p[b];
else sum = p[b]-p[a-1];
cout<<sum<<endl;
}
}
44. 开发商购买土地
一、题目理解
- 有一个
n × m的网格,每个格子有权值(土地价值)。 - 只能切一刀 :要么横向切 (水平方向,分成上下两块),要么纵向切(垂直方向,分成左右两块)。
- 切完后,两部分都必须非空(即不能在最外侧切)。
- 目标:使两部分的总价值之差的绝对值最小。
解题思路
-
先算出整个网格的总和
sum。 -
尝试所有合法的横向切割位置 (共
n-1种):- 上半部分和 = 前 k 行之和
- 下半部分和 = sum − 上半部分和
- 差 = |sum − 2 × 上半部分和|
-
尝试所有合法的纵向切割位置 (共
m-1种):- 左半部分和 = 前 l 列之和
- 右半部分和 = sum − 左半部分和
- 差 = |sum − 2 × 左半部分和|
-
取所有可能中的最小差。
#include<iostream>
#include<vector>
#include<climits>using namespace std;
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>>vec(n,vector<int>(m,0));
int sum =0;
for(int i = 0; i<n;i++){
for(int j =0; j<m;j++){
cin>>vec[i][j];
sum+=vec[i][j];
}
}
//计算横向
vector<int>vectical(m,0);
for(int i =0;i<m;i++){
for(int j =0; j<n;j++){
vectical[i]+=vec[i][j];
}
}
//计算纵向
vector<int>horizontal(n,0);
for(int j =0; j<n;j++){
for(int i =0;i<m;i++){
horizontal[j]+=vec[i][j];
}
}int result =INT_MAX; int vectcutsum=0; for(int i =0;i<m;i++){ vectcutsum+=vectical[i]; result=min(result,abs(sum-vectcutsum-vectcutsum)); } int horcutsum=0; for(int j =0;j<n;j++){ horcutsum+=horizontal[j]; result=min(result,abs(sum-horcutsum-horcutsum)); } return result;}