数据结构以学带练day1——时间复杂度、数组、二分查找、快慢指针

683 阅读3分钟

数组基础知识

时间复杂度

算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))T(n )=O(f(n))。它表示随问题规模nn的增大,算法执行时间的增长率和f(n)f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。

算大O方法

image.png

有时候我们去计算时间复杂度的时候发现不是一个简单的O(n)O(n) 或者O(n2)O(n^2), 而是一个复杂的表达式,例如:

O(2n2 + 10n + 1000)O(2n^2 + 10n + 1000)

1.去掉运行时间中的加法常数项 (因为常数项并不会因为n的增大而增加计算机的操作次数)。

O(2n2 + 10n)O(2n^2 + 10n)

2.去掉常数系数

O(n2 + n)O(n^2 + n)

3.只保留保留最高项,去掉数量级小一级的n (因为n2n^2 的数据规模远大于nn),最终简化为:

O(n2)O(n^2)

4.如果这一步理解有困难,那也可以做提取n的操作,变成O(n(n+1))O(n(n+1)) ,省略加法常数项后也就别变成了:

O(n2)O(n^2)

所以最后我们说:这个算法的算法时间复杂度是O(n2)O(n^2)

常见时间复杂度

image.png

数组理论基础

  • 数组是存放在连续内存空间上的相同类型数据的集合。
  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的。
  • 数组的元素是不能删的,只能覆盖。
  • C++中二维数组是连续分布的。
  • Java的二维数组是无序的。

二分查找

image.png

前提条件

  • 有序数组(一般从小到大);
  • 数组中无重复元素。

题目

704.二分查找

image.png

自己看到题目的第一想法

我的想法是使用"指针"指向整个数组的开头,再利用一个count=-1变量记录指针滑动了多少次,指针依次取值然后与target的值进行对比,然后return count的值即为目标值所在的位置。

定义一个size变量读取数组长度,若count的值大于size,则表示数值内没有与target相同的数,则直接输出count初值。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int size = nums.size();
        int count = -1;
        int p = 0;
        for(int i = 0;i < size;i++){
            if(nums[p+i] == target ){
                count = i;
            }
        }
    return count;
    }
};

代码随想录的方法

左闭右闭

image.png

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

左闭右开

image.png

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

27.删除元素

image.png

自己看到题目的第一想法

1、想法为定义一个新的数组,这样直接把留下的数值赋值到新数组里面,再输出。但题目要求不能开辟新数组空间。此路不通。

2、快慢指针的想法:快指针遍历整个数组,做判断,遇到不是要删除的数值,则赋值给慢指针。

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

代码随想录的方法

暴力方法

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;
    }
};

快慢指针

// 时间复杂度:O(n)
// 空间复杂度: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;
    }
};

Python

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 快慢指针
        fast = 0  # 快指针
        slow = 0  # 慢指针
        size = len(nums)
        while fast < size:  # 不加等于是因为,a = size 时,nums[a] 会越界
            # slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow

利用库函数方法

remove函数

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        return remove(nums.begin(), nums.end(), val) - nums.begin();
    }
};

erase函数

  • erase(pos,n); 删除从pos开始的n个字符,比如erase(0,1)就是删除第一个字符
  • erase(position);删除position处的一个字符(position是个string类型的迭代器)
  • erase(first,last);删除从first到last之间的字符(first和last都是迭代器)
class Solution {
public:
    //直接调用库函数,删除所有等于val值的元素
    int removeElement(vector<int>& nums, int val) {
        nums.erase(std::remove(nums.begin(), nums.end(), val), nums.end());
        return nums.size();
    }
};

学以致用

二分查找和快慢指针皆可用于自动驾驶中点云、图像的一些遍历操作,比如删除点云中为0的点。

今日收获,记录一下自己的学习时长

二分查找印象不是很深,之后还得复习 快慢指针终于自己写出来了!

2月1日:10点到12点半,学习两个半小时。