数组的经典题目

数组注意点

  1. 数组是存放在连续内存空间上的相同类型数据的集合。

  2. 为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

  3. 如果使用C++的话,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。


t704 二分查找

题目大意:看目标值是不是在给定的有序数组中

解题思路:

  1. 二分查找法的前提是有序
  2. 边界条件要确定好(记住<=和middle-1配对,就是有等就有减),为什么呢? 因为循环的时候是可以知道是都是闭区间所以要减一
  3. middle的赋值是在循环里面

代码:

class Solution{
public:
    int search(vector<int>& nums,int target){
        int left = 0;
        int right = nums.size() - 1; 
        while (left <= right) { 
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; 
            } else if (nums[middle] < target) {
                left = middle + 1; 
            } else { // nums[middle] == target
                return middle; 
            }
        }
        return -1;
    }
}

相关题目推荐

t35 搜索插入位置

题目大意: 看目标值是不是在给定的有序数组中,不在的话,返回它将被插入的位置

解题思路:

  1. 被插入的位置可能是头一个或者是最后面,和中间的情况
  2. 注意:其实二分查找就已经考虑到被插入的位置可能是头一个或者是最后面,和中间的情况,主要是最后返回要注意是return right+1
  3. 为什么目标值在数组所有元素之前是right+1,可以自己模拟一下

代码:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0;
        int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle;
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, -1]
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right],return  right + 1
        // 目标值在数组所有元素之后的情况 [left, right], return right + 1
        return right + 1;
    }
};
XXXXt34 在排序数组中查找元素的第一个位置和最后一个位置

这题有点难,代码有点多先不管

题目大意:找出给定目标值在数组中的开始位置和结束位置。

解题思路:

  1. 情况:
    1. 多个值
    2. 一个值
    3. 没有值

代码:

t27 移除元素

题目大意:原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

解题思路:

  1. 要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖

  2. 双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

    双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

  3. 这个题得自己模拟一下

  4. 为什么是条件是判断不等?因为相等的时候让慢指针停下,让其指向那个值,等到不相等的时候让快指针来覆盖

    不等的时候,快慢指针一起向前走

代码:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

t977 有序数组的平方

题目大意:对给定的数组返回每个数字的平方组成的新数组

解题思路:

  1. 双指针
  2. 数组的平方的最大值就在数组的两端
  3. for循环需要注意一下

代码:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        int k = A.size() - 1;
        vector<int> result(A.size(), 0);
        for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
            if (A[i] * A[i] < A[j] * A[j])  {
                result[k--] = A[j] * A[j];
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }
};

t209 长度最小的子数组

题目大意:和大于等于s的长度最小的连续子数组

解题思路:

  1. 滑动窗口:就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

  2. 滑动窗口主要确实什么:

    1. 窗口内是什么
    2. 如何移动窗口的起始位置
    3. 如何移动窗口的结束位置
  3. 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

    窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

    窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针

代码:

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

t59 螺旋矩阵ii

题目大意:给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

解题思路:面试中出现频率较高的题目就是模拟过程,但却十分考察对代码的掌控能力。

  1. 左闭右开的原则
  2. 循环不变量原则有时候就是考虑边界
  3. 本道题就是考虑的因素比较多

代码:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        
        int count = 1; // 用来给矩阵中每一个空格赋值
        int offset = 1; // 每一圈循环,需要控制每一条边遍历的长度
        
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = starty; j < starty + n - offset; j++) {
                res[startx][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = startx; i < startx + n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 2;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

方法总结

  1. 二分查找法

  2. 双指针法

  3. 滑动窗口

posted @ 2021-12-01 14:53  hongyc77  阅读(148)  评论(0)    收藏  举报