Leetcode刷题笔记Day1:数组

77 阅读4分钟

二分查找

  • 使用条件:数组为有序数组,同时题目还强调数组中无重复元素
  • 边界条件:例如到底是 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(n2n^2),空间复杂度为O(1)(题目要求用二维数组,不含在空间复杂度内)

参考资料

[1] 代码随想录

[2] Leetcode题解