算法训练Day1

2023年3月1日

今天的任务有三个

一、数组理论基础 https://programmercarl.com/%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html

i.数组的声明

Type_name Variable[ArraySize]; //[]内的值表示元素的个数,Type_name表示类型名字

int array[10];//定义了一个有10个元素的int型数组

ii. 数组的下标

数组的下标是从0开始的,Array[0]表示第一个元素,Array[1]表示第二个元素,以此类推。

int array[5]={1,2,3,4,5};
cout<<"第一个元素为:"<<array[0]<<"第二个元素为:"<<array[1]<<endl;

iii. 数组的初始化

当提供的值少于数组的元素数目时,编译器会将其余的值设置为0.

当数组初始化时,[]为空,C++会根据你写的内容自动计算元素个数

iv. C++11数组初始化方法

首先,数组初始化时,可以省略等号(=)

double arrays[4]{1.0,2.25,3.6,2.456}

其次,可以不在大括号内包含任何东西,这将把所有元素都设置为0

unsigned int count[10]=[]
float balance[5]{}

最后,列表初始化禁止缩窄转换

char test[]{'h',123,'\0'}//错误的!

v. C++STL中的Vector

首先介绍一下C++STL,容器是STL的基础,提供了存放数据元素的一系列模板,按照实现方式及特性定义了多种容器,不同的容器有着不同的底层数据结构与特点,适用于不同的应用。

本节介绍的是vector向量容器。

vector向量容器以类模板的形式提供的动态数组,与数组类似,vector的元素也存储在连续的空间中,支持使用下标快速访问元素。与数组不同的是,vector容器的大小可以动态改变,相关存储空间的申请与分配由容器自动完成。

首先是容器的构造:

#include<iostream>
#include<string>
#include<vector>
class TestClass{};
int main(){
    string str[]={"wwc","zhl","zza"};
    vector<int> v1;//空vector,元素类型为int
    vector<int> v2(10);//包含10个int元素的vector
    vector<float> v3(10,0)//10个float元素,初始值为0
    vector<std::string> v4(str,str+2);//string初始化
    vector<std::string>::iterator sIt=v4.begin();//设置迭代器sIt,一开始指向第一个元素
    while(sIt != v4.end()) std::cout<<*sIt++<<"";
    std::cout<<std::end;//清空缓冲区
    vector<std::string> v5(v4);//vector对象直接赋值
    for(int i=0;i<v5.size();i++) std::cout<<v5[i]<<"";
    std::cout<<std::endl;
    vector<TestClass> myC;
    vector<TestClass*> pmyC;
    std::cout<<"TestClass Success!"<<std::endl;
}

其次是vector容器的size、capacity、resize和max_size:

vector v;
v.capacity();//capacity表示预分配的存储空间总数
v.size();//表示容器中目前元素个数
v.max_size();//最大大小
v.resize();//改变容器大小

再者是vector的元素访问:

vector重载了operator[]运算符,使其可以像数组一样通过下标访问,起始值也是0

成员函数:

front():返回容器中第一个元素

last():返回容器中最后一个元素

at(size_type n):支持随机访问

同时支持迭代器

at和[]的区别在于,at会对下标值是否越界进行检查,若越界,则抛出out of range错误,而operator[]不会检查

然后就是重头戏,在vector中插入与删除元素

成员函数:

push_back():在尾部插入元素

pop_back():在尾部删除元素

insert():在向量内部插入

erase():删除元素

其中这个insert()用于在指定位置插入新元素,在插入元素后,vector的迭代器会失效,因为vector的存储地址发生了变化(重新申请了一个空间,类似于数组)

(1)insert(iterator position,const value_type& val);iterator position表示插入位置,const value_type& val表示待插入元素的引用,例如:

vector<int> v;
v.insert(v.begin(),8);//在v开始的地方插入8

(2)insert(iterator position,size_type n,const value_type& val):填充n个值相同的元素,其中size_type n表示元素个数

(3)利用迭代器插入指定范围内的元素值:

template<class T>

void insert(iterator positon, T first,T last);

其中T first表示待插入元素的起始位置,T last表示待插入元素的结束位置之后,即存储范围实际是[first,last)。

关于erase

(1)删除指定位置的元素:iterator erase(iterator position)

(2) 删除[first,last)范围内的所有元素:iterator erase(iterator first, iterator last)

insert()向vector中特定位置插入元素将会影响容器的size和capacity,而erase仅会改变容器的size,capacity不会发生改变

最后是vector元素的赋值与交换

成员函数:

assign():用于向向量中分配新的元素以替换现有的元素,同时可修改vector的大小

(1)通过迭代器指明填充范围

template<class T>

void assign(T first,T last)

(2)填充n个特定值

void assign(size_type n,const value_type& val);

swap()用于互换两个类型相同但大小可以不同vector容器对象,其实现方法是直接互换两个vector的引用而非互相拷贝赋值,因此,在两个vector互换后,原有的迭代器依然有效,两个vector中的元素并没有发生任何赋值与交换,调用方法为v1.swap(v2),调用swap()之后,v1与v2的引用地址进行了交换,但容器内的元素仍然维持不变。

vi. C++STL中的array

哥们太累了打字,放个链接自己看把>,<

【STL】C++ STL之array详解_c++ stl array_行码棋的博客-CSDN博客

二、二分查找 https://leetcode.cn/problems/binary-search/

大二的时候学过数据结构,当时只知道思想,很少上手打过代码,这也是我想参加算法训练的原因。

经过简单的学习以后,关于整型相加溢出的问题可以看:int类型用c语言判断相加溢出_如何判断有符号整数加法溢出?_小子骚骚的博客-CSDN博客,需要计组的一点基础

二分查找的前提是有序数组,这点很重要!

二分查找的思想在算法里面就是分治思想,将大问题转化为小问题进行求解,最后得到f(1)

二分查找的关键点就是循环不变量,即区间,左闭右闭的判断和左闭右开的区间的判断是不一样的,在整个的循环过程中要严格按照循环不变量进行判断,判断left和right到底能不能相等,这样才能将问题考虑得更加全面和更加深入。

源码奉上(C++):

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size();
        while(left < right){//左闭右开
            int mid = left + ((right - left) >> 1);
            if(target < nums[mid] ){
                right=mid;
            }
            else if(target > nums[mid]){
                left = mid+1;
            }
            else{
                return mid;
            }
        }
        return -1;
    }
};

三、移除元素 https://leetcode.cn/problems/remove-element/

这道题我一开始想的是,直接erase不就行了吗,源代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        vector<int>::iterator it = nums.begin();
        while(it != nums.end()){
            if(*it == val){
                nums.erase(it);
                it = nums.begin();
                continue;
            }
            it++;
        }
        return nums.size();
    }
};

erase本身能自动删除该位置的元素并将后面的元素进行移动,同时改变size的值,充分利用vector容器的成员函数,不过这种方法时间较长,内存占用较大,注意,erase并不会新创建一个空间进行赋值,而是直接将后面的数字自动往前挪,验证代码如下:

#include<vector>
#include<iostream>
int main(){
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    std::cout << &v[3]<<std::endl;
    v.erase(v.end()-1);
    std::cout << &v[3]<<std::endl;
    system("pause");
    return 0;
}

可以看到,两次的运行结果是一样的。

下面来分享文章给的三种方法:

  1. 暴力解法

这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

第一个for用来查找元素相等时候的位置,第二个for用来将后面的元素进行前移,比较好理解,不多做赘述,源代码如下:

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

    }
};
  1. 快慢指针

这个方法的本质也是覆盖思想,只不过用的方法不一样。当两个指针都从左边开始,到右边结束的过程中,遇到要删除的元素的时候,一个指针原地不动,另一个往后移动,这种情况下,当下一次循环的时候,将往后移动的指针(快指针)对应的值赋值给原地不动的那个指针(慢指针)所指向的值,给人一种“赋值”操作的感觉,这种情况下是时间复杂度是O(n),比暴力解法更快,源代码如下:

// 时间复杂度: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;
    }
};
  1. 双指针

这种方法是很多数组算法题经常用到的,两个指针一前一后,往中间靠拢,左边找跟要删除的值相同的值,右边的值找跟要删除的元素的值不同的值,然后将后面指针所指的数赋值给前面指针所指的数,形成“删除”的效果,最后左边指针所指的位置就是要返回的数组长度(就是把跟val值相同的元素都放到后面,把不同的值都放在前面,最后leftIndex一定指向了最终数组末尾的下一个元素),源代码如下:

/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.size() - 1;
        while (leftIndex <= rightIndex) {
            // 找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        return leftIndex;   // leftIndex一定指向了最终数组末尾的下一个元素
    }
};

今天的分享到此结束了,希望能坚持到底吧,坚持不到那也没办法喽,明天见!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值