Leetcode-树的遍历


q94 二叉树的中序遍历


题目传送门


题解

func inorderTraversal(root *TreeNode) []int {
	var res []int
	if root == nil {
		return res
	}
	var dfs func(root *TreeNode, res *[]int)
	dfs = func(root *TreeNode, res *[]int) {
		if root.Left != nil {
			dfs(root.Left, res)
		}
		*res = append(*res, root.Val)
		if root.Right != nil {
			dfs(root.Right, res)
		}
	}
	dfs(root, &res)
	return res
}

q102 二叉树的层次遍历


题目传送门


题解

使用队列实现:

func levelOrder(root *TreeNode) [][]int {
	var res [][]int
	if root == nil {
		return res
	}
	// 创建一个q队列,每次将一层的节点都入队
	// 第一次将根节点入队
	q := []*TreeNode{root}
	// 不停的入队一层的节点
	for i := 0; len(q) > 0; i++ {
		// 新开一层
		res = append(res, []int{})
		// p队列用来将当前层所有节点的孩子节点入队,即入队下一层的元素
		var p []*TreeNode
		// 遍历当前层节点
		for j := 0; j < len(q); j++ {
			// 将当前层的所有节点添加到res数组中
			node := q[j]
			res[i] = append(res[i], node.Val)
			// 入队下一层节点
			if node.Left != nil {
				p = append(p, node.Left)
			}
			if node.Right != nil {
				p = append(p, node.Right)
			}
		}
		// 遍历下一层
		q = p
	}
	return res
}

递归实现:

func levelOrder(root *TreeNode) [][]int {
	var res [][]int
	var dfs func(node *TreeNode, depth int)
	dfs = func(node *TreeNode, depth int) {
		// 递归出口
		if node == nil {
			return
		}
		// 初始化新的一层的储存空间
		if len(res) == depth {
			res = append(res, []int{})
		}
		// 将第depth层的节点添加到res中
		res[depth] = append(res[depth], node.Val)
		if node.Left != nil {
			dfs(node.Left, depth + 1)
		}
		if node.Right != nil {
			dfs(node.Right, depth + 1)
		}
	}
	dfs(root, 0)
	return res
}

q105 从前序与中序遍历序列构造二叉树


题目传送门


func buildTree(preorder []int, inorder []int) *TreeNode {
	if len(preorder) < 1 || len(inorder) < 1 {
		return nil
	}
	// 寻找中序数组中的根节点下标
	root := findRoot(preorder[0], inorder)
	// 递归构建二叉树
	treeNode := &TreeNode{
		Val:   preorder[0],
		// 左子树就是前序数组的[1:root+1], 中序数组的[0:root]
		Left:  buildTree(preorder[1:root+1], inorder[0:root]),
		// 右子树就是前序数组的[root+1:], 中序数组的[root+1:]
		Right: buildTree(preorder[root+1:], inorder[root+1:]),
	}
	return treeNode
}

// 根据根节点的值寻找根节点在中序数组中的下标
func findRoot(value int, arr []int) int {
	for i, v := range arr {
		if v == value {
			return i
		}
	}
	return -1
}

q106 从中序与后序遍历序列构造二叉树


题目传送门


func buildTree(inorder []int, postorder []int) *TreeNode {
	if len(inorder) < 1 || len(postorder) < 1 {
		return nil
	}
	// 根节点就是后续数组的最后一位
	root := findRoot(inorder, postorder[len(postorder) - 1])
	return &TreeNode{
		Val:   postorder[len(postorder) - 1],
		// 左子树就是中序数组的inorder[:root], 后续数组的postorder[:root]
		Left:  buildTree(inorder[:root], postorder[:root]),
		// 右子树就是中序数组的inorder[root+1:],后续数组的postorder[root:len(postorder) - 1]
		Right: buildTree(inorder[root+1:], postorder[root:len(postorder) - 1]),
	}
}

func findRoot(arr []int, target int) int {
	for i, v := range arr {
		if v == target {
			return i
		}
	}
	return -1
}

q110 平衡二叉树


题目传送门


题解

求二叉树的深度加上判断左右节点是否平衡即可。

func isBalanced(root *TreeNode) bool {
	if root == nil {
		return true
	}
	return abs(dfs(root.Left) - dfs(root.Right)) <= 1 && isBalanced(root.Left) && isBalanced(root.Right)
}

func dfs(t *TreeNode) int {
	if t == nil {
		return 0
	}
	lChild := dfs(t.Left)
	rChild := dfs(t.Right)
	return 1 + max(lChild, rChild)
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

q114 二叉树展开为链表


题目传送门


题解

方法一:使用前序遍历的思想

首先创建一个TreeNode的指针数组res,然后使用前序遍历的顺序递归遍历二叉树,然后将遍历到的每一个节点存到res数组中,最后通过res数组来构建题目要求的二叉树即可。

func flatten(root *TreeNode) {
	res := Preorder(root)
	for i := 1; i < len(res); i++ {
		pre, cur := res[i - 1], res[i]
		pre.Left, pre.Right = nil, cur
	}
}
func Preorder(root *TreeNode) (res []*TreeNode) {
	if root != nil {
		res = append(res, root)
		res = append(res, Preorder(root.Left)...)
		res = append(res, Preorder(root.Right)...)
	}
	return
}

方法二:不使用额外的空间

首先判断当前节点有无左子树,如果有,就找到左子树中的最后一个节点,按照前序遍历的顺序,左子树中的最后一个节点就是右子树的前驱节点。这个时候我们将右子树直接移到这个前驱节点的后面,接着将整个左子树移到右子树的位置,再将左子树的位置置为nil,最后继续往右子树遍历。

func flatten1(root *TreeNode) {
	cur := root
	for cur != nil {
		// 首先判断当前节点的左子树是否为空
		if cur.Left != nil {
			next := cur.Left
			// 寻找左子树中的最后一个节点
			// 按照前序遍历的规律,这个节点就是右子树的前驱节点
			pre := cur.Left
			for pre.Right != nil {
				pre = pre.Right
			}
			pre.Right = cur.Right
			cur.Left, cur.Right = nil, next
		}
		cur = cur.Right
	}
}

q124 二叉树中的最大路径和


题目传送门


题解

首先我们定义一个概念,一个节点的最大贡献值:指的是以这个节点为根节点,包含的到叶子节点的最大值.
所以root节点的最大路径和其实就是root节点的值 + 左子树的最大贡献值 + 右子树的最大贡献值.

func maxPathSum(root *TreeNode) int {
	maxSum := math.MinInt32
	var maxPrice func(node *TreeNode) int
	maxPrice = func(node *TreeNode) int {
		if node == nil {
			return 0
		}
		// 计算左子树的最大贡献值
		// 与0作比较是为了防止累加到负数
		leftPrice := max(maxPrice(node.Left), 0)
		// 计算右子树的最大贡献值
		rightPrice := max(maxPrice(node.Right), 0)
		// 加上当前节点的路径和
		currPrice := node.Val + leftPrice + rightPrice
		// 更新最大路径和
		maxSum = max(maxSum, currPrice)
		// 返回当前节点的最大贡献值
		return node.Val + max(leftPrice, rightPrice)
	}
	// 从根节点开始递归
	maxPrice(root)
	return maxSum
}
func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

q144 二叉树的前序遍历


题目传送门


题解

func preorderTraversal(root *TreeNode) []int {
	var res []int
	if root == nil {
		return res
	}
	var dfs func(root *TreeNode, res *[]int)
	dfs = func(root *TreeNode, res *[]int) {
		*res = append(*res, root.Val)
		if root.Left != nil {
			dfs(root.Left, res)
		}
		if root.Right != nil {
			dfs(root.Right, res)
		}
	}
	dfs(root, &res)
	return res
}

q145 二叉树的后序遍历


题目传送门


题解

func postorderTraversal(root *TreeNode) []int {
	var res []int
	if root == nil {
		return res
	}
	var dfs func(root *TreeNode, res *[]int)
	dfs = func(root *TreeNode, res *[]int) {
		
		if root.Left != nil {
			dfs(root.Left, res)
		}
		if root.Right != nil {
			dfs(root.Right, res)
		}
    *res = append(*res, root.Val)
	}
	dfs(root, &res)
	return res
}

q297 二叉树的序列化与反序列化


题目传送门


题解

使用前序遍历来通过二叉树构建字符串.

type Codec struct {
}

func Constructor() (_ Codec) {
	return
}

// Serializes a tree to a single string.
func (this *Codec) serialize(root *TreeNode) string {
	sb := &strings.Builder{}
	var dfs func(node *TreeNode)
	dfs = func(node *TreeNode) {
		if node == nil {
			sb.WriteString("null,")
			return
		}
		sb.WriteString(strconv.Itoa(node.Val))
		sb.WriteString(",")
		dfs(node.Left)
		dfs(node.Right)
	}
	dfs(root)
	return sb.String()
}

// Deserializes your encoded data to tree.
func (this *Codec) deserialize(data string) *TreeNode {
	sp := strings.Split(data, ",")
	var build func() *TreeNode
	build = func() *TreeNode {
		if sp[0] == "null" {
			sp = sp[1:]
			return nil
		}
		val, _ := strconv.Atoi(sp[0])
		sp = sp[1:]
		return &TreeNode{val, build(), build()}
	}
	return build()
}

q543 二叉树的直径


题目传送门


题解

二叉树的直径其实就是左子树的最大深度+右子树的最大深度+根节点.

func diameterOfBinaryTree(root *TreeNode) int {
	maxLength := 0
	var measure func(root *TreeNode) int
	measure = func(root *TreeNode) int {
		if root == nil {
			return 0
		} else {
			lHeight := measure(root.Left)
			rHeight := measure(root.Right)
			maxLength = max(maxLength, lHeight+rHeight)
			return 1 + max(lHeight, rHeight)
		}
	}
	measure(root)
	return maxLength
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

q617 合并二叉树


题目传送门


题解

直接使用DFS同时遍历两棵树即可.

func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
	var dfs func(r1, r2 *TreeNode) *TreeNode
	dfs = func(r1, r2 *TreeNode) *TreeNode {
		if r1 == nil {
			return r2
		}
		if r2 == nil {
			return r1
		}
		r1.Val = r1.Val + r2.Val
		r1.Left = dfs(r1.Left, r2.Left)
		r1.Right = dfs(r1.Right, r2.Right)
		return r1
	}
	return dfs(root1, root2)
}

q1367 二叉树中的链表

题目传送门

题解

使用dfs求解即可。

func isSubPath(head *ListNode, root *TreeNode) bool {
	var dfs func(head *ListNode, root *TreeNode) bool
	dfs = func(head *ListNode, root *TreeNode) bool {
		// 链表已经全部匹配完全,成功
		if head == nil {
			return true
		}
		// 二叉树访问到了空节点,失败
		if root == nil {
			return false
		}
		// 当前二叉树的节点值与链表的节点值不相等
		if head.Val != root.Val {
			return false
		}
		// 继续往后递归
		return dfs(head.Next, root.Left) || dfs(head.Next, root.Right)
	}
	if root == nil {
		return false
	}
	return dfs(head, root) || isSubPath(head, root.Left) || isSubPath(head, root.Right)
}

剑指 Offer 07. 重建二叉树


题目传送门


题解

实际上是根据前序数组和中序数组来重建二叉树。

func buildTree(preorder []int, inorder []int) *TreeNode {
	if len(preorder) < 1 || len(inorder) < 1 {
		return nil
	}
	// 寻找根节点在中序数组中的下标
	root := findRootIndex(preorder[0], inorder)
	// 递归构造二叉树节点
	treeNode := &TreeNode{
		Val:   preorder[0],
		Left:  buildTree(preorder[1:root+1], inorder[:root]),
		Right: buildTree(preorder[root+1:], inorder[root+1:]),
	}
	return treeNode
}

func findRootIndex(target int, arr []int) int {
	for i := 0; i < len(arr); i++ {
		if arr[i] == target {
			return i
		}
	}
	return -1
}

剑指 Offer 26. 树的子结构


题目传送门


题解

首先分情况考虑:

  1. A与B的根节点重合,同时遍历A和B来判断,两棵树接下来的节点是否相同。
  2. A与B的根节点不重合,递归遍历A树的左子树和右子树,看看有没有和B树根节点重合的,只有重合了才能进一步去判断。

定义一个函数isContain用来判断在根节点重合的情况下,A是否包含B。

func isSubStructure(A *TreeNode, B *TreeNode) bool {
	// AB任何一个节点为空都不成立
	if A == nil || B == nil {
		return false
	}
	// 如果AB的根节点重复,直接判断A是否包含B
	if A.Val == B.Val && isContain(A, B) {
		return true
	} else {
		// 如果AB根节点不重复,判断A的子树是否包含B
		return isSubStructure(A.Left, B) || isSubStructure(A.Right, B)
	}
}

// 该函数是个递归函数,用来判断根节点重合的情况下B是否被A包含
func isContain(A, B *TreeNode) bool {
	// B遍历到了空,B必被A包含
	if B == nil {
		return true
	}
	// B不为空,A为空,B不被A包含
	if A == nil {
		return false
	}
	// AB两个节点的值一样,进一步比较他们的左右子树是否相等
	if A.Val == B.Val {
		return isContain(A.Left, B.Left) && isContain(A.Right, B.Right)
	} else {
		// AB两个节点的值不同,B必不被A包含
		return false
	}
}

剑指 Offer 27. 二叉树的镜像


题目传送门


题解

这道题其实就是翻转二叉树,首先我们需要使用先序遍历遍历二叉树,然后从叶子节点开始翻转二叉树,如果遍历到了空节点就直接返回nil。

func mirrorTree(root *TreeNode) *TreeNode {
	// 遍历到空节点,不用翻转,直接返回nil
	if root == nil {
		return nil
	} else {
		// 获取左子树翻转后的结果
		left := mirrorTree(root.Left)
		// 获取右子树翻转后的结果
		right := mirrorTree(root.Right)
		// 翻转左右子树
		root.Left, root.Right = right, left
	}
	return root
}

剑指 Offer 28. 对称的二叉树


题目传送门


题解

一个二叉树对称,它和它的镜像完全相同。创建一个递归函数,来比较二叉树和它的镜像是否相等。

步骤:

  1. 两个节点都为空,说明相等,返回true。
  2. 一个节点为空另一个节点不为空,必然不相等,返回false。
  3. 比较两个节点的节点值是否相等,如果相等,再进一步比较r1的左子树和r2的右子树,以及r1的右子树和r2的左子树。
func isSymmetric(root *TreeNode) bool {
	var check func(r1, r2 *TreeNode) bool
	check = func(r1, r2 *TreeNode) bool {
		// 两个节点都遍历到了空,说明相等
		if r1 == nil && r2 == nil {
			return true
		}
		// 一个节点为空另一个节点不为空,必然不相等
		if r1 == nil || r2 == nil {
			return false
		}
		// 两个节点值相等,然后做镜像对比
		return r1.Val == r2.Val && check(r1.Left, r2.Right) && check(r1.Right, r2.Left)
	}
	return check(root, root)
}

剑指 Offer 32 - I. 从上到下打印二叉树


题目传送门


题解

这道题其实就是二叉树的层次遍历,应该使用BFS来求解。

步骤:

  1. 判断root是否为nil。
  2. 定义一个q := make([]*TreeNode, 0),是一个用于bfs遍历的队列。
  3. 循环遍历q数组,将当前遍历到的节点值放到res数组中,然后判断当前节点的子孩子是否为nil,如果不为nil,放入q数组中。
func levelOrder(root *TreeNode) (res []int) {
	if root == nil {
		return
	}
	// 定义用于bfs的队列
	q := make([]*TreeNode, 0)
	q = append(q, root)
	for i := 0; i < len(q); i++ {
		node := q[i]
		res = append(res, node.Val)
		if node.Left != nil {
			q = append(q, node.Left)
		}
		if node.Right != nil {
			q = append(q, node.Right)
		}
	}
	return
}

剑指 Offer 32 - II. 从上到下打印二叉树 II


题目传送门


题解

这道题使用BFS,关键点是创建两个临时切片q和p,都是*TreeNode类型,q用于遍历当前层的节点,p用于备份当前层节点的子节点,一层遍历结束之后,就将q = p,开始遍历下一层。

func levelOrder1(root *TreeNode) (res [][]int) {
	if root == nil {
		return
	}
	q := []*TreeNode{root}
	for i := 0; len(q) > 0; i++ {
		// 创建新的一层
		res = append(res, []int{})
		var p []*TreeNode
		for j := 0; j < len(q); j++ {
			// 将当前节点值加入res数组中
			res[i] = append(res[i], q[j].Val)
			// 备份当前节点的孩子节点
			if q[j].Left != nil {
				p = append(p, q[j].Left)
			}
			if q[j].Right != nil {
				p = append(p, q[j].Right)
			}
		}
		q = p
	}
	return
}

剑指 Offer 32 - III. 从上到下打印二叉树 III


题目传送门


题解

这道题的解法与上一道是差不多的,不同的是这道题当遍历到奇数层的时候,应该把res数组对应的层翻转一下。

func levelOrder(root *TreeNode) (res [][]int) {
	if root == nil {
		return
	}
	q := []*TreeNode{root}
	for i := 0; len(q) > 0; i++ {
		// 创建新的一层
		res = append(res, []int{})
		// 创建用于备份当前层孩子节点的p切片
		var p []*TreeNode
		for j := 0; j < len(q); j++ {
			// 将当前节点值加入到res数组中
			res[i] = append(res[i], q[j].Val)
			// 开始备份当前节点的子节点
			if q[j].Left != nil {
				p = append(p, q[j].Left)
			}
			if q[j].Right != nil {
				p = append(p, q[j].Right)
			}
		}
		q = p
		// 如果是奇数层,需要翻转那一层的数组
		if i%2 != 0 {
			reverse(res[i])
		}
	}
	return
}

func reverse(arr []int) {
	for i, n := 0, len(arr); i < n/2; i++ {
		arr[i], arr[n-i-1] = arr[n-i-1], arr[i]
	}
}

剑指 Offer 33. 二叉搜索树的后序遍历序列


题目传送门


题解

二叉树后序遍历的序列,最后一个元素是元素的根节点,然后数组的左边部分是左子树,右边部分是右子树,其中,左子树部分的元素都比根节点小,右子树部分的元素都比根节点大。

步骤:

  1. 序列中的元素数量小于2,肯定是后续遍历的序列,返回true。
  2. index是左右子树边界的下标。
  3. rootValue用于记录根节点的值。
  4. 循环遍历序列。
  5. 当出现第一个大于根节点的值时,这个下标就是左右子树边界的下标。
  6. 如果右子树中出现了小于根节点的值,就返回fasle。
  7. 循环结束,继续递归左右子树。
func verifyPostorder(postorder []int) bool {
	// 如果序列中的元素数量小于2,那么肯定是后续遍历的序列
	if len(postorder) < 2 {
		return true
	}
	// index用于区分左右子树(下标)
	index := len(postorder) - 1
	// 用于记录根节点的值
	rootValue := postorder[index]

	for k, v := range postorder {
		// 当出现第一个大于根节点的值时,这个值往后全是右子树的值
		if index == len(postorder)-1 && v > rootValue {
			index = k
		}
		// 如果右子树中出现了小于根节点的值,就返回fasle
		if index != len(postorder)-1 && v < rootValue {
			return false
		}
	}
	// 继续递归左右子树
	return verifyPostorder(postorder[:index]) && verifyPostorder(postorder[index:len(postorder)-1])
}

剑指 Offer 34. 二叉树中和为某一值的路径


题目传送门


题解

我们从根节点开始,枚举每一条从根节点到叶子节点的路径,中间用一个临时切片path保存某一条路径上的节点值。

步骤:

  1. 定义切片path,创建dfs函数。
  2. 判断node节点是否为nil。
  3. 将target减去当前节点值。
  4. 将当前节点值添加到path数组中。
  5. defer剔除path数组的最后一个值回溯。
  6. 判断是否到达叶子节点,且累加和刚好等于target,其中path切片是用于累加的中间变量,构造结果的时候要创建一个新的切片。
  7. 递归遍历当前节点的左子树和右子树。
func pathSum(root *TreeNode, target int) (res [][]int) {
	var path []int
	var dfs func(node *TreeNode, left int)
	dfs = func(node *TreeNode, left int) {
		if node == nil {
			return
		}
		left -= node.Val
		path = append(path, node.Val)
		// 回溯
		defer func() {
			path = path[:len(path)-1]
		}()
		// 判断是否到达叶子节点,且累加和刚好等于target
		if node.Left == nil && node.Right == nil && left == 0 {
			// path切片是用于累加的中间变量,构造结果的时候要创建一个新的切片
			res = append(res, append([]int(nil), path...))
			return
		}
		dfs(node.Left, left)
		dfs(node.Right, left)
	}
	dfs(root, target)
	return
}

剑指 Offer 54. 二叉搜索树的第k大节点


题目传送门


题解

这道题要求寻找第k大的元素,也就是从最大的元素开始递减查找,因为二叉树中的较大元素都在右子树,所以我们使用中序遍历+优先遍历右子树的方式来进行遍历。

func kthLargest(root *TreeNode, k int) (res int) {
	var dfs func(node *TreeNode)
	dfs = func(node *TreeNode) {
		if node == nil {
			return
		}
		// 先遍历右子树
		dfs(node.Right)
		k--
		if k == 0 {
			res = node.Val
			return
		}
		dfs(node.Left)
	}
	dfs(root)
	return
}

剑指 Offer 55 - I. 二叉树的深度


题目传送门


题解

递归从叶子节点开始统计,在递归的每一层,左子树高就返回左子树高度+1,右子树高就返回右子树高度+1。

func maxDepth(root *TreeNode) int {
	var dfs func(node *TreeNode) int
	dfs = func(node *TreeNode) int {
		if node == nil {
			return 0
		} else {
			lson := dfs(node.Left)
			rson := dfs(node.Right)
			return max(lson, rson) + 1
		}
	}
	return dfs(root)
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

剑指 Offer 55 - II. 平衡二叉树


题目传送门


题解

首先定义一个求二叉树高度的函数。具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,首先计算左右子树的高度,如果左右子树的高度差是否不超过 1,再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。这是一个自顶向下的递归的过程。

func isBalanced(root *TreeNode) bool {
	if root == nil {
		return true
	}
	// 计算左右子树的高度是否符合要求
	// 然后再递归判断左右子树的高度是否平衡
	return abs(dfs(root.Left)-dfs(root.Right)) <= 1 && isBalanced(root.Left) && isBalanced(root.Right)
}

// 求二叉树的高度
func dfs(node *TreeNode) int {
	if node == nil {
		return 0
	} else {
		lson := dfs(node.Left)
		rson := dfs(node.Right)
		return max(lson, rson) + 1
	}
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func abs(n int) int {
	if n < 0 {
		return -n
	} else {
		return n
	}
}

剑指 Offer 55 - II. 平衡二叉树


题目传送门


题解

存在三种情况:

  1. root为p,q中的一个,这个时候公共祖先为root。
  2. p和q分别在root的左右子树上,这个公共祖先也为root。
  3. p和q只在左子树或者右子树上,这个时候就需要递归遍历左子树或者右子树来找。
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
	if root == nil {
		return nil
	}
	// root为p或q中的一个
	if root == p || root == q {
		return root
	}
	// 递归遍历左子树和右子树
	left := lowestCommonAncestor(root.Left, p, q)
	right := lowestCommonAncestor(root.Right, p, q)
	// 如果分别在左右子树中,公共祖先就是root
	if left != nil && right != nil {
		return root
		// 都分布在左子树中
	} else if left != nil {
		return left
		// 都分布在右子树中
	} else {
		return right
	}
}

代码中如果p,q全都分布在左子树或者右子树上就直接返回left或right,这个时候能不能保证是最近公共祖先呢?其实是可以的,因为递归遍历以后,最终return是从叶子节点开始return的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值