数据结构与算法一【数组】

2022-12-27阳了,2023-01-01重新启动

1.数组的理论基础

数组本身就是一种线性表。
线性表:逻辑结构指的是1相同特征元素的2有序3序列。
线性表:存储结构包括顺序存储和链式存储,其中顺序存储指的就是一维数组,而链式结构就是链表。

数组在内存中的存储特点:
在这里插入图片描述

  1. 数组的下标都是从0开始的;
  2. 数组内存空间的地址是连续的;

2.二分查找

查找算法

首先,查找是数据结构与算法中的一大部分内容,在做这个题之前我们先简要把一些查找的方法总结一下;
对于一个散列表(哈希表),我们通过构建Hash函数后对元素进行查找的时间复杂度就是O(1)。具体内容可以参考数据结构与算法三【哈希表】

顺序查找

对于顺序表(顺序存储和链式存储),如果是顺序查找那么对于长度为n的顺序表,查找成功的平均时间复杂度就是 A S L 1 = 1 n × n ( n + 1 ) 2 ASL_1=\frac{1}{n} \times\frac{n(n+1)}{2} ASL1=n1×2n(n+1),其时间复杂度为O(n)。
对于查找失败的平均查找长度 A S L 2 = 1 n × n × n ASL_2=\frac{1}{n} \times n\times n ASL2=n1×n×n

折半查找(二分查找)

对于折半查找(二分查找)要求线性表本身有序,且题目一般要求无重复,因为重复的话返回的下标不唯一。
其平均查找长度为$ lg2(n+1)上取整-1$。时间复杂度是O(log_2(n))

编程实现

https://leetcode.cn/problems/binary-search/
其核心要点在于清晰的条件判定区间(这一不变量),在二分查找中要保持不变量。就是在while寻找每一次边界的时候都要根据区间的定义来操作,这就是循环不变量规则。
我们定义判定区间是一个[左,右]的闭区间,这样每次折半的时候,while判定条件中用<=或者>=,==号有意义,
分支判定,如果if(num[middle]>target) right就要赋值为num[middle-1],
在这里插入图片描述

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)/2;
            if (nums[middle]>target){
    
    
                right = middle-1;
            }else if(nums[middle]<target){
    
    
                left = middle+1;
            }else{
    
    
                return middle;
            }
        }
        return -1;
    }
};

在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则!!

3删除元素

https://leetcode.cn/problems/remove-element/submissions/
暴力双循环思路:
时间复杂度是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; j<size-1;j++){
    
    
                    nums[j]=nums[j+1];
                }
                i--;
                size--;
            }
        }
        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;//slowindex==新数组长度
    }
};

4排序

最直观的想法,计算出平方后进行排序;或者边计算边进行插入排序。
二者本质上没有区别,先计算出来后,也需要逐个进行排序。

这里我们直接调用快速排序的包。

class Solution {
    
    
public:
    vector<int> sortedSquares(vector<int>& nums) {
    
    
        for (int i=0;i<nums.size();i++){
    
    
            nums[i]*=nums[i];
        }
        sort(nums.begin(),nums.end());
        return nums;
    }

};

巧法:由于平方后的最大值一定在左右两侧,因此可以通过双指针法,设置左右两个指针,然后开辟一个和输入数组大小相等的数组,通过左右指针循环找最大值逆序填充到新开辟的数组里即可。

4.1各类排序算法分类总结

其中,在这个题目中,我们使用了C++ STL中的快速排序sort()函数,那还有哪些排序算法,不同的排序算法的时间复杂度是多少呢?
排序算分类总结:

插入类:

直折希、

交换类:

冒快、

选择类:

简堆、

归并和基数排序:

基归(基数排序和归并排序)

4.2排序算法时间复杂度分析

时间复杂度

基数排序比较特殊,平均时间复杂度为O(d(n+rd)) 空间负载度O(rd)

  1. 平均时间复杂度
    口诀:快些以 n l o g 2 n nlog_{2}{n} nlog2n的速度归堆
    (快速排序、希尔排序、归并排序、堆排序)
    其他算法的平均时间复杂度都是O(n^2)

  2. 最坏情况下的时间复杂度
    快速排序在有序的时候要变慢O(n^2),其他算法最坏情况都和平均时间复杂度相同)

  3. 最好情况下的时间复杂度
    直接插入冒泡冒泡,有序时要变快O(n)

空间复杂度:

快归基本要求高
快速排序O(log2n); 归并排序O(n)、基数排序O(rd)

排序算法C++代码实现

插入类

直接插入排序

假设第一个元素已经有序,从第二个元素开始往有序序列中(从有序序列中,从后往前,依次比较插入)

class Solution {
    
    
public:
    vector<int> sortedSquares(vector<int>& nums) {
    
    
        for (int i=0;i<nums.size();i++){
    
    
            nums[i]*=nums[i];
        }
        // sort(nums.begin(),nums.end());
        int temp,k;
        for (int j=1;j<nums.size();j++){
    
    
            temp = nums[j];
            k=j;
            while(k>=1&&temp<nums[k-1]){
    
    
                nums[k]=nums[k-1];
                k--;
            }
            nums[k]=temp;
        }
        return nums;
    }
};

数组操作-滑动窗口

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

其本质上是双指针法的一种,由于这种解法更像是一个窗口的移动,因此叫做滑动窗口更为合适。
在本题中,实现滑动窗口主要确定如下三点:

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

循环不变量原则-螺旋矩阵

猜你喜欢

转载自blog.csdn.net/adreammaker/article/details/128435412