c++刷题基础


一、运算符tips

  1. c++中&&的优先级高于||
  2. c++中+的优先级高于三元表达式,a+b==c?1:0:此写法错误

二、lambda表达式

1.基本概念

  1. [捕获列表](参数列表)mutable(可选) 异常属性 -> 返回类型 { // 函数体 }
  2. auto 函数名=[捕获列表](参数列表)mutable(可选) 异常属性 -> 返回类型 { // 函数体 }
    写法2调用:函数名(注入的实参列表)
    常用格式:[捕获列表](参数列表) -> 返回类型 { // 函数体 }
    关于捕获列表:捕获调用者上下文环境的需要访问的变量,可按值捕获或按引用捕获,其规则如下:
    []:什么也不捕获
    [=]:捕获所有一切变量,都按值捕获
    [&]:捕获所有一切变量,都按引用捕获
    [=, &a]:捕获所有一切变量,除了a按引用捕获,其余都按值捕获
    [&, =a]:捕获所有一切变量,除了a按值捕获,其余都按引用捕获
    [a]:只按值捕获a
    [&a]:只按引用捕获a
    [a, &b]:按值捕获a,按引用捕获b

例子:内部排序,捕获列表为空,所以直接内部排序

sort(a.begin(),a.end(),[](vector<int>&v1,vector<int>&v2)->bool{
    if(v1[0]==v2[0]) return v1[1]<v2[1];
    return v1[0]<v2[0];
});//二维数组排序,先按第一个维度排序,再按第二个维度排序,v1,v2的比较实际上是a内数据的比较

2.值捕获vs引用捕获

拷贝捕获变量的值,在匿名函数被创建时就已经捕获了

#include <iostream>
using namespace std;
 
void learn_lambda_func_1() {
	int value_1 = 1;
	auto copy_value_1 = [value_1] {
		return value_1;
	};
	value_1 = 100;
	auto stored_value_1 = copy_value_1();
	// 这时, stored_value_1 == 1, 而 value_1 == 100.
	// 因为 copy_value_1 在创建时就保存了一份 value_1 的拷贝
	cout << "value_1 = " << value_1 << endl;
	cout << "stored_value_1 = " << stored_value_1 << endl;
}
 
int main()
{
	learn_lambda_func_1();
	return 0;
}
// 输出:
// value_1 = 100
// stored_value_1 = 1

类似于引用,即使捕获的值发生的变化在匿名函数创建后,匿名函数内的引用值也会发生变化

void learn_lambda_func_2() {
	int value_2 = 1;
	auto copy_value_2 = [&value_2] {
		return value_2;
	};
	value_2 = 100;
	auto stored_value_2 = copy_value_2();
	// 这时, stored_value_2 == 100, value_1 == 100.
	// 因为 copy_value_2 保存的是引用
	cout << "value_2 = " << value_2 << endl;
	cout << "stored_value_2 = " << stored_value_2 << endl;
}

三、内嵌函数function

目的:语法糖,内嵌子函数,子函数仅需读取而不做操作的参数可以不在子函数中定义,直接默认可以读取
function<void(TreeNode*)> DFS = [&] (TreeNode root*) {};

function<int(int,int)> DFS = [&] (int u, int p) {};

1.内嵌function写法

代码如下(示例):

class Solution {
public:
    int maxOperations(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> memo(n, vector<int>(n));
        for (auto& row : memo) {
                ranges::fill(row, -1); // -1 表示没有计算过
            }
        //代码具体内容不重要,看一个格式
        // 注意不能写成auto dfs=...,
        // dfs函数需要在其定义中引用自身来进行递归调用。如果你试图直接用auto关键字来进行声明,
        // 那么在Lambda表达式开始的时候,这个dfs还未定义完成,是不能引用它自己的。
            function<int(int, int,int)> dfs = [&](int i, int j,int target) -> int {
                if (i >= j) return 0;
                int& res = memo[i][j]; // 注意这里是引用
                if (res != -1) return res; // 之前计算过
                res = 0;
                if (nums[i] + nums[i + 1] == target) res = max(res, dfs(i + 2, j,target) + 1);
                if (nums[j - 1] + nums[j] == target) res = max(res, dfs(i, j - 2,target) + 1);
                if (nums[i] + nums[j] == target) res = max(res, dfs(i + 1, j - 1,target) + 1);
                return res;
            };

        int res1 = dfs(2, n - 1, nums[0] + nums[1]); // 删除前两个数
        int res2 = dfs(0, n - 3, nums[n - 2] + nums[n - 1]); // 删除后两个数
        int res3 = dfs(1, n - 2, nums[0] + nums[n - 1]); // 删除第一个和最后一个数
        return max({res1, res2, res3}) + 1; // 加上第一次操作
    }
};

2.闭包写法,仅展示闭包,实际可以不这么写

题外话:在此处理解一下闭包,读取其他函数的局部变量,不用像函数内调用外部函数一样显式的传递值

代码如下(示例):

class Solution {
public:
    int maxOperations(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> memo(n, vector<int>(n));
        // 在这里,helper是一个Lambda函数,
        // 它定义了另一个Lambda函数dfs作为其内部函数(在一些语言里,可能会被称为"闭包")。
        // 这种结构使得dfs函数可以访问和修改helper函数定义的变量。
        // 特别的,在这段代码中,dfs函数需要访问和修改memo数组
        // 这个数组是在helper函数中定义的,因此必须把dfs函数定义在helper函数内部,这样才能访问到memo。
        auto helper = [&](int i, int j, int target) -> int {
            for (auto& row : memo) {
                ranges::fill(row, -1); // -1 表示没有计算过
            }
            function<int(int, int)> dfs = [&](int i, int j) -> int {
                if (i >= j) return 0;
                int& res = memo[i][j]; // 注意这里是引用
                if (memo[i][j] != -1) return memo[i][j]; // 之前计算过
                res = 0;
                if (nums[i] + nums[i + 1] == target) res = max(res, dfs(i + 2, j) + 1);
                if (nums[j - 1] + nums[j] == target) res = max(res, dfs(i, j - 2) + 1);
                if (nums[i] + nums[j] == target) res = max(res, dfs(i + 1, j - 1) + 1);
                return res;
            };
            return dfs(i, j);
        };
        int res1 = helper(2, n - 1, nums[0] + nums[1]); // 删除前两个数
        int res2 = helper(0, n - 3, nums[n - 2] + nums[n - 1]); // 删除后两个数
        int res3 = helper(1, n - 2, nums[0] + nums[n - 1]); // 删除第一个和最后一个数
        return max({res1, res2, res3}) + 1; // 加上第一次操作
    }
};

3.再列一个函数的写法,该写法明显函数参数变多

代码如下(示例):

class Solution {
public:
    int maxOperations(vector<int>& nums) {
        int n=nums.size();
        vector<vector<int>> dp(n, vector<int>(n, -1)); // 使用动态大小的二维数组
        int ans=0;
        ans=max(ans,1+dfs(dp,nums[0]+nums[1],2,n-1,nums));
        ans=max(ans,1+dfs(dp,nums[0]+nums[n-1],1,n-2,nums));
        ans=max(ans,1+dfs(dp,nums[n-2]+nums[n-1],0,n-3,nums));
        return ans;
    }
    int dfs(vector<vector<int>>& dp,int target,int left,int right,vector<int>& nums){ // 修改函数参数
        if(left>=right) return 0;
        if(dp[left][right] != -1) return dp[left][right];
        int ans=0;
        if(nums[left]+nums[left+1]==target)
        ans=max(ans,1+dfs(dp,target,left+2,right,nums));
        if(nums[left]+nums[right]==target)
        ans=max(ans,1+dfs(dp,target,left+1,right-1,nums));
        if(nums[right]+nums[right-1]==target)
        ans=max(ans,1+dfs(dp,target,left,right-2,nums));
        dp[left][right] = ans; // 修改dp数组的设定
        return ans;
    }
};

四、值引用和引用传递

在C++中以值传递参数时,实际上是创建了实参的一个副本。这就意味着你在每次进行递归调用时都在复制整个数组。这导致了你的时间和空间复杂度大大增加
而引用传递参数,可以实现不复制整个数组,直接引用原数组,很大程度减少空间和时间复杂度
总结!在递归调用,只需要读取的参数最好也是用引用传递

五、make_pair()和pair()

● 作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。
● C++标准程序库中凡是“必须返回两个值”的函数, 也都会利用pair对象。
当数据以make_pair/pair 绑定到了一起则想要访问不是[0][1],而是first和second
定义数据结构时:deque<pair<string,int>> haha;
运行普通函数:haha.push_front(make_pair(“123”,1));

六、数据插入:insert/push/empalce

insert/push是插入一个完全的对象,而emplace是先调用该对象的构造函数生成该对象,再将该对象插入vector中,使用emplace时,该对象类必须有相应的构造函数

struct A{
int a;
float b;
A(int _a,float _b): a{_a},b{_b} {};//使用emplace必须要有构造函数
//没有会产生报错
//no matching function for call to 'main()::A::A(int)'
A(int _a):a{_a},b{0.0} {};
};
A x1(1,1.1);
vector<A> v1;
v1.insert(v1.end(),x1);
v1.emplace(v1.begin(),2,2.2);
v1.emplace_back(3,3.3);
v1.emplace_back(4);
for(int i=0;i<4;i++){
    cout<<v1[i].a<<" "<<v1[i].b<<endl;
}

比较push_back和emplace_back(写法很多给自己统一一下

插入vector数组
a.push_back({1,2,3});
a.emplace_back(vector<int>{1,2,3});
插入pair型数据
a.push/emplace_back(pair(1,1));

七、c++构造类

class LockingTree{
public:
//注意有些题目不需要通过构造去初始化,外部通过调用内部函数去给类传递数据(如下一示例)
    LockingTree(可有外部系数可没有){//构造函数
        this->a=a;
        this->b=vector<int>9=(n,-1);
    }
    bool lock(){//内部函数
        int y=b[i];
        int x=a;
        int a=x;//错误写法
        a=x;//正确写法,int a是内部函数的局部变量,a才是整个构造数据类型的private变量
        ...}
private:
//属性并不一定要写在private里面,可以写在public或者protected里面
//在该类中定义的属性,内部都能直接访问
    int a;
    vector <int> b;//写明属性
//注意!!!数据不能直接在构造函数中初始化,一定要先写明
//以及!!!不能内部函数中再定义属性,而是应该直接使用属性
//以及!!!如果数据有自定义的数据结构,可以在public:或者private:定义数据结构
//eg:
struct Node{
int c,d;
}
    vector<Node> e;
}
    

eg:

class aa{
public:
  aa(){}
  int next(int p){
      ...;
      return xxx;
  }
private:
  stack<int>q;
  stack<int>p;
};
// 调用
aa a=new aa();
aa.next(100);

八、c++构造体

  • 结构体自带初始化
  • 运算符重载(指定运算符进行操作)
struct Node{
int cnt,time;
//自带初始化
Node(int_cnt,int_time):cnt(_cnt),time(_time){}
//运算符重载
bool operator < (const Node&rhs) const{
   return cnt==rhs.cnt?time<rhs.time:cnt<rhs.cnt;
}
}
  • 结构体中带可变长数组
struct Node {
   vector<int> nums;  
   int num;
   Node() = default;
   Node(const Node&) = default;
   Node& operator = (const Node&) = default;
   Node(vector<int> _nums, int _num) : nums(_nums), num(_num) {} // 修改构造函数
};

初始化

创建对象
node cache=node(1,time);
创建指针
node*pnode=new node(1,time);
创建引用
attention:c++中,引用一旦被初始化为某个对象的引用,就无法改变为引用另一个对象
node cache=node(1,time);
node&c=cache;//绑定引用
attention
对象和指针获取值的方式相同,直接用.
引用获取值的方式为->

运算符重载

sets;
s.begin();//s.begin()是以上规则中的min值
因为s为set类型数据集合,set自动进行排序,这个写法相当于重置set的排序规则

unordered_map插入自定义结构体小tips

方法1:调用pair函数

  • make_pair会返回一个pair对象,里面使用了pair的构造函数初始化值。
  • pair内部的两个元素会使用默认构造函数构造,而不是复制构造。
  • insert方法接受pair作为参数,实际上是通过二进制拷贝pair到内部实例,不涉及对象的复制构造。

Node cache=Node(1,time);
unordered_map<int, Node> m;
key_table.insert(make_pair(1,cache));

方法2:实现构造函数和拷贝构造函数

struct Node {
 // ...

 Node() = default;
 Node(const Node&) = default; 
 Node& operator=(const Node&) = default;
};
unordered_map<int, Node> m;
Node cache=Node(1,time)/new Node(1,time);;
m[1]=cache;

九、memset函数

函数原型
void memset(voidstr,int c,size_t n)

  • 解释:复制字符c(一个无符号字符 到参数str所指向的字符串的前n个字符
  • 作用:是在一段内存块中填充某个给定的值,对较大的结构体或数组进行清零操作的较快方法
  • 头文件:c++

memset赋值为按字节赋值,将参数转化为二进制之后按字节填入
eg:memset(a,100,sizeof a)给int类型的数组a赋值,会将100转化为二进制,01100100,因为int有4个字节,则会扩充为01100100 01100100 01100100 01100100 赋值给数组
因此,memset的赋值最好赋值为0/-1
结论:

  1. 数组类型为char型,value可为任意字符值
  2. 非char型,如int型,赋值为-1/0

十、分割字符串

方法1,两层while循环

       int m=s.size();
       int wordStart=0;//单词的起点索引
       int wordEnd=0;//单词的终点索引(边界或指向空格)不包含
       string word;
       vector<string> resul;
       while(wordStart<m){
           while(wordEnd<m&&s[wordEnd]!=' ')wordEnd++;
           word=s.substr(wordStart,wordEnd-wordStart);//截取单词
           resul.push_back(word);
           // 更新单词区间,起点为当前终点的下一个位置;终点初始与起点相同
           wordStart = wordEnd + 1;
           wordEnd = wordStart; 
       }

方法2,匿名函数

       auto split = [](const string& s, char delim) -> vector<string> {
           vector<string> ans;
           string cur;
           for (char ch: s) {
               if (ch == delim) {
                   ans.push_back(move(cur));
                   cur.clear();
               }
               else {
                   cur += ch;
               }
           }
           ans.push_back(move(cur));
           //move() 将对象的状态或者所有权从一个对象转移到另一个对象
           //只是转移,没有内存的搬迁或者内存拷贝,所以可以提高效率,改善性能。
           return ans;
       };
      vector<string> result=split(path,'/');
//将字符串数组path以/分割为多个子字符串

十一、翻转链表

定义三个辅助节点,一个指向当前需要进行指向翻转的节点(cur),一个指向cur的前一个节点(pre),一个指向cur的后一个节点(nextt)

listnode* reverse(listnode*head){
   listnode*pre=nullptr,*cur=head;
   while(cur){
       listnode*nextt=cur->next;
       cur->next=pre;
       pre=cur;
       cur=nextt;
   }
   return pre;//遍历到后面cur指向空节点,pre指向原链表尾节点,即新链表头节点
}

十二、最大最小值

INT_MIN:-2147483648(-2^31)
INT_MAX:2147483647(2^31-1)
LONG_MIN:-2147483648(-2^31)
LONG_MAX:2147483647(2^31-1)
LLONG_MIN:-9223372036854775808(-2^63)
LLONG_MAX:9223372036854775807(2^63-1)

十三、仿函数/函数对象 function objects

定义:仿函数是一个类或者结构体,重载了函数调用运算符operator(),使得对象可以像函数一样被调用

// 可以加模板,加模板后它可以比较int类型,也可以比较double类型
class Greater
{
public:
   bool operator()(const int& x, const int& y)
   {
   	return x > y;
   }
};

int main()
{
   Greater com;//com是一个对象
   //情况1它可以像函数一样去使用
   cout << com(10, 100) << endl; // false->0
   vector<int> v = { 1,9,3,6,7,5,8,2 };
   //默认情况下是升序
   sort(v.begin(), v.end(), com);//降序
   //情况2也可以传匿名对象
   //注意传的对象是形参,()内是形参
   priority_queue<int, vector<int>, Greater> pq;
   //<>内是模板参数,直接取类名
   return 0;
}

总结

写leetcode题目碰到的需要了解的c++基本知识,一直更新

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值