【王道】数据结构与算法线性表(二),Java面试题库

    • 2.2.1、初始化顺序表
  • 2.2、动态分配实现

    • 2.2.1、初始化顺序表
  • 2.3、顺序表的特点

  • 2.4、顺序表的插入

    • 2.4.1、插入操作时间复杂度
  • 2.5、顺序表的删除

    • 2.5.1、删除操作时间复杂度
  • 2.5.2、总结

  • 2.6、顺序表的查找

    • 2.6.1、静态分配按位查找
  • 2.6.2、动态分配按位查找

  • 2.6.3、按位查找时间复杂度

  • 2.6.4、按值查找

  • 2.6.5、按值查找时间复杂度

  • 2.6.6、总结

  • 3、单链表

    • 3.1、单链表的定义
  • 3.2、初始化不带头结点的单链表

  • 3.3、不带头结点的单链表是否为空

  • 3.4、初始化带头结点的单链表

  • 3.5、带头结点的单链表是否为空

  • 3.6、单链表的插入

    • 3.6.1、带头结点按位序插入
  • 3.6.2、不带头结点按位序插入

  • 3.7、指定结点的后插操作

  • 3.8、指定结点的前插操作

  • 3.9、单链表的删除

    • 3.9.1、带头结点按位序删除
  • 3.9.2、指定结点的删除

  • 3.10、单链表的查找

    • 3.10.1、按位查找
  • 3.10.2、按值查找

  • 3.10.3、求单链表长度

  • 3.10.4、总结

  • 3.11、单链表的建立

    • 3.11.1、尾插法建立单链表
  • 3.11.2、头插法

  • 3.11.3、总结

  • 4、双链表

    • 4.1、双链表的定义
  • 4.2、双链表的初始化

  • 4.3、判断双链表是否为空

  • 4.4、双链表的插入

    • 4.4.1、后插操作
  • 4.4.2、按位序插入

  • 4.4.3、前插操作

  • 4.5、双链表的删除

  • 4.6、双链表的销毁

  • 4.7、双链表的遍历

  • 4.8、总结

  • 5、循环链表

    • 5.1、循环单链表
    • 5.1.1、初始化循环单链表
  • 5.1.2、判断循环单链表是否为空

  • 5.1.3、判断循环单链表表尾结点

  • 5.2、循环双链表

    • 5.2.1、初始化循环双链表
  • 5.2.2、判断循环双链表是否为空

  • 5.2.3、判断循环双链表表尾结点

  • 5.3、循环双链表的插入

  • 5.4、循环双链表的删除

  • 5.5、总结

  • 6、静态链表

    • 6.1、代码定义静态链表
  • 7、第一章总结

1、线性表

=======================================================================

定义:线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列,其中n为表长,当n = 0 时线性表是一个空表。若用L命名线性表,则其一般表示为:

L = (a1,a2,…,ai,ai+1,…,an)

线性表的特点

  • 线性表中的元素的个数是有限的

  • 线性表中的元素有先后次序

  • 线性表中的数据类型都相同,这意味着每个元素占有相同大小的存储空间

  • ai 是线性表中的 “第i个” 元素线性表中的位序(位序是从1开始,数组下标从0开始)

  • a1是表头元素,an是表尾元素

  • 除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继

2、顺序表

=======================================================================

定义:把用顺序存储的方式实现的线性表叫做顺序表

顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

顺序表有两种实现方式:静态分配实现和动态分配实现

2.1、静态分配实现


#define MaxSize 10; //定义最大长度

typedef struct{

ElemType data[MaxSize]; //用静态的"数组"存放数据元素

int length; //顺序表的当前长度

}SqList; //顺序表的类型定义(静态分配方式)

2.2.1、初始化顺序表

#include<stdio.h>

#define MaxSize 10; //定义最大长度

typedef struct{

ElemType data[MaxSize]; //用静态的"数组"存放数据元素

int length; //顺序表的当前长度

}SqList; //顺序表的类型定义

// 基本操作-初始化一个顺序表

void InitList(SqList &L){

for(int i=0;i<MaxSize;i++)

{

L.data[i] = 0; //将所有数据元素设置为默认初始值

}

L.length = 0; //顺序表初始长度为0

}

int main(){

SqList L; //声明一个顺序表

InitList(L); //初始化顺序表

return 0;

}

2.2、动态分配实现


#define MaxSize 10; //定义最大长度

typedef struct{

ElemType *data; //指针指向第一个数据元素

int MaxSize; //顺序表的最大容量

int length; //顺序表的当前长度

}SeqList; //顺序表的类型定义(动态分配方式)

2.2.1、初始化顺序表

#define MaxSize 10; //定义最大长度

#include<stdio.h>

#include<stdlib.h>

typedef struct{

ElemType *data; //指针指向第一个数据元素

int MaxSize; //顺序表的最大容量

int length; //顺序表的当前长度

}SeqList; //顺序表的类型定义

// 基本操作-初始化一个顺序表

void InitList(SeqList &L){

//用malloc函数申请一片连续的存储空间

L.data=(ElemType *)malloc(sizeof(ElemType)*InitSize);

L.length = 0; //顺序表初始长度为0

L.MaxSize = InitSize; //顺序表的最大长度

}

// 增加动态数组的长度

void IncreaseSize(SeqList &L,int len){

int *p = L.data; //让指针p指向顺序表中的第一个数据元素

L.data=(ElemType )malloc(sizeof(ElemType)(L.MaxSize+len));

for(int i=0; i<L.length; i++){

L.data[i] = p[i]; //将数据复制到新区域

}

L.MaxSize = L.MaxSize + len; //顺序表最大长度增加 len

free§; //释放原来的内存空间

}

int main(){

SeqList L; //声明一个顺序表

InitList(L);//初始化顺序表

// 往顺序表中随便插入几个元素

IncreaseSize(L,5);

return 0;

}

2.3、顺序表的特点


  1. 随机访问:即可以在O(1)时间内中找到第i个元素

  2. 存储密度高,每个节点只存储数据元素

  3. 扩展容量不方便

  4. 插入、删除操作不方便,需要移动大量元素

2.4、顺序表的插入


ListInsert(&L,i,e) :插入操作,在表L中的第 i 个位置上插入指定元素e

#define MaxSize 10; //定义最大长度

typedef struct{

ElemType data[MaxSize]; //用静态的"数组"存放数据元素

int length; //顺序表的当前长度

}SqList; //顺序表的类型定义(静态分配方式)

// 插入操作:在L的位序 i 处插入元素e

bool ListInsert(SqList &L,int i,int e){

if(i<1 || i>L.length+1) //判断i的范围是否有效

return false;

if(L.length >= MaxSize) //当前存储空间已满,不能插入

return false;

//将第i个元素及之后的元素后移

for(int j=L.length;j>=i;j–)

{

L.data[j] = L.data[j-1];

}

L.data[i-1]=e; //在位置i处放入e,注意位序、数组下标的关系

L.length++; //长度加1

return true;

}

int main(){

SqList L; //声明一个顺序表

InitList(L);//初始化顺序表

//…此处省略一些代码,插入几个元素

ListInsert(L,3,3);

return 0;

}

在这里插入图片描述

2.4.1、插入操作时间复杂度

只需关注最深层循环语句的执行次数与问题规模n的关系

//将第i个元素及之后的元素后移

for(int j=L.length;j>=i;j–)

{

L.data[j] = L.data[j-1];

}

  • 最好情况:新元素插入到表尾,不需要移动元素,时间复杂度O(1)

  • 最坏情况:新元素插入到表头,需要将原有的n个元素全都向后移动,循环n次,时间复杂度O(n)

  • 平均情况:假设新元素插入到任何一个位置的概率相同,时间复杂度O(n)

2.5、顺序表的删除


ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值

bool ListDelete(SqList &L,int i,int &e){

if(i<1 || i>L.length+1) //判断i的范围是否有效

return false;

e = L.data[i-1]; //将被删除的元素赋值给e

//将第i个位置后的元素前移

for(int j=i;j<length;j++)

{

L.data[j-1] = L.data[j];

}

L.length–; //线性表长度减1

return true;

}

int main(){

SqList L; //声明一个顺序表

InitList(L);//初始化顺序表

//…此处省略一些代码,插入几个元素

int e = -1; //用变量e把删除的元素"带回来"

if(ListDelete(L,3,e)){

print(“已删除第3个元素,删除元素值为=%d\n”,e);

}else{

print(“位序i不合法,删除失败\n”);

}

return 0;

}

在这里插入图片描述

  • 这里函数定义中的参数e加了引用符号,目的是使得在此时声明的变量e和main函数中声明的变量e是同一片内存空间

2.5.1、删除操作时间复杂度

只需关注最深层循环语句的执行次数与问题规模n的关系

//将第i个位置后的元素前移

for(int j=i;j<length;j++)

{

L.data[j-1] = L.data[j];

}

  • 最好情况:删除表尾元素,不需要移动其他元素,最好时间复杂度O(1)

  • 最坏情况:删除表头元素,需要将后续的 n-1 个元素全都向前移动。循环n-1 次,最坏时间复杂度O(n)

  • 平均情况:平均时间复杂度O(n)

2.5.2、总结

在这里插入图片描述

2.6、顺序表的查找


2.6.1、静态分配按位查找

GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值

typedef struct{

ElemType data[MaxSize]; //用静态的"数组"存放数据元素

int length; //顺序表的当前长度

}SqList; //顺序表的类型定义(静态分配方式)

ElemType GetElem(SqList L,int i){

return L.data[i-1];

}

2.6.2、动态分配按位查找

#define InitSize 10; //顺序表的初始长度

typedef struct{

ElemType *data; //指针指向第一个数据元素

int MaxSize; //顺序表的最大容量

int length; //顺序表的当前长度

}SeqList; //顺序表的类型定义(动态分配方式)

ElemType GetElem(SqList L,int i){

return L.data[i-1]; //和访问普通数组的方法一样

}

2.6.3、按位查找时间复杂度

由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素➡"随机存取"特性,按位查找的时间复杂度为O(1)

2.6.4、按值查找

LocateElem(L,e):按值查找操作。在表L中查找值为e的元素

#define InitSize 10; //顺序表的初始长度

typedef struct{

ElemType *data; //指针指向第一个数据元素

int MaxSize; //顺序表的最大容量

int length; //顺序表的当前长度

}SeqList; //顺序表的类型定义(动态分配方式)

//在顺序表L中查找第一个元素值等于e的元素,并返回其位序

int LocateElem(SeqList L,ElemType e){

for(int i=0;i<L.length;i++)

{

if(L.data[i]==e)

{

return i+1; //数组小标为i的元素值等于e,返回其位序i+1

}

return 0; //退出循环,说明查找失败

}

}

2.6.5、按值查找时间复杂度

关注最深层循环语句的执行次数与问题规模n的关系,问题规模n=L.length(表长)

int LocateElem(SeqList L,ElemType e){

for(int i=0;i<L.length;i++)

{

if(L.data[i]==e)

{

return i+1; //数组小标为i的元素值等于e,返回其位序i+1

}

return 0; //退出循环,说明查找失败

}

}

  • 最好情况:目标元素在表头,只需要循环1次,最好的时间复杂度为O(1)

  • 最坏情况:目标元素在表尾,需要循环n次,最坏时间复杂度为O(n)

  • 平均情况:O(n)

2.6.6、总结

在这里插入图片描述

3、单链表

=======================================================================

顺序表:用顺序存储结构实现的线性表叫做顺序表

  • 顺序表优点:可随机存取,存储密度高

  • 顺序表缺点:要求大片连续空间,改变容量不方便

链表:用链式存储结构实现的线性表叫做链表,链表分为:

  • 单链表

  • 双链表

  • 循环链表

  • 静态链表

单链表优点:不要求大片连续空间,改变容量方便

单链表缺点:不可随机存取,要耗费一定空间存放指针

3.1、单链表的定义


typedef struct LNode{ //定义单链表结点类型

ElemType data; //定义单链表结点类型(数据域)

struct LNode * next; //每个节点存放一个数据元素(指针域)

}LNode,*LinkList; //LinkList为指向结构体LNode的指针类型

//增加一个新的结点:在内存中申请一个结点所需空间,并用指针p指向这个结点

LNode *p =(LNode *)malloc(sizeof(LNode));

// 上述定义代码等价于

struct LNode{

ElemType data;

struct LNode *next;

};

typedef struct LNode LNode; //struct LNode = LNode

typedef struct LNode * LinkList; //struct LNode *= LinkList

//增加一个新的结点:在内存中申请一个结点所需空间,并用指针p指向这个结点

struct LNode *p =(struct LNode *)malloc(sizeof(struct LNode))

要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

LNode *L; //声明一个指向单链表的第一个结点的指针

LinkList L; //声明一个指向单链表的第一个结点的指针

上述两种声明方式有什么区别呢?

  • LNode * : 强调这是一个结点

  • LinkList:强调这是一个单链表

3.2、初始化不带头结点的单链表


typedef struct LNode{ //定义单链表结点类型

ElemType data; //定义单链表结点类型(数据域)

struct LNode * next; //每个节点存放一个数据元素(指针域)

}LNode,*LinkList; //LinkList为指向结构体LNode的指针类型

// 初始化一个空的单链表

bool InitList(LinkList &L){

L = NULL; //空表,暂时还没有任何结点

return true;

}

void test(){

LinkList L; //声明一个指向单链表的指针

//初始化一个空表

InitList(L);

}

在这里插入图片描述

3.3、不带头结点的单链表是否为空


//判断单链表是否为空

bool Empty(LinkList L){

return (L==NULL);

}

3.4、初始化带头结点的单链表


typedef struct LNode{ //定义单链表结点类型

ElemType data; //定义单链表结点类型(数据域)

struct LNode * next; //每个节点存放一个数据元素(指针域)

}LNode,*LinkList; //LinkList为指向结构体LNode的指针类型

//初始化一个单链表(带头结点)

bool InitList(LinkList &L){

//用malloc申请一片空间存一个结点

//并且把malloc返回的地址赋给头指针L,也就是说头指针L是指向了这个结点

L =(LNode *)malloc(sizeof(LNode));

if(L==NULL) //分配不足,分配失败

{

return false;

}

L->next = NULL; //头节点之后暂时还没有结点

return true;

}

void test(){

LinkList L; //声明一个指向单链表的指针

//初始化一个空表

InitList(L);

}

在这里插入图片描述

3.5、带头结点的单链表是否为空


//判断单链表是否为空(带头结点)

bool Empty(LinkList L){

if(L->next == NULL){

return true;

}else{

return false;

}

}

3.6、单链表的插入


3.6.1、带头结点按位序插入

ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e

(找到第i-1个结点,将新结点插入其后)

在这里插入图片描述

bool ListInsert(LinkList &L,int i,ElemType e){

if(i<1){

return false;

}

LNode *p; //指针p指向当前扫描到的结点

int j=0; //当前p指向的是第几个结点

p = L; //L指向头结点,头结点是第0个结点(不存是数据)

while(P!=NULL && j<i-1){ //循环找到第 i-1 个结点

p=p->next;

j++;

}

if(p==NULL) //i值不合法

{

return false;

}

LNode *s = (LNode *)malloc(sizeof(LNode));

s->data=e;

//将s指向结点的next指针指向p指向结点的next指针

s->next = p->next;

p->next = s; //将p指向结点的next指针指向s

return true; //插入成功

}

分析:

  1. 如果 i=1(也就是在表头插入元素):时间复杂度O(1)

在这里插入图片描述

  1. 如果 i = 3(也就是在表中插入元素)

在这里插入图片描述

  1. 如果 i = 5(也就是在表尾插入元素):时间复杂度O(n)

在这里插入图片描述

3.6.2、不带头结点按位序插入

ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e

(找到第i-1个结点,将新结点插入其后)

在这里插入图片描述

bool ListInsert(LinkList &L,int i,ElemType e){

if(i<1)

{

return false;

}

if(i==1) //插入第1个结点的操作与其他结点操作不同

{

LNode *s =(LNode *)malloc(sizeof(LNode));

s->data = e;

s->next = L;

L = s; //头指针指向新结点

return true;

}

LNode *p; //指针p指向当前扫描到的结点

int j=1; //当前p指向的是第几个结点

p = L; //p指向第1个结点(注意:不是头结点)

while(P!=NULL && j<i-1){ //循环找到第 i-1 个结点

p=p->next;

j++;

}

if(p==NULL) //i值不合法

{

return false;

}

LNode *s = (LNode *)malloc(sizeof(LNode));

s->data=e;

//将s指向结点的next指针指向p指向结点的next指针

s->next = p->next;

p->next = s; //将p指向结点的next指针指向s

return true; //插入成功

}

}

  • 结论:不带头结点写代码不方便,推荐用带头结点

  • 注意:考试中带头、不带头都有可能考察,注意审题

3.7、指定结点的后插操作


后插操作:在结点之后插入元素

在这里插入图片描述

//后插操作:在p结点之后插入元素e

bool InsertNextNode(LNode *p,ElemType e){

if(p==NULL)

{

return false;

}

LNode *s =(LNode *)malloc(sizeof(LNode));

if(s==NULL) //某些情况下有可能分配失败,比如内存不足

{

return false;

}

s->data = e;

s->next = p->next;

p->next = s;

return true;

}

3.8、指定结点的前插操作


前插操作:在结点之前插入元素

在这里插入图片描述

//前插操作:在p结点之前插入元素e

bool InsertPriorNode(LNode *p,ElemType e){

if(p==NULL)

{

return false;

}

LNode *s =(LNode *)malloc(sizeof(LNode));

if(s==NULL) //内存分配失败

{

return false;

}

s->next = p->next;

p->next = s; //新结点 s 连接到 p 之后

s->data = p->data; //将p中元素复制到s中

p->data = e; //p中元素覆盖为e

return true;

}

王道书中的版本:

在这里插入图片描述

//前插操作:在p结点之前插入结点s

bool InsertPriorNode(LNode *p,LNode *s){

if(pNULL || sNULL)

{

return false;

}

s->next = p->next;

p->next = s; //s连到p之后

ElemType temp = p->data; //交换数据域部分

p->data = s->data;

s->data = temp;

return true;

}

3.9、单链表的删除


3.9.1、带头结点按位序删除

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值(找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点)

在这里插入图片描述

bool ListDelete(LinkList &L,int i,ElemType &e){

if(i<1)

{

return false;

}

LNode *p; //指针p指向当前扫描到的结点

int j = 0; //当前p指向的是第几个结点

p = L; //L指向头结点,头结点是第0个结点(不存数据)

while(p!=NULL && j<i-1) //循环找到第 i-1 个结点

{

p = p->next;

j++

}

if(p==NULL) // i值不合法

{

return false;

}

if(p->next == NULL) //第 i-1 个结点之后已无其他结点

{

return false;

}

LNode *q = p->next; //令q指向被删除结点

e = q->data; //用e返回元素的值

p->next = q->next; //将*q结点从链中断开

free(q); //释放结点的存储空间

return true; //删除成功

}

3.9.2、指定结点的删除

bool DeleteNode(LNode *p):删除结点p

在这里插入图片描述

//删除指定结点p

bool DeleteNode(LNode *p){

if(p==NULL)

{

return false;

}

LNode q = p->next; //令q指向p的后继结点

p->data = p->next->data;//和后继结点交换数据域

p->next = q->next; //将*q结点从链中"断开"

free(q); //释放后继结点的存储空间

return true;

}

3.10、单链表的查找


3.10.1、按位查找

GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。

//按位查找,返回第i个元素(带头结点)

LNode *GetElem(LinkList L,int i){

if(i<0)

{

return NULL;

}

LNode *p; //指针p指向当前扫描到的结点

int j = 0; //当前p指向的是第几个急待你

p = L; //L指向头结点,头结点是第0个结点(不存数据)

while(p!=NULL && j<i) //循环找到第i个结点

{

p = p->next; //让p指针依次向后移

j++;

}

return p;

}

  1. 如果i=0

在这里插入图片描述

  1. 如果 i = 8(i大于链表长度)

在这里插入图片描述

王道书版本:

在这里插入图片描述

LNode *GetElem(LinkList L,int i){

int j=1;

LNode *p = L->next;

if(i==0)

{

return L;

}

if(i<1)

{

return NULL;

}

while(p!=NULL && j<i)

{

p = p->next;

j++;

}

return p;

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

由于篇幅原因,就不多做展示了
点是第0个结点(不存数据)

while(p!=NULL && j<i) //循环找到第i个结点

{

p = p->next; //让p指针依次向后移

j++;

}

return p;

}

  1. 如果i=0

在这里插入图片描述

  1. 如果 i = 8(i大于链表长度)

在这里插入图片描述

王道书版本:

在这里插入图片描述

LNode *GetElem(LinkList L,int i){

int j=1;

LNode *p = L->next;

if(i==0)

{

return L;

}

if(i<1)

{

return NULL;

}

while(p!=NULL && j<i)

{

p = p->next;

j++;

}

return p;

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-3cWpMKuC-1711996328607)]
[外链图片转存中…(img-hyfV5HSc-1711996328608)]
[外链图片转存中…(img-F0FOujid-1711996328609)]
[外链图片转存中…(img-yIaw4L1u-1711996328609)]
[外链图片转存中…(img-8xVYTy1N-1711996328609)]
[外链图片转存中…(img-NuHC4oIC-1711996328610)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-GlhmnEIs-1711996328610)]

最后

[外链图片转存中…(img-BOnxFhV5-1711996328611)]

[外链图片转存中…(img-fk401HdJ-1711996328611)]

[外链图片转存中…(img-Hmg0UXmt-1711996328611)]

[外链图片转存中…(img-Lof5Lypl-1711996328612)]

[外链图片转存中…(img-WdWLpYcg-1711996328612)]

[外链图片转存中…(img-kstpp1ba-1711996328612)]

由于篇幅原因,就不多做展示了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值