算法拉胯之旅

LeetCode

11. 盛最多水的容器

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。

在这里插入图片描述

示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49



题解

双指针法
两个指针分别指向左右两端,每次水的容量为两个指针指向的数字中较小值∗指针之间的距离
每次记录下当次计算得的容量,移动指向数字较小的那个指针,向中间靠拢,计算得容量,若比原来的大则覆盖,否则保持原来的容量。

对于左右指针相等的情况,因为此时左右指针都是“瓶颈”,无论移动哪一个,因为x轴方向的长度变小,移动之后的计算的面积一定减小,故应该两个指针同时移动,而代码实现时任意移动一边的指针即可。

用到algorithm中的min()和max()函数。

class Solution {
public:
	// 双指针法
    int maxArea(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int area = 0;
        while (left < right) {
            int temp = min(height[left], height[right]) * (right - left);
            area = max(area, temp);
            // if (area < min(height[left], height[right]) * (right - left))
            //     area = min(height[left], height[right]) * (right - left);

            if (height[left] > height[right])
                right--;
            else if (height[left] <= height[right])
                left++;
        }
        return area;
    }
};

时间复杂度:O(n),向量中每个元素最多访问一次;
空间复杂度:O(1),额外使用常数级别的空间。


15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。

示例:给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]



题解

双指针法
先对数组进行排序,使用algorithm库的sort(nums.begin(), nums.end()),排序begin()到end()-1的数。
为得到不重复的三元组,需要进行相等判断,相等则移动指针,本题中是在边界之后判断数组某个index的值是否等于前一个使用的下标的值。

由于有外层循环first++条件,判断语句中使用continue,不能用first++,否则程序继续执行出现越界错误。

if (first > 0 && nums[first] == nums[first - 1]) 
	continue;

第二重循环内部,由于有second++的递增条件,可以不用显示判断左指针移动的情况,只需要在循环内部判断右指针移动的情况,同时进行左右指针位置的检验(left < right)。

while (second < third && nums[second] + nums[third] > target)
	third--;

左右指针边界条件的判断:

if (second == third)
	break;
	//相等,说明当前first值无法获取满足条件的三元组,停止当前循环
vector<vector<int>> threeSum(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    vector<vector<int>> ans;
    int n = nums.size();

    for (int first = 0; first < n; ++first) {
        // skip index that has the same value
        if (first > 0 && nums[first] == nums[first - 1]) 
            continue;
            // 不可为first++:下面程序会出现越界错误
        int target = -nums[first];
        int third = n - 1;
        for (int second = first + 1; second < n; ++second) {
            // skip index that has the same value
            if (second > first + 1 && nums[second] == nums[second - 1])
                continue;
            // 双指针典型用法
            while (second < third && nums[second] + nums[third] > target)
                third--;
            
            // 左右指针相等,first指针++
            if (second == third)
                break;
            if (nums[second] + nums[third] == target) {
                ans.push_back({nums[first], nums[second], nums[third]});
            }
        }
    }
    return ans;
}

时间复杂度:O(N2),N为nums数组的长度,对每一个值,在其右侧进行双指针判断(O(N));
空间复杂度:O(logN),额外的排序空间(sort函数)。由于改变了nums数组的值,如果需要另外存储nums数组,则为O(N)。


34.在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。

示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]



错误方法

顺序查找,时间复杂度为O(n),不满足O(logn)条件。

int left = 0, right = nums.size() - 1;
int ptr = 0, flag = 0;
if (left == right && nums[0] == target)
	return { 0, 0 };
	//二分法找到target位置
while (left < right) {
	ptr = (left + right) / 2;
	if (nums[ptr] > target) {
		right = ptr - 1;
	}
	else if (nums[ptr] < target) {
		left = ptr + 1;
	}
	else {
		flag = 1;
		break;
	}
}
if (flag == 0)
	return {-1, -1};
	
left = ptr;
right = ptr;
//但判断的时候不满足O(logn),仍为O(n)
while (nums[left] == target)
	left--;
while (nums[right] == target)
	right++;
return { left + 1, right - 1 };


题解

要求O(logn),故考虑二分法
由于要找出第一个和最后一个位置,在二分位置的值和target相等时,不停止,而是继续移动左右指针,最终找到第一个和最后一个位置。

本次代码实现较多,可以将第一个位置和最后一个位置的获取封装成两个函数。

	int left = 0, right = nums.size() - 1;
	int ptr = 0;
	while (left <= right) {
		ptr = (left + right) / 2;
		if (nums[ptr] > target) {
			right = ptr - 1;
		}
		else if (nums[ptr] < target) {
			left = ptr + 1;
		}

		//==========================关键代码===========================
		//==时,不停止循环,
		//right向左移动,结束时,如果数组nums中存在target,则left指向target的第一个出现的位置
		else {
			right = ptr - 1;
		}
	}
		
	int first_pos, last_pos;
	//注意两个条件顺序,nums[left] == target条件不可前置,否则会出错,涉及短路判断
	if (left < nums.size() && nums[left] == target) {
		first_pos = left;
	}
	else
		first_pos = -1;

	left = 0; right = nums.size() - 1;
	while (left <= right) {
		ptr = (left + right) / 2;
		if (nums[ptr] > target) {
			right = ptr - 1;
		}
		else if (nums[ptr] < target) {
			left = ptr + 1;
		}
		//==========================关键代码===========================
		//==时,不停止循环,
		//left向右移动,结束时,如果数组nums中存在target,则right指向target的最后一个出现的位置
		else {
			left = ptr + 1;
		}
	}
	last_pos = right;
	if (first_pos == -1) {
		return { -1, -1 };
	}
	return { first_pos, last_pos };

48.旋转图像

给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]

示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]



关于vector

初始化和输入

一维向量:

vector<int> nums;
int r;
cin >> r;

//说明nums向量的长度大小,才能对向量元素进行赋值操作
nums.resize(r);
for (int i = 0; i < r; ++i) {
	cin >> nums[i];
	//nums[i] = i * i...
}

二维向量:

int row;
int column;
cout << "请输入数组的行数和列数:";
cin >> row >> column;
//下面给向量分配存储空间
arr.resize(row);
for (int i = 0; i < row; i++)
{
	arr[i].resize(column);
}

将矩阵的输入和输出封装起来,之后本地调用函数即可进行输入和输出:

void inputMatrix(vector<vector<int>>& matrix) {
	int r, c;
	cout << "row: ";
	cin >> r;
	cout << "column: ";
	cin >> c;
	matrix.resize(r);
	for (int i = 0; i < r; ++i) {
		matrix[i].resize(c);
		//需要先确定向量的长度,之后才能对确定下标的元素操作赋值/输入
			//否则只能使用push_back
	}

	//输入r*c的矩阵
	for (int i = 0; i < r; ++i) {
		for (int j = 0; j < c; ++j) {
			cin >> matrix[i][j];
		}
	}
}
void outputMatrix(vector<vector<int>>& matrix) {
	//输出r*c的矩阵
	for (int i = 0; i < matrix.size(); ++i) {
		for (int j = 0; j < matrix[0].size(); ++j) {
			cout << matrix[i][j];
			if (j != matrix[0].size() - 1)
				cout << ", ";
			else
				cout << endl;
		}
	}
}

题解

由于要原地翻转,即空间复杂度为O(1),不可以使用一个与输入矩阵长度相关联的空间存储中间值。

有的题解使用逐层旋转的方法,比较难操作比较繁琐。
为了实现顺时针旋转90°,这里使用矩阵的翻转操作:

  1. 先对矩阵上下翻转;
  2. 对矩阵做转置。
    注意:1.2.顺序不调换,若先转置,后面左右翻转操作需要对二维操作,而先上下翻转只需要一位操作即可。
void rotate(vector<vector<int>>& matrix) {
    int r = matrix.size(), c = matrix[0].size();

    //先上下翻转,不左右,因为上下翻转只需要到行坐标,更方便
    for (int i = 0; i < r / 2; ++i) {
        swap(matrix[i], matrix[r - i - 1]);
    }

    //再转置
    for (int i = 0; i < r; ++i) {
        for (int j = i; j < c; ++j) {
            swap(matrix[i][j], matrix[j][i]);
        }
    }
}

54.螺旋矩阵

给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。

示例 1:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]

示例 2:
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出: [1,2,3,4,8,12,11,10,9,5,6,7]



题解

逐层由外到内
如图所示,看成一层一层的矩阵。先输出最外层,在逐步输出内层,直到结束。
如何判断边界呢?
设定4个值即可:left、top、right、bottom,即左上角右下角的坐标(top,left)和(bottom,right)。

  1. 先输出(top,left)到(top,right)的;
  2. 再输出(top,right)到(bottom,right);
  3. 此时需要判断该层内部是否“包着下一层”,是则进入4,否则该层就是一个行向量或者列向量,进行了12之后已经遍历完成;
  4. 输出(bottom,right - 1)到(bottom,left);
  5. 输出(bottom - 1, left)到(top + 1, left);
  6. top++,bottom–,left++,right–,重复上述步骤。

注意:边界条件的判断,bottom和right是逐次递减的,top和left是逐次递增的,不要弄错。

vector<int> spiralOrder(vector<vector<int>>& matrix) {
	//如果矩阵为空,返回一个空向量,向量用{}包起来。
	if (matrix.size() == 0 || matrix[0].size() == 0)
		return {};

	int row = matrix.size(), col = matrix[0].size();
	vector<int> ans;
	int top = 0, left = 0, bottom = row - 1, right = col - 1;
	while (top <= bottom && left <= right) {
		for (int i = left; i <= right; ++i) {
			ans.push_back(matrix[top][i]);
		}
		for (int j = top + 1; j <= bottom; ++j) {
			ans.push_back(matrix[j][right]);
		}
		if (left < right && top < bottom) {
			for (int ii = right - 1; ii > left; --ii) {
				ans.push_back(matrix[bottom][ii]);
			}
			for (int jj = bottom; jj > top; --jj) {
				ans.push_back(matrix[jj][left]);
			}
		}
		top++;
		bottom--;
		left++;
		right--;
	}
	return ans;
}

55.跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。

示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。



题解

使用贪心算法。记录下当前能到达的最远下标farthest,在[当前下标, farthest]里移动,不断更新farthest,若farthest大于最右下标,则返回true,否则若遍历完成,则返回false。

注意:

  1. 更新farthest容易漏掉,使用algorithm库的max()函数判断当前farthestnums[i]+i的较大值;
  2. 只有下标满足index<farthest条件时,才能更新farthest,否则index为无效值,不参与更新;
  3. 由于2,可以在遍历循环中添加if条件,满足条件才更新farthest。
bool canJump(vector<int>& nums) {
	int index = 0, farthest = 0;
	//遍历数组
	for (int i = 0; i < nums.size(); ++i) {
		//只有满足<farthest条件才更新farthest值
		if (i <= farthest) {
			farthest = max(farthest, nums[i] + i);
			if (farthest >= nums.size() - 1)
				return true;
		}
	}
	//遍历结束,说明farthest始终<nums.size()-1,故返回false
	return false;
}

56.合并区间

给出一个区间的集合,请合并所有重叠的区间。

示例 1:
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。



题解

  1. sort对二维vector的作用:按照二维数组中每一行的第一个元素进行大小排序,该元素大,则该元素所在一维数组就大。

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <time.h>
    using namespace std;
    
    int main()
    {
    	srand(time(NULL));
    	vector<vector<int> > a(10);
    	for (int i = 0; i < 10; i++)
    		for (int j = 0; j < 10; j++) {
    			a[i].push_back(rand() % 100);
    		}
    	for (int i = 0; i < 10; i++) {
    		for (int j = 0; j < 10; j++) {
    			cout << a[i][j] << "\t";
    		}
    		cout << endl;
    	}
    	cout << "After 2D sorting:" << endl;
    	sort(a.begin(), a.end());
    	for (int i = 0; i < 10; i++) {
    		for (int j = 0; j < 10; j++) {
    			cout << a[i][j] << "\t";
    		}
    		cout << endl;
    	}
    	system("pause");
    	return 0;
    }
    

    结果如下:
    在这里插入图片描述

  2. 贪心算法

  • 先对区间按上述方法排序,初始时将第一个区间加入到结果数组results数组中,随后按顺序考虑之后的每个区间;

  • 当前区间的左端点大于results最后一个区间的右端点,则它们不会重合,将当前区间直接加入到results的末尾

  • 否则它们重合更新results中最后一个区间的右端点,设为二者的较大值

  • 对于第一次循环要单独拉出来考虑,这里第一次循环的操作和判断不会重合的情况一致,故将第一次循环的条件和不会重合的条件放在一起。

    class Solution {
    public:
    // 贪心算法
    // 1.先对所有区间按照左端点大小排序;
    // 2.逐个对比当前区间左端点和results中最后一个区间右端点大小。(初始时将第一个区间直接加入到results)
        vector<vector<int>> merge(vector<vector<int>>& intervals) {
            if (intervals.size() == 0)
                return {};
            sort(intervals.begin(), intervals.end());
            vector<vector<int>> results;
            for (int i = 0; i < intervals.size(); ++i) {
                int L = intervals[i][0], R = intervals[i][1];
                if (!results.size() || results.back()[1] < L) {
                    results.push_back({L, R});//添加一个二元数组
                }
                else {
                    results.back()[1] = max(results.back()[1], R);
                }
            }
            return results;
        }
    };
    

62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
在这里插入图片描述
输入:m = 3, n = 7
输出:28



题解

  1. 递归
    最开始想到的是递归。每次到达之后相当于求解(m-1, n)或(m, n-1)规模的问题。
    但是因为递归中,重复计算太多的值,导致超时。
    int uniquePaths(int m, int n) {
    	if (m == 1 || n == 1)
    		return 1;
    	return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
    }
    
  2. 动态规划(dynamic programming)
    之前不了解dp,发现思想和递归很像,都是大规模问题转换为更小规模的问题来解决。
  3. 组合数学
    高中数学问题。m*n的方格,左上角到达右下角,只能向右向下
    共走了m+n-2次,其中向右n-1次,向下m-1次,组合数问题。
    C m + n − 2 m − 1 = ( m + n − 2 ) ( m + n − 3 ) . . . n ( m − 1 ) ! . C{^{m-1} _{m+n-2}} = \frac{(m+n-2)(m+n-3)...n}{(m-1)!}. Cm+n2m1=(m1)!(m+n2)(m+n3)...n.
    int uniquePaths(int m, int n) {
        long int ans = 1;
        for (int x = 1, y = n; x < m; ++x, ++y) {
            ans = ans * y / x; //注意不要写成ans *= y / x,有小数舍去。
        }
        return ans;
    }
    

73. 矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。
示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]

示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]



题解

不能直接对原矩阵作置0操作,否则会改变后面要遍历的元素的值,导致错误。

  1. 暴力。
    最简单的方法。直接复制一个matrix2,遍历原矩阵matrix1,对matrix2操作,需要的额外的空间O(mn)
  2. 标记要置0的行和列。
    方法1的改进。不存储整个矩阵,存储需要置0的行和列的标志,每行每列都有一个标志位,需要的额外空间O(m+n)
    // 不能一边遍历一边更改,后面的数据需要用原本矩阵的值。
    // 2.m+n的额外存储空间,标记需要置0的行和列。
    void setZeroes(vector<vector<int>>& matrix) {
    	int row = matrix.size(), col = matrix[0].size();
    	// 初始化为true。
    	vector<bool> rowReset(row, true);
    	vector<bool> colReset(col, true);
    	for (int i = 0; i < row; ++i) {
    		for (int j = 0; j < col; ++j) {
    			if (matrix[i][j] == 0) {
    				rowReset[i] = 0;
    				colReset[j] = 0;
    			}
    		}
    	}
    	// 修改标记为false的行。
    	for (int i = 0; i < row; ++i) {
    		if (rowReset[i] == 0)
    			for (int j = 0; j < col; ++j)
    				matrix[i][j] = 0;
    	}
    	// 修改标记为false的列。
    	for (int i = 0; i < col; ++i) {
    		if (colReset[i] == 0)
    			for (int j = 0; j < row; ++j)
    				matrix[j][i] = 0;
    	}
    }
    
  3. 方法2的改进:用第一行和第一列来存储标志位,第一行和第一列的标志位用两个变量存储即可。
    用第一行和第一列存储标志位,注意修改元素值的时候,先从第二行第二列开始,即先不改动标志位的第一行和第一列,最后在根据两个标记变量修改。
    // 3.方法2的改进:用第一行和第一列来存储标志位,第一行和第一列的标志位用两个变量存储即可。
    void setZeroes(vector<vector<int>>& matrix) {
        int row = matrix.size(), col = matrix[0].size();
    
        // 初始化为true。
        bool firstRow = true, firstCol = true;
        for (int i = 0; i < col; ++i) {
            if (matrix[0][i] == 0) {
                firstRow = false;
                break;
            }
        }
        for (int i = 0; i < row; ++i) {
            if (matrix[i][0] == 0) {
                firstCol = false;
                break;
            }
        }
    
        // 从第二行和第二列开始,第一行和第一列若有0,则标志位就是0,不用操作。
        for (int i = 1; i < row; ++i) {
            for (int j = 1; j < col; ++j) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = 0;
                    matrix[0][j] = 0;
                }
            }
        }
    
        // 置0,注意下标从1开始,否则改变了标记位的值,导致出错。
        for (int i = 1; i < row; ++i) {
            for (int j = 1; j < col; ++j) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
    
        if (firstRow == 0) {
            for (int i = 0; i < col; ++i)
                matrix[0][i] = 0;
        }
        if (firstCol == 0) {
            for (int i = 0; i < row; ++i)
                matrix[i][0] = 0;
        }
    }
    

75. 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?

示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
示例 3:
输入:nums = [0]
输出:[0]
示例 4:
输入:nums = [1]
输出:[1]



题解

  1. 直接用algorithm库进行排序即可。
    sort(nums.begin(), nums.end());
    
  2. 单指针法。(当想改变数组,又想标记数组本身,考虑指针
    两遍遍历,第一次把0移到前面,第二次把1移到0的后面。
    用一个指针表示头部尾部(不包括头部),一开始为0,表示没有头部
    使用标准库中的swap()交换。
    时间:O(n).
    空间:O(1).
    int n = nums.size();
    int ptr = 0;
    for (int i = 0; i < n; ++i) {
    	if (nums[i] == 0) {
    		swap(nums[i], nums[ptr]);
    		ptr++;
    	}
    }
    
    for (int i = ptr; i < n; ++i) {
    	if (nums[i] == 1) {
    		swap(nums[i], nums[ptr]);
    		ptr++;
    	}
    }
    
  3. 双指针法。(略)

78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]



题解

  1. 子集二进制掩码枚举。
    在这里插入图片描述
    可以用1~ 2 n 2^n 2n来表示n个元素的选/不选,有 2 n 2^n 2n个mask,集合中每一个元素有自己唯一的位置,用(1 << 下标)表示。
    用位操作&来判断。
    vector<vector<int>> subsets(vector<int>& nums) {
    	vector<vector<int>> ans;
    	vector<int> tmp;
    	int n = nums.size();
    	for (int mask = 0; mask < (1 << n); ++mask) {
    		tmp.clear();
    		for (int i = 0; i < n; ++i) {
    			if (mask & (1 << i))
    				tmp.push_back(nums[i]);
    		}
    		ans.push_back(tmp);
    	}
    	return ans;
    }
    
  2. 回溯(TODO)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值