ch1 of programmercarl


1 二分查找

1.1 题目描述

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

示例1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4

示例2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1

1.2 思路

简单二分法,如果 nums[m]==target 说明找到下标直接返回即可。

完整代码如下:

  • Cpp 实现
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size()-1;
        while(l <= r){      //可以改为(l < r)
            int m = l + (r-l)/2;
            if(nums[m] == target){
                return m;
            }else if(nums[m] < target){
                l = m + 1;
            }else{
                r = m - 1;  //如果循环条件更改,则需要改为 `r = m;`
            }
        }
        return -1;
    }
};
  • C 实现
int search(int* nums, int numsSize, int target){
    int left=0,right=numsSize-1;
    while(left<=right){
        int mid=left+(right-left)/2;
        if(nums[mid]==target){
            return mid;
        }else if(nums[mid]<target){
            left=mid+1;
        }else{
            right=mid-1;
        }
    }
    return -1;
}

2 移除元素

2.1 题目描述

给你一个数组 nums 和一个值 val ,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例1:

输入: nums = [3,2,2,3], val = 3
输出: 2, nums = [2,2]

示例2:

输入: nums = [0,1,2,2,3,0,4,2], val = 2
输出: 5, nums = [0,1,4,0,3]

2.2 思路

设置两个指针,首先判断快指针对应的数组中的值是否等于 val, 如果是则不进行操作,如果不等于则说明这个当前遍历到的元素应该进行保留,因而将其赋给慢指针指向的位置。整体的思路就是遍历一遍数组,遇到与 val 不一样的元素就把该元素插到原数组的前端(用慢指针指定位置),最后返回慢指针即为答案。

完整代码如下:

  • Cpp 实现
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int fast = 0, slow = 0;
        for(; fast < nums.size(); ++fast){
            if(nums[fast] == val){
                continue;
            }
            nums[slow++] = nums[fast];
        }
        return slow;
    }
};
  • C 实现
int removeElement(int* nums, int numsSize, int val){
    int order=0;
    for(int i=0;i<numsSize;i++){
        if(nums[i]!=val){
            nums[order++]=nums[i];
        }
    }
    return order;
}

3 有序数组的平方

3.1 题目描述

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例1:

输入: nums = [-4,-1,0,3,10]
输出: [0,1,9,16,100]

示例2:

输入: nums = [-7,-3,2,3,11]
输出: [4,9,9,49,121]

3.2 思路

最简单的方法就是先遍历一遍数组,遍历过程中对元素进行平方处理,然后最后再对平方过后的数组进行排序即可。(C++实现)

或者新建一个数组 res,容量与 nums 的容量相同,然后创建一个位置指针指向该数组的最后一个位置。创建两个指针分别指向 nums 的开头与结尾(因为 nums 中有负数,平方过后的最大值只会出现在最左侧或最右侧),对左右两个指针指向的元素进行平方,大者写入答案数组 res 中并将指针前移一位,而指向原数组中那个平方后较大的指针移动一位。(C实现)

完整代码如下:

  • Cpp 实现
class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for(int i = 0; i < nums.size(); ++i){
            nums[i] = nums[i]*nums[i];
        }
        sort(nums.begin(), nums.end());
        return nums;
    }
};
  • C 实现
int* sortedSquares(int* nums, int numsSize, int* returnSize){
    int *ret = malloc(sizeof(int) * numsSize);
    *returnSize = numsSize;
    int i = numsSize - 1;
    int right = numsSize - 1;
    for(int left = 0; left <= right;){
        int lSquare = nums[left]*nums[left];
        int rSquare = nums[right]*nums[right];
        if(lSquare < rSquare){
            ret[i--] = rSquare;
            right--;
        }else{
            ret[i--] = lSquare;
            left++;
        }
    }
    return ret;
}

4 长度最小的子数组

4.1 题目描述

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

示例1:

输入: target = 7, nums = [2,3,1,2,4,3]
输出: 2

示例2:

输入: target = 4, nums = [1,4,4]
输出: 1

示例3:

输入: target = 11, nums = [1,1,1,1,1,1,1,1]
输出: 0

4.2 思路

经典滑动窗口题目,设置两个指针,右指针用来遍历数组,左指针置 0. 创建一个 sum 变量用来记录当前滑动窗口中的元素的加和。当 sum < target 时不进行操作,只将当前元素的值加到 sum 中;当sum >= target 说明当前窗口中有符合条件的连续子数组,将当前子数组的长度与当前res 进行比较并将较小者赋给 res, 并将左指针右移一位,相当于窗口缩短一位。

完整代码如下:

  • Cpp 实现
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0,sum = 0, res = INT_MAX;
        for(int i = 0; i < nums.size(); i++){
            sum += nums[i];
            while(sum >= target){
                res = min(res, i - left + 1);
                sum -= nums[left++];
            }
        }
        return res==INT_MAX ? 0:res;
    }
};
  • C 实现
int minSubArrayLen(int target, int* nums, int numsSize){
    int ans = INT_MAX;
    int leftIdx = 0;
    int sum = 0;
    for(int i = 0; i < numsSize; i++){
        sum += nums[i];
        while(sum >= target){
            ans = fmin(ans,i-leftIdx+1);
            sum -= nums[leftIdx++];
        }
    }
    return ans == INT_MAX ? 0 : ans;
}

5 螺旋矩阵 II

5.1 题目描述

给你一个正整数 n ,生成一个包含 1 到 $n^2$ 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

示例1:

输入: n = 3
输出: [[1,2,3],[8,9,4],[7,6,5]]

示例2:

输入: n = 1
输出: [[1]]

5.2 思路

直接模拟即可。设置上下左右的边界(0,n),然后创建两个变量,分别存放当前行和当前的列,然后创建 cnt 变量用来记录当前待插入的元素。

4个for循环即可解决问题:第一个 for 循环解决正向行插入(从左到右),第二个 for 解决下行列插入(从上到下),第三个 for 循环解决反向行插入(从右到左),第四个 for 解决上行列插入(从下到上)。

但是需要注意终止循环的条件是什么。举几个例子可以发现,最后的终止条件就是第一个 for 循环结束后和第三个 for 循环结束后判断是否有 cnt > n*n ,如果这个条件成立说明已经插入完成,跳出循环并返回答案。

另外,每个 for 循环结束后都需要对边界进行更新,第一个 for 循环结束后需要对 右边界 进行减 1,第二个 需要对 下边界 进行减 1,第三个 需要对 左边界 进行加 1,第四个需要对 上边界 进行加 1.

还有一个细节,就是每次 for 循环结束后,都需要对行变量和列变量进行操作,因为 上一个循环会往当前循环的方向多走一步 ,所以进行下一次循环前需要将这一步减去,然后再往要走的方向迈一步,为实现代码的统一,则将行变量和列变量均初始化为 -1

完整代码如下:

  • Cpp 实现
class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res;    
        vector<int> tmp(n);
        res.resize(n, tmp);     //定义二维vector的容量为n*n
        int left = 0, right = n, high = 0, low = n; //边界
        int cnt = 1, c = -1, r = -1;    //依次为当前待插入数值、列变量、行变量
        while(1){
            for(++r, ++c; c < right; ++c){
                res[r][c] = cnt++;
            }
            if(cnt > n*n) break;    //判断是否插入已经完成,待插入数值最大为n^2
            --right;    //更新右边界
            for(++r, --c; r < low; ++r){
                res[r][c] = cnt++;
            }
            --low;      //更新下边界
            for(--c, --r; c >= left; --c){
                res[r][c] = cnt++;
            }
            if(cnt > n*n) break;
            ++left;     //更新左边界
            for(--r, ++c; r > high; --r){
                res[r][c] = cnt++;
            }
            ++high;     //更新上边界
        }
        return res;
    }
};
  • C 实现
int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){
    bool flag = 0;
    *returnSize = n;
    int **res = (int **)malloc(sizeof(int *)*n);
    *returnColumnSizes = (int *)malloc(sizeof(int)*n);
    for(int i = 0; i < n; i++){
        res[i] = (int *)malloc(sizeof(int) * n);
        (*returnColumnSizes)[i] = n;
        memset(res[i], 0, sizeof(int) * n);
    }
    int cnt = 1;
    int col = 0, row = -1;
    int colLeft = 0, rowLeft = 0;
    int colRight = n, rowRight = n;
    res[0][0] = cnt++;
    if(cnt > n) return res;
    while(1){
        for(row+=1,col += 1; col < colRight; col++){
            res[row][col] = cnt++;
        }
        if(cnt > n*n){
            break;
        }
        colRight--;
        for(row += 1,col-=1; row < rowRight; row++){
            res[row][col] = cnt++;
        }
        rowRight--;
        for(col -= 1, row -= 1; col >= colLeft; col--){
            res[row][col] = cnt++;
        }
        if(cnt > n*n){
            break;
        }
        colLeft++;
        for(col+=1,row -= 1; row > rowLeft; row--){
            res[row][col] = cnt++;
        }
        //break;
        rowLeft++;
    }
    return res;
}

总结

  本章都相对简单,涉及二分法、滑动窗口、双指针三种最基本的算法以及过程模拟。

  前三种方法在后边会经常用到,二分法常用来解决在一个指标的约束遍历整个数组最少/最大的值,滑动窗口则是解决连续子数组(或串)的问题,双指针用处太多了,一时写不出来,等强一点再来总结,这里先挖个坑,模拟比较锻炼思维,要把整个过程在脑中过一遍,与之相对的是递归,递归不能想太深,一想深就乱!


文章作者: Vyron Su
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Vyron Su !