数据结构第一天之数组

第一天——数组

二分查找

首先需要注意的是,二分查找需要随机寻址和关键字有序,不然不可使用。

关于二分法,最重要的要点在于区间定义和二分中点的更新,现总结如下:

左右为闭区间的二分查找

循环条件:左浮标小于等于右浮标

左右区间更新:中间位置 + / - 1

代码示例:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 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; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

左闭右开的区间的二分查找

循环条件:左浮标小于右浮标

左右区间更新:左浮标更新为中间位置+1、右浮标更新为中间位置

代码示例:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

数组元素删除

力扣原题

由于数组的地址是连续的,因此要删除某一个元素,不可以直接将其删除,而是需要调整后续的元素位置来填补删除元素后留下的位置空缺(这里需要与链表区分开来)

如果采用每次扫过整个数组,每次删除一个元素,我们得到的算法解决效率较低

  • 时间复杂度:
    O ( n 2 ) O(n^2) O(n2)

  • 空间复杂度:
    O ( 1 ) O(1) O(1)

比较高效的方法是采用双指针,两个指针同时往下扫描整个数组,快指针提取记录,慢指针写入记录,如果遇到要移除的记录就将快指针后跳以覆盖记录。

代码如下:

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;
    }
};
  • 时间复杂度:

O ( n ) O(n) O(n)

长度最小的子数组

力扣原题

这算是一道经典的滑动窗口题了,由于数组内均为正整数,我们只需要从数组开始位置设置一个起点和终点,当起点终点内的数字之和小于目标,向右扩展终点,否则从左缩小起点。这样一次遍历数组即可得到结果。

代码如下:

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;
    }
};

螺旋数组

力扣原题

这种题就挺搞人的,基本就是看你能不能搞明白边界条件。

为了统一循环条件,我们统一每条边的填充都是左闭右开。如题目的3阶矩阵,我们就先填充1,2、再填充3,4、再填充5,6、再填充7,8,最后补充9即可。因为统一了循环条件,每次填充的值的数量是固定的,最后要不要填充一个中间数则由起始的条件决定。这样我们就完成了边界条件的判断。

代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        int psx = 0, psy = 0; // 定义起始位置
        int loop = n / 2; // 循环的圈数
        int mid = n / 2; // 螺旋数组的中间位置
        int count = 1; // 赋值初始化
        int offset = 1; // 数组循环时的边长控制
        int i, j;
        while (loop --)
        {
            i = psx;
            j = psy;
            // 填充外圈
            for (j = psy; j < psy + n - offset; j++)
            {
                res[psx][j] = count++;
            }
            for (i = psx; i < psx + n - offset; i++)
            {
                res[i][j] = count++;
            }
            for (; j > psy; j--)
            {
                res[i][j] = count++;
            }
            for (; i > psx; i--)
            {
                res[i][j] = count++;
            }
            // 下一圈开始时调整起始位置和偏移量
            offset += 2;
            psx++;
            psy++;
        }
        // 如果n为奇数在中间填充最后一个数
        if (n % 2)
        {
            res[mid][mid] = count;
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值