二分查找
- 使用条件:数组为有序数组,同时题目还强调数组中无重复元素
- 边界条件:例如到底是
while(left < right)还是while(left <= right),到底是right = middle呢,还是要right = middle - 1呢? - 原则:循环不变量,在循环中坚持对区间的定义
- 力扣题目链接
- 第一种写法(左闭右闭即[left, right]):
int search(vector<int>& nums, int target) {
int i=0, j=nums.size()-1; //diff
while(i<=j){ //diff
int k=i+(j-i)/2; //防溢出,相当于(i+j)/2
if(nums[k]==target) return k;
else if(nums[k]<target) i=k+1;
else j=k-1; //diff
}
return -1;
}
- 第二种写法(左闭右开即[left, right)):
int search(vector<int>& nums, int target) {
int i=0, j=nums.size(); //diff
while(i<j){ //diff
int k=i+((j-i)>>1); //防溢出,相当于(i+j)/2
if(nums[k]==target) return k;
else if(nums[k]<target) i=k+1;
else j=k; //diff
}
return -1;
}
- 算法复杂度:时间复杂度为O(log n),空间复杂度为O(1)
移除元素
- 要点:数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖
- 方法:双指针法,从后往前删除元素(即直接覆盖)
- 力扣题目链接
- 解法(也可采取类似快排两头夹的写法):
int removeElement(vector<int>& nums, int val) {
int i, j; //i是工作指针,j是数组末尾指针
i=j=nums.size()-1;
while(i>=0){
if(nums[i]!=val) i--;
else{
if(i<j) nums[i]=nums[j];//覆盖
i--; j--;
}
}
return j+1;
}
- 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)
有序数组的平方
- 刚开始的想法是从小到大排序,自然就想到先找出正负数的分界点,然后设置左右指针,代码非常复杂:
vector<int> sortedSquares(vector<int>& nums) {
int n=nums.size();
vector<int> square(n);
int left, right, cnt=0; //cnt记录square位置
if(nums[0]>=0) left=0;
else if(nums[n-1]<=0) left=n-1;
else for(int i=0; i<n; i++)
if(nums[i]<=0 && nums[i+1]>0) left=i;
right=left+1;
while(left>=0 || right<n){
if(left<0){
for(int i=right; i<n; i++, cnt++)
square[cnt]=nums[i]*nums[i];
break;
} else if(right==n){
for(int i=left; i!=-1; i--, cnt++)
square[cnt]=nums[i]*nums[i];
break;
} else{
if(-nums[left]<=nums[right]){
square[cnt++]=nums[left]*nums[left];left--;
} else{
square[cnt++]=nums[right]*nums[right];right++;
}
}
}
return square;
}
- 受到Carl的启发,为什么不能从大到小逆序来写呢?解法如下:
vector<int> sortedSquares(vector<int>& nums) {
int n=nums.size();
vector<int> square(n);
int left=0, right=n-1, cnt=n-1; //cnt记录square位置
while(left<=right){ //一定要取等
if(-nums[left]>nums[right]){
square[cnt--]=nums[left]*nums[left]; left++;
} else{
square[cnt--]=nums[right]*nums[right]; right--;
}
}
return square;
}
- 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)
长度最小的子数组
- 要点:滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置
- 力扣题目链接
- 解法:
int minSubArrayLen(int target, vector<int>& nums) {
int n=nums.size();
int min_cnt=n+1, sum=0; //min_cnt记录最小长度,sum记录临时和
int i=0; //滑动窗口起始位置
for(int j=0; j<n; j++){ //主循环控制滑动窗口尾指针
sum+=nums[j];
while(sum>=target){
if(j-i+1<min_cnt) min_cnt=j-i+1;
sum-=nums[i++]; //精髓所在
}
}
if(min_cnt==n+1) return 0;
return min_cnt;
}
- 算法复杂度:时间复杂度为O(n),空间复杂度为O(1)
螺旋矩阵Ⅱ
- 算法考点:模拟行为
- 原则:循环不变量原则,其实这也是写程序中的重要原则。
- 相信大家有遇到过这种情况:感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的,大家可以在这道题目中体会到这一点。
- 力扣题目链接
- 解法:
vector<vector<int>> generateMatrix(int n) {
int t=0; // top
int b=n-1; // bottom
int l=0; // left
int r=n-1; // right
vector<vector<int>> matrix(n,vector<int>(n));
int k=1;
while(k<=n*n){
for(int i=l; i<=r; ++i, ++k) matrix[t][i]=k;//右
++t;
for(int i=t; i<=b; ++i, ++k) matrix[i][r]=k;//下
--r;
for(int i=r; i>=l; --i, ++k) matrix[b][i]=k;//左
--b;
for(int i=b; i>=t; --i, ++k) matrix[i][l]=k;//上
++l;
}
return matrix;
}
- 不知道大家发现没,代码竟有循环对称的美感!
- 算法复杂度:时间复杂度为O(),空间复杂度为O(1)(题目要求用二维数组,不含在空间复杂度内)
参考资料
[1] 代码随想录
[2] Leetcode题解