数据结构学习 数位dp

关键词:数位dp 记忆化搜索 dfs

数位dp属于比较难的题目,所有数位dp在leetcode都是hard。

因为没有做出jz43.里面用到了数位dp,所以去学习了一下,学习看了这位大神的基础知识。

题目基本上是跟着这位灵大哥的题单做的。

学完数位dp之后,我发现数位dp是一个非常套路化的过程难点是确定dp需要记忆的内容。要结合实际例子来理解这个套路化的过程。

数位dp的套路:

关键思想:

从高到低给每位数填数字

比如:要填四位数,从xxxx开始,填了2xxx,再填23xx。

需要两个循环:

第一个循环(是一个递归,调用dfs函数,每次pose+1):遍历每一位数字,给每一位填数字。(第一位2xxx,第二位21xx,这样的)

第二个循环:遍历每一位数字的可选项,每一种可选项都填一遍。(要填第一位,可以填1,2,3,4,5...)

islimit:

可选项的确定依靠这个bool,表示这一位的数字是否收到上限的限制。如果这一位数没受到限制,我们就可以填0-9(十进制)或者0-1(二进制)。如果受到限制,那么就只能填这一位数的上限。

如果确定这一位数是否收到限制:如果它的前一位不受限制,那么这一位一定不受限制。如果它的前一位受到限制,那么得看我们前一位填的是否等于前一位数的上限,如果等于,那么这一位依然受到限制。

比如:我们只能从2345-0000这一段填数字,我们从高到低填数字。(初始化默认islimit==true,因为前几位填的数字都是000,和上限一样。)

假如目前状态是xxxx,第一位受到限制,只能填2-0。

        1、填了2,2xxx,那么第二位继续受到限制只能填3-0,我们不可能填24xx 25xx 26xx这些了,大了。

        2、填了1,1xxx,那么第二维就自由了,它填0-9都可以,不论是19xx还是10xx都不会超过2345.

数位dp的函数参数:

数位dp里的dfs的函数一般需要一下这些参数,不一定是所有参数都被需要:

题目一:面试题 17.06. 2出现的次数 

思路:

逐位填写数字,每一位枚举要填入的数字。对于本题来说,由于前导零对答案无影响,isNum可以省略。

dp状态:

dp[pose][count]:构造到从左往右第pose位,已经出现了 count个2,在这种情况下,继续构造最终会得到的2的个数。初始化为-1。

中止条件:

if(pose<0) return count;//中止条件

说明这一条已经填完了,返回的是这一路的数字二的个数,比如1222,返回3;1221,返回2

调取记忆:

if(!islimit&&dp[pose][count]>=0) return dp[pose][count];

//注意只有在islimit==0而且之前有记载过的时候才可以调取。

求上限:

int up=islimit?dig[pose]:9;

求上限,即求可选值。如果有上限,那么取上限值。

开始填数,temp计录每一位可选值的结果:

        int temp=0;//用来计数,记录在这个状态下,继续填数,一共可以得到的2的个数
        for(int i=0;i<=up;++i)
        {
            temp=temp+fun(pose-1,islimit&&i==dig[pose],count+(i==2),dp);
        }

i<=up//这一位填的数不能超过up

pose-1//填下一位数

islimit&&i==dig[pose]//是否有限制的确定

count+(i==2)//如果这一位填的数为2,计数+1

画了个示意图,方便理解temp记的是啥,返回的又是啥。


 

记忆化:

注意前提是没有限制。

        if(!islimit)//记忆化
            dp[pose][count]=temp;

复杂度计算:

时间复杂度O(log^2 n)

时间复杂度 = 状态个数 * 单个状态的转移次数,状态个数就是 dp 数组的长度,即 O(log^2 n) ,而单个状态的转移次数 = O(10) = O(1),所以时间复杂度为 O(log^2 n)

空间复杂度O(log^2 n) 

代码:

class Solution {
public:
    int fun(int pose,bool islimit,int count,vector<vector<int>>& dp)
    {
        if(pose<0) return count;//中止条件
        if(!islimit&&dp[pose][count]>=0) return dp[pose][count];//记忆化
        int up=islimit?dig[pose]:9;//求上限
        int temp=0;//用来计数
        for(int i=0;i<=up;++i)
        {
            temp=temp+fun(pose-1,islimit&&i==dig[pose],count+(i==2),dp);
        }
        if(!islimit)//记忆化
            dp[pose][count]=temp;
        return temp;
    }
    int numberOf2sInRange(int n) {
        while(n)//把数分成一位一位
        {
            dig.push_back(n%10);
            n=n/10;
        }
        vector<vector<int>> dp(dig.size(),vector<int>(dig.size()+1,-1));//dp状态,初始化为-1
        return fun(dig.size()-1,true,0,dp);//dfs
    }
    vector<int> dig;
};

 题目二:不含连续1的非负整数

这道题我是看了答案才会的。

思路:

逐位填写数字,每一位枚举要填入的数字。对于本题来说,由于前导零对答案无影响,isNum可以省略。

 dp状态:

std::vector<vector<int>> dp(dig+1,vector<int>(2,-1));

dp[pose][pre]:第pose-1位为pre时,构造从左往右第pose位及其之后数位的合法方案数

中止条件:

if(pose<0) return 1;//中止条件

说明这一条已经填完了,返回的是1,表示这一路的已经被统计了,比如10101(二进制),返回1;10100(二进制),又返回1

 调取记忆:

if (!islimit && dp[pose][pre] >= 0) return dp[pose][pre];//之前已经记录了

注意这里要!islimit

求上限:

int up = islimit ? (n >> pose)&1 : 1;

开始填数,temp计录每一位可选值(合法组合数)的结果:

这里只能填0或者0和1。要看上限是0还是1。

        //temp 统计合法的组合数
        int temp = fun(pose - 1, 0, islimit && up==0, n, dp);//如果下一个数字填0
        if(up==1&&pre==0) temp+=fun(pose-1,1,islimit&&up==1,n,dp);//如果下一个数字填1:前提是前一个数字不能为1而且可以填1

记忆化:

if (!islimit) dp[pose][pre] = temp;//记忆化

复杂度计算:

时间复杂度O(logn) //2*logn

空间复杂度O(logn) //存dp

代码:

class Solution {
public:
    int findIntegers(int n) {
        int dig = 0;
        int temp = n;
        while (temp)
        {
            dig++;
            temp = temp >> 1;
        }
        std::vector<vector<int>> dp(dig+1,vector<int>(2,-1));
        return fun(dig - 1, 0, true, n, dp);
    }
    int fun(int pose, int pre, bool islimit, int n, std::vector<std::vector<int>>& dp)
    {
        //dp状态:第pose-1位为pre时,构造从左往右第pose位及其之后数位的合法方案数
        if (pose < 0) return 1;//中止条件
        if (!islimit && dp[pose][pre] >= 0) return dp[pose][pre];//之前已经记录了
        int up = islimit ? (n >> pose)&1 : 1;
        //temp 统计合法的组合数
        int temp = fun(pose - 1, 0, islimit && up==0, n, dp);//如果下一个数字填0
        if(up==1&&pre==0) temp+=fun(pose-1,1,islimit&&up==1,n,dp);//如果下一个数字填1:前提是前一个数字不能为1而且可以填1
        if (!islimit) dp[pose][pre] = temp;//记忆化
        return temp;
    }
};

 题目三:这位灵大哥的题单

 [ 用时: 23 m 30 s ] 自己写出来了

思路:

逐位填数字,可以填digits的数字,也可以填0(前导零),为了区分前导零和其他数字,所以需要hasnum(isnum)来控制前导零。

dp状态:

题解是没有记录hasnum状态的,我记录了。

dp[pose][hasnum]:在hasnum的状态下,构造从左往右第pose位及其之后数位的合法方案数

vector<vector<int>> dp(dig.size(),vector<int>(2,-1));

中止条件:

if(pose<0) return 1;

hasnum状态:

        //hasnum==1:前面有非零的数字

        //hasnum==0:前面全是零(前导零) 

 比如n=9999,那么就是有四个空可以填数字。我们可以只填一位:1,也可以填两位:13,也可以填三位:135,还可以填四位:1351。

如果前面不填,比如只填了两位:0013,那么前面的0就是前导零。

注意:hasnum的状态会影响到dp,所以我们才要把dp状态里面加入hasnum状态。

        //比如:n=1000,当pose=1,也就是我们第一个数已经填了

        //第一个数填了digits里的数(比如7),即hasnum==1时,现在的填数状态:7???,那么第二位就不能填0了

        //第一个数填了0(前导零),即hasnum==0时,现在的填数状态:0???,那么第二位既可以填digits也可以填0

调取记忆:

if(!islimit&&dp[pose][hasnum]>=0) return dp[pose][hasnum];//之前记录了

求上限:

int up=islimit?dig[pose]:9;//上限

开始填数,temp计录每一位可选值(合法组合数)的结果:

可以填两种数,一种是digits里的数,一种是前导零。

        for(int i=0;i<=up;++i)
        {
            if(digits_hash.find(i)!=digits_hash.end())//填digits里面的数
            {
                temp+=fun(pose-1,true,islimit&&i==up,digits_hash,dig,dp);//填了所以hasnum==true
            }
            if(i==0&&hasnum==false&&pose!=0)//如果&hasnum==false,就可以继续填0
            {//pose!=0是为了防止00000全零也被算进去的情况:如果到了pose==0的时候,前面还是全零,最后一个数就不能继续填0了
                temp+=fun(pose-1,false,islimit&&i==up,digits_hash,dig,dp);//继续填0所以hasnum==false
            }
        }

记忆化:

if(!islimit) dp[pose][hasnum]=temp;//记忆化

复杂度计算:

时间复杂度O(logn)

空间复杂度O(logn)

代码:

class Solution {
    //填数字,可以填digits的数字,也可以填0(前导零)
public:
    int atMostNGivenDigitSet(vector<string>& digits, int n) {
        vector<int> dig;//把n逐位拆分
        while(n)
        {
            dig.push_back(n%10);
            n=n/10;
        }
        unordered_map<int,int> digits_hash;//把digits转为字典
        for(const string&s:digits)
        {
            digits_hash[s[0]-'0']=1;
        }
        //dp状态:在hasnum的状态下,构造从左往右第pose位及其之后数位的合法方案数
        //hasnum==1:前面有非零的数字
        //hasnum==0:前面全是零(前导零)
        //hasnum的状态会影响到dp。
        //比如:n=1000,当pose=1,也就是我们第一个数已经填了
        //第一个数填了digits里的数(比如7),即hasnum==1时,现在的填数状态:7???,那么第二位就不能填0了
        //第一个数填了0(前导零),即hasnum==0时,现在的填数状态:0???,那么第二位既可以填digits也可以填0
        vector<vector<int>> dp(dig.size(),vector<int>(2,-1));
        return fun(dig.size()-1,false,true,digits_hash,dig,dp);
    }
    int fun(int pose,bool hasnum,bool islimit,const unordered_map<int,int>& digits_hash,const vector<int>& dig,vector<vector<int>>& dp)
    {
        if(pose<0) return 1;
        if(!islimit&&dp[pose][hasnum]>=0) return dp[pose][hasnum];//之前记录了
        int up=islimit?dig[pose]:9;//上限
        int temp=0;//记录该阶段(该pose)的合法数
        for(int i=0;i<=up;++i)
        {
            if(digits_hash.find(i)!=digits_hash.end())//填digits里面的数
            {
                temp+=fun(pose-1,true,islimit&&i==up,digits_hash,dig,dp);//填了所以hasnum==true
            }
            if(i==0&&hasnum==false&&pose!=0)//如果&hasnum==false,就可以继续填0
            {//pose!=0是为了防止00000全零也被算进去的情况:如果到了pose==0的时候,前面还是全零,最后一个数就不能继续填0了
                temp+=fun(pose-1,false,islimit&&i==up,digits_hash,dig,dp);//继续填0所以hasnum==false
            }
        }
        if(!islimit) dp[pose][hasnum]=temp;//记忆化
        return temp;
    }
};

题目四:至少有 1 位重复的数字

看了答案才会的 主要是把求重复变成求非重复这里没想到,而且vis用int来表示我没想到。

思路:

    //关键思想:求有重复了太困难了,求不重复的比较简单

    //先求不重复的,然后总数-不重复的=重复的

    //所以:逐位填入不重复的数字

    //注意这题需要isnum来控制前导零的

dp状态:

vector<vector<int>> dp(dig.size(),vector<int>(1<<10,-1));

dp[pose][vis]在前面已经被用了vis的情况下,构造第pose位及其之后数位的合法方案数

为什么第二维是1<<10呢?请看vis状态。

vis状态:

vis记录了前面选过的数字。

注意:这里有一个技巧,这里的vis不是一个长度为10的vector,而是int,节省空间了。

  • 问:那么怎么记录呢?
  • 答:比如vis=(00000000101)(2进制)即(5)(10进制),意味着0和2被用过了
  • 问:为什么dp第二维是1<<10呢?
  • 答:在构造dp的时候,为了装下vis=(0000000000)~(1111111111)(2进制),所以要用(10000000000)(2进制)的大小,所以大小写成1<<10

 isnum状态:

前面是否填了除了前导零以外的数字。这个状态可以记可以不记,上一题我记了,这一题我没记是因为如果记就三维了,太多了。

中止条件:

表示这一分支已经填数字填到底了,完成了1组数字组合,返回1

if(pose<0) return 1;

调取记忆:

if(!islimit&&dp[pose][vis]>=0) return dp[pose][vis];

求上限:

int up=islimit?dig[pose]:9;

开始填数,temp计录每一位可选值(合法组合数)的结果:

这里分成两种情况,前面全是0和前面有非零的数字:

        1、如果前面全为0,即if(!isnum)

                a、那么如果继续填零,isnum的状态不变(注意:pose不能在==0的时候再填0了,不然整个数字全是0)

                b、如果填了别的数,isnum的状态改变

        2、如果前面有非零的数字

                a、 这个数之前没有被用过(vis>>i &1)==0,那么统计

                b、 这个数之前被用过(vis>>i &1)!=0,那么跳过

        for(int i=0;i<=up;++i)
        {//这里分成两种情况,前面全是0和前面有非零的数字
            if(!isnum)//如果前面全是0
            {
                if(i==0&&pose!=0)//填0:前面全是0,那么再加一个0,isnum还是保持不变,相当于啥都没做。pose不能在==0的时候再填0了,不然整个数字全是0
                {//所以vis不记录,这时候vis=(0000000000)
                    temp+=fun(pose-1,vis,islimit&&i==up,isnum,dig,dp);
                }//填除了0其他的
                else if(i!=0)
                {//vis|(1<<i):比如填了个2,那么1<<i:(100)(2进制) 按位或(其实这里用+也没事):将1<<i合并进vis里面
                //isnum要改成true,因为填数了
                    temp+=fun(pose-1,vis|(1<<i),islimit&&i==up,true,dig,dp);
                }  
            }
            else if((vis>>i &1)==0)//如果前面有非零数字,那么只需要判断这个数之前有没有被用过(vis>>i &1)==0
            {//同上
                temp+=fun(pose-1,vis|(1<<i),islimit&&i==up,true,dig,dp);
            }
        }

 记忆化:

注意:要在没有任何限制的情况下(!islimit&&isnum)才记忆化

if(!islimit&&isnum) dp[pose][vis]=temp;//记忆化

复杂度计算:

代码:

class Solution {
    //关键思想:求有重复了太困难了,求不重复的比较简单
    //先求不重复的,然后总数-不重复的=重复的
    //所以:逐位填入不重复的数字
    //注意这题需要isnum来控制前导零的
public:
    int numDupDigitsAtMostN(int n) {
        int m=n;
        vector<int> dig;
        while(n)
        {
            dig.push_back(n%10);
            n=n/10;
        }//把n逐位分成一个一个数字
        //dp状态:在前面已经被用了vis的情况下,构造第pose位及其之后数位的合法方案数
        //vis记录了前面选过的数字
        //注意这里有一个技巧,这里的vis不是一个长度为10的vector,而是int,那么怎么记录呢?
        //比如vis=(00000000101)(2进制)即(5)(10进制),意味着0和2被用过了
        //在构造dp的时候,为了装下vis=(0000000000)~(1111111111)(2进制),所以要用(10000000000)(2进制)的大小,所以大小写成1<<10
        vector<vector<int>> dp(dig.size(),vector<int>(1<<10,-1));
        return m-fun(dig.size()-1,0,true,false,dig,dp);
    }
    int fun(int pose,int vis,bool islimit,bool isnum,const vector<int>& dig,vector<vector<int>>& dp)
    {
        if(pose<0) return 1;//中止条件,表示这一分支已经填数字填到底了,完成了1组数字组合,返回1
        if(!islimit&&dp[pose][vis]>=0) return dp[pose][vis];//之前已经记录过了
        int up=islimit?dig[pose]:9;
        int temp=0;
        for(int i=0;i<=up;++i)
        {//这里分成两种情况,前面全是0和前面有非零的数字
            if(!isnum)//如果前面全是0
            {
                if(i==0&&pose!=0)//填0:前面全是0,那么再加一个0,isnum还是保持不变,相当于啥都没做。pose不能在==0的时候再填0了,不然整个数字全是0
                {//所以vis不记录,这时候vis=(0000000000)
                    temp+=fun(pose-1,vis,islimit&&i==up,isnum,dig,dp);
                }//填除了0其他的
                else if(i!=0)
                {//vis|(1<<i):比如填了个2,那么1<<i:(100)(2进制) 按位或(其实这里用+也没事):将1<<i合并进vis里面
                //isnum要改成true,因为填数了
                    temp+=fun(pose-1,vis|(1<<i),islimit&&i==up,true,dig,dp);
                }  
            }
            else if((vis>>i &1)==0)//如果前面有非零数字,那么只需要判断这个数之前有没有被用过(vis>>i &1)==0
            {//同上
                temp+=fun(pose-1,vis|(1<<i),islimit&&i==up,true,dig,dp);
            }
        }
        if(!islimit&&isnum) dp[pose][vis]=temp;//记忆化
        return temp;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/322306.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hive分区表实战 - 多分区字段

文章目录 一、实战概述二、实战步骤&#xff08;一&#xff09;创建学校数据库&#xff08;二&#xff09;创建省市分区的大学表&#xff08;三&#xff09;在本地创建数据文件1、创建四川成都学校数据文件2、创建四川泸州学校数据文件3、创建江苏南京学校数据文件4、创建江苏苏…

ubuntu打开epub格式的文件

Koodo Reader 是一个开源免费的电子书阅读器&#xff0c;支持多达15种主流电子书格式&#xff0c; 内置笔记、高亮、翻译功能&#xff0c;助力高效书籍阅读和学习。 官网地址 拖拽到此处即可添加图书 支持滚轮和点击翻页 菜单在这里 可以记笔记 查看笔记

大数据开发之Hive(压缩和存储)

第 9 章&#xff1a;压缩和存储 Hive不会强制要求将数据转换成特定的格式才能使用。利用Hadoop的InputFormat API可以从不同数据源读取数据&#xff0c;使用OutputFormat API可以将数据写成不同的格式输出。 对数据进行压缩虽然会增加额外的CPU开销&#xff0c;但是会节约客观…

CTFhub-phpinfo

CTFhub-Web-信息泄露-“phpinfo” 题目信息 解题过程 ctrlF搜索关键字…

【Linux驱动】设备树中指定中断 | 驱动中获得中断 | 按键中断实验

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;在设备树中指定中断&#x1f3c0;代码中获得中断&#x1f3c0;按键中断⚽驱动…

Python教程79:关于海龟画图,Turtle模块需要学习的知识点

1.海龟画图的基本步骤&#xff1a;A-B-C-D-E-F A.导入turtle模块&#xff1a;首先需要导入Python中的turtle模块&#xff0c;该模块提供了海龟绘图所需的基本函数和属性。 import turtle as tB.设置画布大小 使用turtle.screensize()函数。该函数可以设置画布的宽度高度背景…

中小企业如何做好信息化规划?

中小企业需不需要做信息化规划&#xff1f;什么时候做信息化规划比较好&#xff1f; 企业的信息化规划&#xff0c;一定是越早越好&#xff0c;越快越好。 因为信息化是一个过程&#xff0c;不是一个结果&#xff0c;它不是一天完成的事情&#xff0c;而是贯穿着企业经营管理…

FPGA引脚 Bank认知--FPGA 选型的一些常识

关键字 HP I/O Banks, High performance The HP I/O banks are deisgned to meet the performance requirements of high-speed memory and other chip-to-chip interface with voltages up to 1.8V. HR I/O Banks, High Range The HR I/O banks are designed to support a wid…

用python提取PDF中各类文本内容的方法

从PDF文档中提取信息&#xff0c;是很多类似RAG这样的应用第一步要处理的事情&#xff0c;这里需要做好三件事&#xff1a; 提取出来的文本要保持信息完整性&#xff0c;也就是准确性提出的结果需要有附加信息&#xff0c;也就是要保存元数据提取过程要完成自动化&#xff0c;…

FPGA四选一的多路选择器(用三元运算符?:解决)

一. 三元运算符? :用法 ?:符号通常用于条件运算符&#xff0c;表示条件判断。它类似于C语言中的三元运算符&#xff0c;用于根据条件选择不同的操作或值。 例如&#xff0c;在Verilog中&#xff0c;条件运算符?:可以用于if-else语句的简写形式。它的一般语法格式如下&#x…

市场复盘总结 20240113

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 昨日主题投资 连板进级率 100% 二进三&#xff1a; 进级率低 14% 最常用的二种方法&#xff1a; 方法一&a…

IIC学习之SHT30温湿度传感器(基于STM32)

简介 附上SHT30资料和逻辑分析仪源文件&#xff0c;点击下载 关于IIC的介绍网上已经非常详尽&#xff0c;这里只说重点&#xff1a; 双线&#xff08;SDA&#xff0c;SCL&#xff09;&#xff0c;半双工采用主从结构&#xff0c;支持一主多从&#xff0c;通过地址寻址&#…

Hologres + Flink 流式湖仓建设

Hologres Flink 流式湖仓建设 1 Flink Hologres2 实时维表 Lookup 1 Flink Hologres holo在实时数仓领域非常受欢迎&#xff0c;一般搭配flinkhologres来做实时数仓&#xff0c;中间分层用holo&#xff0c;上下游一般依赖于holo的binlog来下发数据 2 实时维表 Lookup Holo…

生活的再思考,写在开篇

近几年的就业行情很特别&#xff0c;特别是对中年人&#xff0c;早先网络上主流的声音是动不动总包百万&#xff0c;手握几个 Offer &#xff0c;纠结应该去哪里。现在的主流声音变成了&#xff0c;被毕业优化掉后几个月都没找到合适的工作&#xff0c;焦虑迷茫无所适从&#x…

药物“出气”可知|ZL-005大小鼠雾化给药仪

雾化吸入给药是一种通过装置使药物进入肺部局部或全身发挥作用的给药方式。相较于口服药剂&#xff0c;雾化吸入给药可避免首过效应和药剂破坏&#xff0c;提高药物生物利用度。 而ZL-005大小鼠雾化给药仪&#xff0c;则是新一代小动物雾化装置的理想选择&#xff0c;可完成动…

你升级GPT-4了吗?,如何申请GPT-4 API?最全攻略

ChatGPT4就是ChatGPTPlus&#xff0c;调用接口就分ChatGPT3.5与ChatGPT4.0&#xff0c;如果你想使用ChatGPT4的接口就先充值ChatGPTPlus&#xff0c;在区ChatGPT绑定API即可调用4.0的API 在国内很多小伙伴还不知道怎么升级ChatGPT-4&#xff0c;因为自己只有国内的卡&#xff…

春晚为什么失去了观众?

​春晚失去了观众&#xff0c;这是近年来不可忽视的现象。春晚作为中国的一项传统文化活动&#xff0c;曾经吸引了亿万观众的眼球&#xff0c;但如今却面临着越来越多的挑战和质疑。那么&#xff0c;春晚为什么失去了观众呢&#xff1f; 首先&#xff0c;随着时代的变迁&#…

【C语言】指针进阶

指针的主题&#xff0c;在初阶的《指针》章节已经接触过了&#xff0c;我们知道了指针的概念&#xff1a; 指针就是个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间。指针的大小是固定的4/8个字节&#xff08;32位平台/64位平台&#xff09;。指针是有类…

如何使用Imagewheel搭建一个简单的的私人图床无公网ip也能访问

文章目录 1.前言2. Imagewheel网站搭建2.1. Imagewheel下载和安装2.2. Imagewheel网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

每日一题——LeetCode1200.最小绝对差

方法一 个人方法 排序一次遍历&#xff1a; 最小差值一定是出现在大小相邻的两个元素之间&#xff0c;所以将数组从小到大排序 循环求两元素之间的差值&#xff0c;先假设当前差值为最小差值&#xff0c;先往res数组里面push数据&#xff0c;当碰到更小差值的时候&#xff0c…