215. 数组中的第K个最大元素(快速排序、堆排序)

根据这道题总结一下快速排序和堆排序,再根据这两种方法写这道题。

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104

我们首先给出快速排序的代码,快速排序的思路是先选取一个基准值,然后把小于基准值的放到基准值左边,把大于基准值的放到基准值右边,这样就会变成三部分(基准值左边部分、基准值、基准值右边部分),对基准值左右再递归进行这个步骤。代码分三部分:快速排序辅助分区部分、排序部分和主函数,分区部分就是把比基准值小的放左边,比基准值大的放右边,然后把基准值放中间,排序部分就是递归排序。

#include <iostream>
#include <vector>
#include <utility> // for std::swap

// 快速排序的辅助函数,进行分区
int partition(std::vector<int> &nums, int low, int high) {
    // 选择最左侧的元素作为基准值(pivot)
    int pivot = nums[low];
    int i = low + 1; // i指针用来记录比基准值小的区域的最后一个元素的位置
    int j = high; // j指针用来记录比基准值大的区域的第一个元素的位置

    // 循环进行分区操作
    while(true) {
        // 从左向右找,找到大于等于基准值的元素
        while (nums[i] < pivot) {
            i++;
        }
        // 从右向左找,找到小于等于基准值的元素
        while (nums[j] > pivot) {
            j--;
        }
        if (i < j) {
            std::swap(nums[i], nums[j]);
        } else {
            // 完成分区,左边全是小于等于基准值,右边全是大于等于基准值
            break;
        }
    }

    // 交换基准值到分区的中间
    std::swap(nums[low], nums[j]);

    // 返回基准值的最终位置
    return i;
}

// 快速排序的递归函数
void quickSort(std::vector<int> &nums, int low, int high) {
    if (low < high) {
        // 分区操作
        int pivotIndex = partition(nums, low, high);

        // 对基准值左边的子序列进行快速排序
        quickSort(nums, low, pivotIndex - 1);

        // 对基准值右边的子序列进行快速排序
        quickSort(nums, pivotIndex + 1, high);
    }
}

int main() {
    std::vector<int> nums = {10, 7, 8, 9, 1, 5};
    int n = nums.size();
    quickSort(nums, 0, n - 1);
    for (int num : nums) {
        std::cout << num << " ";
    }
    return 0;
}

运行结果(每一步分区的过程)为:

6 7 8 9 1 5 3 3 6 1 10 
6 1 3 3 1 5 6 9 8 7 10 
5 1 3 3 1 6 6 9 8 7 10 
1 1 3 3 5 6 6 9 8 7 10 
1 1 3 3 5 6 6 9 8 7 10 
1 1 3 3 5 6 6 9 8 7 10 
1 1 3 3 5 6 6 7 8 9 10 
1 1 3 3 5 6 6 7 8 9 10 
1 1 3 3 5 6 6 7 8 9 10 

快速排序的时间复杂度是O(nlogn)

基于快速排序可以写出做这道题的快速选择方法的代码,与快速排序一样,需要先分区,之后确定了基准值最终所在的位置,然后不需要进行排序操作,只需要知道第k大的元素是在基准值左边还是右边,然后在那个分区找就可以了,也是递归来查找,这个就是在快排的过程中直接找到了,所以不需要进行完整的快排,因此复杂度变低:

#include <iostream>
#include <vector>
#include <utility> // for std::swap

// 快速排序的辅助函数,进行分区
int partition(std::vector<int> &nums, int low, int high) {
    // 选择最左侧的元素作为基准值(pivot)
    int pivot = nums[low];
    int i = low + 1; // i指针用来记录比基准值小的区域的最后一个元素的位置
    int j = high; // j指针用来记录比基准值大的区域的第一个元素的位置

    // 循环进行分区操作
    while(true) {
        // 从左向右找,找到大于等于基准值的元素
        while (nums[i] < pivot) {
            i++;
        }
        // 从右向左找,找到小于等于基准值的元素
        while (nums[j] > pivot) {
            j--;
        }
        if (i < j) {
            std::swap(nums[i], nums[j]);
        } else {
            // 完成分区,左边全是小于等于基准值,右边全是大于等于基准值
            break;
        }
    }

    // 交换基准值到分区的中间
    std::swap(nums[low], nums[j]);

    // 返回基准值的最终位置
    return j;
}

// 快速排序的递归函数
int quickSelect(std::vector<int> &nums, int low, int high, int kIndex) {
    if (low == high) {
        // 当子数组只有一个元素时,返回该元素
        return nums[low];
    }

    int pivotIndex = partition(nums, low, high);
    
    if (kIndex <= pivotIndex) {
        // 第k大的元素索引在左侧子数组中
        return quickSelect(nums, low, pivotIndex, kIndex);
    } else {
        // 第k大的元素索引在右侧子数组中
        return quickSelect(nums, pivotIndex + 1, high, kIndex);
    }
}

int main() {
    std::vector<int> nums = {10, 5, 3, 2, 1, 6, 8, 7};
    int n = nums.size();
    int k = 3;
    // 第k大的元素的索引是k-1
    int kIndex = k - 1;
    int ans = quickSelect(nums, 0, n - 1, n - 1 - kIndex);
    std::cout << "The ans is " << ans << std::endl;
    return 0;
}

注意,当求第k大的元素时,传入的是索引k-1,当求第k小的元素(第n-k+1大)时,传入索引n-k(即n-1-kIndex)。这个方法时间复杂度是O(n)

下面来总结一下堆排序和这道题,我们给出堆排序的代码:

#include <iostream>
#include <vector>
#include <algorithm> // for std::swap

// 自上向下调整堆,保证堆的性质
void heapify(std::vector<int> &nums, int n, int i) {
    int largest = i; // 初始时假设当前节点为最大值
    int left = 2 * i + 1;  // 左子节点
    int right = 2 * i + 2; // 右子节点

    // 如果左子节点存在且大于当前节点,更新最大值节点
    if (left < n && nums[left] > nums[largest]) {
        largest = left;
    }

    // 如果右子节点存在且大于当前节点,更新最大值节点
    if (right < n && nums[right] > nums[largest]) {
        largest = right;
    }

    // 如果最大值节点发生了变化,交换当前节点和最大值节点的值,并继续调整
    if (largest != i) {
        std::swap(nums[i], nums[largest]);
        heapify(nums, n, largest);
    }
}

// 堆排序
void heapSort(std::vector<int> &nums) {
    int n = nums.size();

    // 从最后一个非叶子节点开始建堆,即从 (n/2 - 1) 节点开始
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(nums, n, i);
    }

    // 从最后一个元素开始,交换元素并进行调整堆操作
    for (int i = n - 1; i > 0; i--) {
        std::swap(nums[0], nums[i]); // 将当前堆的最大值放到数组末尾
        heapify(nums, i, 0); // 调整堆,新的堆大小为 i
    }
}

int main() {
    std::vector<int> nums = {16, 10, 8, 7, 2, 3, 4, 1, 9, 14};
    heapSort(nums);
    std::cout << "Sorted array: ";
    for (int num : nums) {
        std::cout << num << " ";
    }
    return 0;
}

堆排序有三个重要部分:维护堆的性质,建堆,排序。以大根堆为例,这是一颗完全二叉树,父节点的值大于子节点的值,下标为i的节点的父节点下标是(i - 1) / 2(整数除法),下标为i的节点的左孩子下标是i * 2 + 1,右孩子下标是i * 2 + 2,因此,假如有n个元素,那么堆的最后一个非叶子节点的下标是n / 2 - 1

  • 维护堆的性质,即为保证父节点值大于子节点值,从上而下调整,比如当前i节点不满足这个性质,那么交换i节点和它的左右孩子中最大的那个,然后再判断子节点那里是否满足堆的性质(之所以需要这样是因为如果进行了交换,那么子节点那里可能会发生变化,比如3 6 5 2 4这个情况,首先36进行了交换,变成了6 3 5 2 4,那么3 2 4那个部分(之前是6 2 4)就需要再次进行交换)。
  • 建堆,即从最后一个非叶子节点开始,自下而上维护堆的性质,直到根节点。
  • 堆排序,将当前堆的最大值放到数组末尾,然后把它排除出去,再从根向下进行堆的维护,新的堆的大小为n-1,重复这个过程,直到只剩一个元素。
    运行结果为:
create heap
16 14 8 9 10 3 4 1 7 2
sort heap for 9 nums 14 10 8 9 2 3 4 1 7 16
sort heap for 8 nums 10 9 8 7 2 3 4 1 14 16
sort heap for 7 nums 9 7 8 1 2 3 4 10 14 16
sort heap for 6 nums 8 7 4 1 2 3 9 10 14 16
sort heap for 5 nums 7 3 4 1 2 8 9 10 14 16
sort heap for 4 nums 4 3 2 1 7 8 9 10 14 16
sort heap for 3 nums 3 1 2 4 7 8 9 10 14 16
sort heap for 2 nums 2 1 3 4 7 8 9 10 14 16
sort heap for 1 nums 1 2 3 4 7 8 9 10 14 16
sorted heap
1 2 3 4 7 8 9 10 14 16

可以看到16, 10, 8, 7, 2, 3, 4, 1, 9, 14经过建堆过程(自最后一个非叶子节点向上维护堆),变成了16 14 8 9 10 3 4 1 7 2,然后需要进行堆排序,将1614交换,然后不管16了,这个时候它是最后一个元素,再从根向下维护堆,得到14 10 8 9 2 3 4 1 7 16,然后再将147交换,进行相同的步骤,最后排序成功。

有了堆排序的基础,我们利用堆排序解决数组中的第K个最大元素的问题,事实上,在堆排序取最大值的过程中,已经体现出来了,在第一次取16,这就是第1大的元素,第二次取14就是第2大的元素,那么我们想得到第k大元素的值,只需要设置堆排序的停止条件为i > n - k,然后这时候的nums[0](即根节点值)为第k大的元素。如果我们想得到第’k’小的元素,那么就取第n-k+1大的元素。

详细代码如下:

#include <iostream>
#include <vector>
#include <algorithm> // for std::swap

// 自上向下调整堆,保证堆的性质
void heapify(std::vector<int> &nums, int n, int i) {
    int largest = i; // 初始时假设当前节点为最大值
    int left = 2 * i + 1;  // 左子节点
    int right = 2 * i + 2; // 右子节点

    // 如果左子节点存在且大于当前节点,更新最大值节点
    if (left < n && nums[left] > nums[largest]) {
        largest = left;
    }

    // 如果右子节点存在且大于当前节点,更新最大值节点
    if (right < n && nums[right] > nums[largest]) {
        largest = right;
    }

    // 如果最大值节点发生了变化,交换当前节点和最大值节点的值,并继续调整
    if (largest != i) {
        std::swap(nums[i], nums[largest]);
        heapify(nums, n, largest);
    }
}

// 堆排序取数
int heapSelect(std::vector<int> &nums, int k) {
    int n = nums.size();

    // 从最后一个非叶子节点开始建堆,即从 (n/2 - 1) 节点开始
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(nums, n, i);
    }

    std::cout << "create heap" << std::endl;
    for (int num : nums) {
        std::cout << num << " ";
    }
    std::cout << "\n";

    // 从最后一个元素开始,交换元素并进行调整堆操作
    for (int i = n - 1; i > n - k; i--) {
        std::swap(nums[0], nums[i]); // 将当前堆的最大值放到数组末尾
        heapify(nums, i, 0); // 调整堆,新的堆大小为 i
        std::cout << "sort heap for " << i << " nums" << " ";
        for (int num : nums) {
            std::cout << num << " ";
        }
        std::cout << "\n";
    }

    std::cout << "sorted heap" << std::endl;  
    for (int num : nums) {
        std::cout << num << " ";
    }
    return nums[0];
}

int main() {
    std::vector<int> nums = {16, 10, 8, 7, 2, 3, 4, 1, 9, 14};
    int n = nums.size();
    int k = 4;
    int ans = heapSelect(nums, n - k + 1);
    std::cout << "ans=" << ans << std::endl;
    return 0;
}

运行结果:

create heap
16 14 8 9 10 3 4 1 7 2
sort heap for 9 nums 14 10 8 9 2 3 4 1 7 16
sort heap for 8 nums 10 9 8 7 2 3 4 1 14 16
sort heap for 7 nums 9 7 8 1 2 3 4 10 14 16
sort heap for 6 nums 8 7 4 1 2 3 9 10 14 16
sort heap for 5 nums 7 3 4 1 2 8 9 10 14 16
sort heap for 4 nums 4 3 2 1 7 8 9 10 14 16
sorted heap
4 3 2 1 7 8 9 10 14 16 ans=4

时间复杂度是O(nlogn),建堆的复杂度是O(n),删除堆顶元素的复杂度是O(klogn),所以总共的时间复杂度是O(n+klogn)=O(nlogn)

  • 45
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、冒泡排序属于稳定排序,是一种借助“交换”进行排序的方法。首先要将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则将两个记录交换之,然后比较第二个记录与第三个记录的关键字,以此类推,直至第n-1个记录与第n个记录的关键字进行比较为止,这一过程称为第一趟冒泡排序,其结果使得关键字最大的记录被安置在最后一个记录的位置上;然后进行第二趟冒泡排序,对前N-1个记录进行同样操作;以此类推,直到在一趟排序过程没有进行过交换记录的操作为止。 2、直接插入排序属于稳定的排序,每次从无序表取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。第一趟将待比较的数值与它的前一个数值进行比较,当前一数值比待比较数值大的情况下继续循环比较,依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程,结束该次循环。 3、快速排序属于不稳定排序,是对起泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。假设待排序的序列为{R.[s],R.[s+1],…….,R.[t]},首先任意选取一个记录,然后按下述原则从新排序记录:将关键字较他小的记录都安置在他的位置之前,将所有关键字较他大的记录都安置在他的位置后面。由此可以该“枢轴”记录最后所落的位置i作为分界线,将序列{R[s],R[s+1]…….R[t]}分割成两个子序列{R[s],R[s+1]…..R[i-1]}和{R[i+1]……R[t]},这个过程称作一趟快速排序。一趟快速排序的具体做法是:附设两个指针low和high,它们的初值分别指向数组第一个数据和最后一个数据,将枢轴记录暂存在R[0]的位置上排序过程只作R[low]或R[high]的单向移动,直至一趟排序结束后再将枢轴记录移至正确位置上。 4、简单选择排序属于不稳定排序,基本思想是,每一趟在n-i+1(i=1,2,…n-1)个记录选取关键字最小的记录作为有序序列第i个记录。第i趟简单选择排序是指通过n-i次关键字的比较,从n-i+1个记录选出关键字最小的记录,并和第i个记录进行交换。共需进行n-1趟比较,直到所有记录排序完成为止。例如:进行第i趟选择时,从当前候选记录选出关键字最小的k号记录,并和第i个记录进行交换。 5、希尔排序属于不稳定排序,也是一种属插入排序类,它的基本思想是:先将整个待排记录序列分割称为若干个子序列分别进行直接插入排序,待整个序列记录“基本有序”时,再对全体记录进行一次直接插入排序。希尔排序的一个特点是:子序列的构成不是简单的“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列。 6、堆排序属于不稳定排序,它的基本思想是,先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区,再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key;由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆,然后再次将R[1..n-1]关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n- 2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。直到无序区只有一个元素为止。
220个经典C程序源码文件,可以做为你的学习设计参考: 第一部分 基础篇 001 第一个C程序 002 运行多个源文件 003 求整数之积 004 比较实数大小 005 字符的输出 006 显示变量所占字节数 007 自增/自减运算 008 数列求和 009 乘法口诀表 010 猜数字游戏 011 模拟ATM(自动柜员机)界面 012 用一维数组统计学生成绩 013 用二维数组实现矩阵转置 014 求解二维数组的最大/最小元素 015 利用数组求前n个质数 016 编制万年历 017 对数组元素排序 018 任意进制数的转换 019 判断回文数 020 求数组前n元素之和 021 求解钢材切割的最佳订单 022 通过指针比较整数大小 023 指向数组的指针 024 寻找指定元素的指针 025 寻找相同元素的指针 026 阿拉伯数字转换为罗马数字 027 字符替换 028 从键盘读入实数 029 字符行排版 030 字符排列 031 判断字符串是否回文 032 通讯录的输入输出 033 扑克牌的结构表示 034 用“结构”统计学生成绩 035 报数游戏 036 模拟社会关系 037 统计文件的字符数 038 同时显示两个文件的内容 039 简单的文本编辑器 040 文件的字数统计程序 041 学生成绩管理程序 第二部分 数据结构篇 042 插入排序 043 希尔排序 044 冒泡排序 045 快速排序 046 选择排序 047 堆排序 048 归并排序 049 基数排序 050 二叉搜索树操作 051 二项式系数递归 052 背包问题 053 顺序表插入和删除 054 链表操作(1) 055 链表操作(2) 056 单链表就地逆置 057 运动会分数统计 058 双链表 059 约瑟夫环 060 记录个人资料 061 二叉树遍利 062 浮点数转换为字符串 063 汉诺塔问题 064 哈夫曼编码 065 图的深度优先遍利 066 图的广度优先遍利 067 求解最优交通路径 068 八皇后问题 069 骑士巡游 070 用栈设置密码 071 魔王语言翻译 072 火车车厢重排 073 队列实例 074 K阶斐波那契序列 第三部分 数值计算与趣味数学篇 075 绘制余弦曲线和直线的迭加 076 计算高次方数的尾数 077 打鱼还是晒网 078 怎样存钱以获取最大利息 079 阿姆斯特朗数 080 亲密数 081 自守数 082 具有abcd=(ab+cd)2性质的数 083 验证歌德巴赫猜想 084 素数幻方 085 百钱百鸡问题 086 爱因斯坦的数学题 087 三色球问题 088 马克思手稿的数学题 089 配对新郎和新娘 090 约瑟夫问题 091 邮票组合 092 分糖果 093 波瓦松的分酒趣题 094 求π的近似值 095 奇数平方的有趣性质 096 角谷猜想 097 四方定理 098 卡布列克常数 099 尼科彻斯定理 100 扑克牌自动发牌 101 常胜将军 102 搬山游戏 103 兔子产子(菲波那契数列) 104 数字移动 105 多项式乘法 106 产生随机数 107 堆栈四则运算 108 递归整数四则运算 109 复平面作图 110 绘制彩色抛物线 111 绘制正态分布曲线 112 求解非线性方程 113 实矩阵乘法运算 114 求解线性方程 115 n阶方阵求逆 116 复矩阵乘法 117 求定积分 118 求满足特异条件的数列 119 超长正整数的加法 第四部分 图形篇 120 绘制直线 121 绘制圆 122 绘制圆弧 123 绘制椭圆 124 设置背景色和前景色 125 设置线条类型 126 设置填充类型和填充颜色 127 图形文本的输出 128 金刚石图案 129 飘带图案 130 圆环图案 131 肾形图案 132 心脏形图案 133 渔网图案 134 沙丘图案 135 设置图形方式下的文本类型 136 绘制正多边形 137 正六边形螺旋图案 138 正方形螺旋拼块图案 139 图形法绘制圆 140 递归法绘制三角形图案 141 图形法绘
部分代码 010 猜数字游戏 011 模拟ATM(自动柜员机)界面 012 用一维数组统计学生成绩 013 用二维数组实现矩阵转置 014 求解二维数组的最大/最小元素 015 利用数组求前n个质数 016 编制万年历 017 对数组元素排序 018 任意进制数的转换 019 判断回文数 020 求数组前n元素之和 021 求解钢材切割的最佳订单 022 通过指针比较整数大小 023 指向数组的指针 024 寻找指定元素的指针 025 寻找相同元素的指针 026 阿拉伯数字转换为罗马数字 027 字符替换 028 从键盘读入实数 029 字符行排版 030 字符排列 031 判断字符串是否回文 032 通讯录的输入输出 033 扑克牌的结构表示 034 用“结构”统计学生成绩 035 报数游戏 036 模拟社会关系 037 统计文件的字符数 038 同时显示两个文件的内容 039 简单的文本编辑器 040 文件的字数统计程序 041 学生成绩管理程序 042 插入排序 043 希尔排序 044 冒泡排序 045 快速排序 046 选择排序 047 堆排序 048 归并排序 049 基数排序 050 二叉搜索树操作 051 二项式系数递归 052 背包问题 053 顺序表插入和删除 054 链表操作(1) 055 链表操作(2) 056 单链表就地逆置 057 运动会分数统计 058 双链表 059 约瑟夫环 060 记录个人资料 061 二叉树遍利 062 浮点数转换为字符串 063 汉诺塔问题 064 哈夫曼编码 065 图的深度优先遍利 066 图的广度优先遍利 067 求解最优交通路径 068 八皇后问题 069 骑士巡游 070 用栈设置密码 071 魔王语言翻译 072 火车车厢重排 073 队列实例 074 K阶斐波那契序列 第三部分 数值计算与趣味数学篇 075 绘制余弦曲线和直线的迭加 076 计算高次方数的尾数 077 打鱼还是晒网 078 怎样存钱以获取最大利息 079 阿姆斯特朗数 080 亲密数 081 自守数 082 具有abcd=(ab+cd)2性质的数 083 验证歌德巴赫猜想 084 素数幻方 085 百钱百鸡问题 086 爱因斯坦的数学题 087 三色球问题 088 马克思手稿的数学题 089 配对新郎和新娘 090 约瑟夫问题 091 邮票组合 092 分糖果 093 波瓦松的分酒趣题 094 求π的近似值 095 奇数平方的有趣性质 096 角谷猜想 097 四方定理 098 卡布列克常数 099 尼科彻斯定理 100 扑克牌自动发牌 101 常胜将军 102 搬山游戏 103 兔子产子(菲波那契数列) 104 数字移动 105 多项式乘法 106 产生随机数 107 堆栈四则运算 108 递归整数四则运算 109 复平面作图 110 绘制彩色抛物线 111 绘制正态分布曲线 112 求解非线性方程 113 实矩阵乘法运算 114 求解线性方程 115 n阶方阵求逆 116 复矩阵乘法 117 求定积分 118 求满足特异条件的数列 119 超长正整数的加法 第四部分 图形篇 120 绘制直线 121 绘制圆 122 绘制圆弧 123 绘制椭圆 124 设置背景色和前景色 125 设置线条类型 126 设置填充类型和填充颜色 127 图形文本的输出 128 金刚石图案 129 飘带图案 130 圆环图案 131 肾形图案 132 心脏形图案 133 渔网图案 134 沙丘图案 135 设置图形方式下的文本类型 136 绘制正多边形 137 正六边形螺旋图案 138 正方形螺旋拼块图案 139 图形法绘制圆 140 递归法绘制三角形图案 141 图形法绘制椭圆 142 抛物样条曲线 143 Mandelbrot分形图案 144 绘制布朗运动曲线 145 艺术清屏 146 矩形区域的颜色填充 147 VGA256色模式编程 148 绘制蓝天图案 149 屏幕检测程序 150 运动的小车动画 151 动态显示位图 152 利用图形页

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值