LeeCode 解题报告:2097. Valid Arrangement of Pairs

零: 为什么要写这个blog

这题是一个hard的题目,这题我自己的算法复杂度是很吓人的,因为涉及到遍历,我使用的DFS深度优先搜索算法,然后我的depth和node相关,原文要求100000个node,我的DFS在600个节点的时候已经Time out了,我的具体做法大家可以看我的问题初探

但是这个题目的正解也是dfs,但是复杂度居然是O(E)的,我觉得里面有一个非常牛而且很巧妙的思考,所以我写下了这个blog

注意:这个算法的核心是Hierholzer算法,如果你已经非常了解,可以跳过这个blog,如果你也跟我一样刚刚接触,我有一个自己对于这个算法逻辑的总结,大家可以一起讨论下

一、题目分析

附上原文题目的链接

1. 问题描述

  • 给定一组数对pairs,每个数对[a,b]表示一条从a到b的有向边
  • 要求重新排列这些数对,使得每个数对的第二个数字等于下一个数对的第一个数字
  • 返回任意一个合法的重排序列
    注意题目确认了一定存在一个这样的解

2. 问题特点

输入:pairs = [[5,1],[4,5],[11,9],[9,4]]
输出:[[11,9],[9,4],[4,5],[5,1]]

二、解题思路

原文是一个数列的问题,但是既然考虑到前后的逻辑关系,我们将其转变成图论的问题,每一个pair是一条边,对应两个Node。

1. 关键观察

  • 每个数对代表一条有向边
  • 要求找到一条通路,走完所有边恰好一次
  • 符合欧拉通路的特征(欧拉通路:一笔遍历了所有的边)

2.初步思考

每个节点入度是可以到这个点的有向边的数量,出度是从这个节点出去的边的数量,既然题目报保证了是欧拉通路,那么我们可以简单的保证最多只有两个点的出度不等于入度,则很容易找到起点,要么是唯一的起点,要么是一个欧拉环路,可以随便找一个起点

理论上来说,我们可以认为是一个主环带着无数个子环,以及子环的子环,正确的欧拉路径的逻辑是,每次都先走更深层次的子环,但是问题是我们怎么知道一个点的出入边哪个是主的路径,哪个是子的路径
举个例子:节点如下
[[1,2],[2,3],[3,4],[4,1],[3,5],[5,6],[6,7],[7,3]]
显然1是入口,主环是1-2-3-4-1,3-5-6-7-3是子环,正确的路径就是走到3的时候去走567的路径,但是问题是你其实并不知道5和4到底谁是子环

3.算法初探

理论上来说,欧拉可以认为是从任意一个节点拉通的任意一条环,所以我开始了我的算法思路

  • 首先从起点出发,如果有分支那么我们选一个节点加入,然后DFS,如果不可以,则回溯。结果:肉眼可见的超时了
  • 然后经过分析发现超时是因为长度太长,depth有点高,那么我们把没有分支的节点不进行递归,而是直接加入,这里涉及到很多的出度入度的计算和回溯来保证,依然超时了,因为很多节点加入然后回溯依然很耗时
  • 我发现可以把所有的出度和入度为1的节点,全部缩减成一条边,那么大大的减少了回溯的层数,每次把一个节点加入到路径后,我们就遍历当前出度和入度为1的边,然后将他们和start和end合并,然后保存整条路径,依然需要回溯,解决了600的问题,显然无法解决的10w节点的问题
  • 到这个时候,python代码超过200行了,已经我已经很难往下思考了,去看了很多人的解法包括欧拉的一些算法当然涉及到Hierholzer算法,为了给大家一点我感受到的震撼,我直接放这个代码的最核心的代码

4. Hierholzer算法核心

def dfs(node):
    while graph[node]:
        next_node = graph[node].pop()
        dfs(next_node)
    stack.append(node)

node是我们选出来的起点节点,graph放的是这个node的邻接边,如果你初看这个算法也是一个DFS的算法平平无奇,你仔细看:这个DFS是不回溯的,换句话说,这个算的复杂度是O(E),E是边的数量

如果你已经对Hierholzer算法和熟悉了,那这篇文章你可以跳过了。

5. 算法框架

整体的算法框架和实现

class Solution:
    def validArrangement(self, pairs: List[List[int]]) -> List[List[int]]:
        # 1. 建图
        graph = defaultdict(list)
        in_degree = defaultdict(int)
        out_degree = defaultdict(int)
        
        # 2. 找起点
        for u, v in pairs:
            graph[u].append(v)
            out_degree[u] += 1
            in_degree[v] += 1
        
        start = pairs[0][0]
        for node in graph:
            if out_degree[node] - in_degree[node] == 1:
                start = node
                break
        
        # 3. Hierholzer算法
        stack = []
        def dfs(node):
            while graph[node]:
                next_node = graph[node].pop()
                dfs(next_node)
            stack.append(node)
        
        dfs(start)
        #回溯stack
        stack = stack[::-1]
        
        # 4. 构建结果
        return [[stack[i], stack[i+1]] for i in range(len(stack)-1)]

三、算法详解

如果你对这个算法其实也还有一点疑问,我们可以一起来讨论这个事情。

初看这个算法我是很懵的,我一直在问,凭什么?凭什么我这么努力的做回溯的时候,这个算法就可以直接访问下去了。

这个算法有很多个说法
1:所有的路径都是对的,因为是环
2:所有的路径都会自我调整,因为是环
显然这个依然没法解释我的疑问,如何选择优先选择子环的问题,虽然都是环,怎么保证选到主环在后呢?

然后从某一个角度我想通了这个算法的核心的原理,再发一下代码

def dfs(node):
    while graph[node]:
        next_node = graph[node].pop()
        dfs(next_node)
    stack.append(node)

注意代码逻辑while(只要有边),就dfs到下一层。
问题来了这个DFS,什么时候会返回?算法的核心逻辑在于:因为dfs的压栈,真正最开始能返回的一定是出口的那条路径(主环)。换句话说:栈头一定是出口,经过最后的反转,出口一定在队尾。

一个反问:凭什么最后的是主环?逻辑就是,因为先走子环最终会回到自己这个节点,而这个时候,主环依然还有路径可以继续往下dfs,就不会执行到while下面的stack.append()操作

这个逻辑进一步展开,还是刚刚的例子:主环是1-2-3-4-1,3-5-6-7-3是子环

DFS,1,2到3的时候,如果是走到5的分支(子环),子环一圈到3,是不会执行stack操作的,因为转了一圈回到3是依然有别的路径可以走的4到1,dfs深度是9,所以压栈的顺序是[1,4,3,7,6,5,3,2,1]。
但是如果在3优先选择了4,必然从end节点1返回到3,此时最大depth深度是5,栈的压栈(返回)路径是[1,4],但是注意这个时候是不会stack.append()加3,因为3的while还在执行,会走向5,显然dfs顺序5,6,7,3,depth =7,到最后的3的时候,已经没有其他的node可以去访问了,所以会立即返回,注意此时的stack已经是[1,4]了,所以变成[1,4,3,7,6,5]此时返回到3的node的while,4,5都已经访问完毕,开始回溯自己[1,4,3,7,6,5,3,2.1]

但是这么拆开理解是比较散的,我依然用我自己的逻辑去理解:
因为dfs的压栈,真正最开始能返回的一定是出口的那条路径(主环),翻转后就是最后执行的路径
如同我们一开始说的,一切时候先走子环,反过来理解就是:一切时候后走主环

1. 算法正确性

  • 每条边只访问一次
  • 子路径完整性:走到某节点的所有相关路径会被完整处理

四、复杂度分析

1. 时间复杂度

  • O(E),E为边数
  • 每条边只被访问一次
  • 图的构建和结果构建都是O(E),最多加上O(V),节点数量,本质是最多是2*E

2. 空间复杂度

  • O(E) 用于存储图
  • 递归栈深度通常远小于E
  • 因为子路径会形成环而不是长链
01-01 08:00:54.715794 1386 1386 F libc : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 1386 (camerahalserver), pid 1386 (camerahalserver) 01-01 08:00:55.727947 1845 1845 F DEBUG : Process name is /vendor/bin/hw/camerahalserver, uid is 1047, not key_process 01-01 08:00:55.728074 1845 1845 F DEBUG : keyProcess: 0 01-01 08:00:55.728117 1845 1845 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 01-01 08:00:55.728149 1845 1845 F DEBUG : Build fingerprint: 'realme/RMX5020/RE6089:16/BP2A.250605.015/V.R4T2.4b11a9d-2ab80a8:user/release-keys' 01-01 08:00:55.728176 1845 1845 F DEBUG : Revision: '0' 01-01 08:00:55.728200 1845 1845 F DEBUG : ABI: 'arm64' 01-01 08:00:55.728224 1845 1845 F DEBUG : Timestamp: 2009-12-31 19:00:54.868891310-0500 01-01 08:00:55.728253 1845 1845 F DEBUG : Process uptime: 0s 01-01 08:00:55.728283 1845 1845 F DEBUG : Cmdline: /vendor/bin/hw/camerahalserver 01-01 08:00:55.728311 1845 1845 F DEBUG : pid: 1386, tid: 1386, name: camerahalserver >>> /vendor/bin/hw/camerahalserver <<< 01-01 08:00:55.728335 1845 1845 F DEBUG : uid: 1047 01-01 08:00:55.728365 1845 1845 F DEBUG : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE) 01-01 08:00:55.728395 1845 1845 F DEBUG : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- 01-01 08:00:55.728424 1845 1845 F DEBUG : Abort message: 'constructStaticMetadata_v1(611):no tag: MTK_SENSOR_INFO_WHITE_LEVEL in deviceID(0) (constructStaticMetadata_v1){#611:vendor/mediatek/proprietary/hardware/mtkcam/utils/metastore/metadataprovider/constructStaticMetadata.cpp}' 01-01 08:00:55.728519 1845 1845 F DEBUG : x0 0000000000000000 x1 000000000000056a x2 0000000000000006 x3 0000007fc62f1970 01-01 08:00:55.728614 1845 1845 F DEBUG : x4 0000000000000010 x5 0000000000000010 x6 0000000000000010 x7 7f7f7f7f7f7f7f7f 01-01 08:00:55.728690 1845 1845 F DEBUG : x8 00000000000000f0 x9 aaf486de20f70110 x10 0000000000000001 x11 000000727f76e4e0 01-01 08:00:55.728756 1845 1845 F DEBUG : x12 0000000000000028 x13 0000000000000005 x14 0000000000004dd8 x15 000000002aec070c 01-01 08:00:55.728828 1845 1845 F DEBUG : x16 000000727f7df828 x17 000000727f7c3580 x18 0000007297d9c000 x19 000000000000056a 01-01 08:00:55.728919 1845 1845 F DEBUG : x20 000000000000056a x21 00000000ffffffff x22 b4000071af664d80 x23 0000000000000000 01-01 08:00:55.728990 1845 1845 F DEBUG : x24 0000007296d12d80 x25 0000007263520420 x26 0000007296d12d80 x27 b4000071ae4af63c 01-01 08:00:55.729075 1845 1845 F DEBUG : x28 0000000000000000 x29 0000007fc62f19f0 01-01 08:00:55.729120 1845 1845 F DEBUG : lr 000000727f7576e8 sp 0000007fc62f1970 pc 000000727f75770c pst 0000000000000000 01-01 08:00:55.729186 1845 1845 F DEBUG : 21 total frames 01-01 08:00:55.729211 1845 1845 F DEBUG : backtrace: 01-01 08:00:55.729265 1845 1845 F DEBUG : #00 pc 000000000008b70c /apex/com.android.runtime/lib64/bionic/libc.so (abort+156) (BuildId: 566f0fd827eec167190e923129b20248) 01-01 08:00:55.729306 1845 1845 F DEBUG : #01 pc 000000000000d11c /system/lib64/liblog.so (__android_log_default_aborter+12) (BuildId: bedc00477f245f5ce04a59ae69da4ee1) 01-01 08:00:55.729354 1845 1845 F DEBUG : #02 pc 000000000000d270 /system/lib64/liblog.so (__android_log_assert+288) (BuildId: bedc00477f245f5ce04a59ae69da4ee1) 01-01 08:00:55.729413 1845 1845 F DEBUG : #03 pc 00000000000e8bb4 /vendor/lib64/libmtkcam_metastore.so (android::NSMetadataProvider::MetadataProvider::constructStaticMetadata_v1(android::sp<NSCam::IMetadataConverter>, camera_metadata*&, NSCam::IMetadata&)+3220) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729464 1845 1845 F DEBUG : #04 pc 00000000000f7488 /vendor/lib64/libmtkcam_metastore.so (android::NSMetadataProvider::MetadataProvider::onCreate()+152) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729515 1845 1845 F DEBUG : #05 pc 00000000000f5dc4 /vendor/lib64/libmtkcam_metastore.so (NSCam::IMetadataProvider::create(int)+68) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729596 1845 1845 F DEBUG : #06 pc 000000000012dfc8 /vendor/lib64/libmtkcam_metastore.so (android::NSTemplateRequest::TemplateRequest::updateData(int, NSCam::IMetadata&)+72) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729638 1845 1845 F DEBUG : #07 pc 0000000000127634 /vendor/lib64/libmtkcam_metastore.so (android::NSTemplateRequest::TemplateRequest::constructRequestMetadata(int, camera_metadata*&, NSCam::IMetadata&)+852) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729678 1845 1845 F DEBUG : #08 pc 000000000012f1a8 /vendor/lib64/libmtkcam_metastore.so (android::NSTemplateRequest::TemplateRequest::onCreate(int)+616) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729726 1845 1845 F DEBUG : #09 pc 000000000012f698 /vendor/lib64/libmtkcam_metastore.so (NSCam::ITemplateRequest::getInstance(int)+200) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729763 1845 1845 F DEBUG : #10 pc 0000000000130770 /vendor/lib64/libmtkcam_metastore.so (NSCam::NSTemplateRequestManager::constructRequestMetadata(int)+48) (BuildId: 1eeb0a00a1f9ecdc854a2d2b44632286) 01-01 08:00:55.729799 1845 1845 F DEBUG : #11 pc 000000000005bdc4 /vendor/lib64/libmtkcam.logicalmodule.so (updateRequestMetadata()+68) (BuildId: c55076c33a907f23ae502b494baf6c47) 01-01 08:00:55.729836 1845 1845 F DEBUG : #12 pc 0000000000044c5c /vendor/lib64/libmtkcam.logicalmodule.so (NSCam::HalLogicalDeviceList::createDeviceMap()+3260) (BuildId: c55076c33a907f23ae502b494baf6c47) 01-01 08:00:55.729871 1845 1845 F DEBUG : #13 pc 0000000000048538 /vendor/lib64/libmtkcam.logicalmodule.so (NSCam::HalLogicalDeviceList::searchDevices()+296) (BuildId: c55076c33a907f23ae502b494baf6c47) 01-01 08:00:55.730075 1845 1845 F DEBUG : #14 pc 0000000000014a78 /vendor/lib64/hw/android.hardware.camera.provider@2.6-impl-mediatek.so (NSCam::CameraDeviceManagerImpl::onEnumerateDevicesLocked()+104) (BuildId: e7e14152a0d20022586953512f201ec5) 01-01 08:00:55.730120 1845 1845 F DEBUG : #15 pc 0000000000025ad0 /vendor/lib64/hw/android.hardware.camera.provider@2.6-impl-mediatek.so (NSCam::CameraDeviceManagerBase::enumerateDevicesLocked()+80) (BuildId: e7e14152a0d20022586953512f201ec5) 01-01 08:00:55.730174 1845 1845 F DEBUG : #16 pc 000000000001e408 /vendor/lib64/hw/android.hardware.camera.provider@2.6-impl-mediatek.so (NSCam::CameraDeviceManagerBase::initialize()+984) (BuildId: e7e14152a0d20022586953512f201ec5) 01-01 08:00:55.730215 1845 1845 F DEBUG : #17 pc 0000000000010ab4 /vendor/lib64/libmtkcam_hal_aidl_provider.so (mcam::aidl::AidlCameraProvider::AidlCameraProvider(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&)+596) (BuildId: a91443ed20389f4ba1d32fd0c4a93e3d) 01-01 08:00:55.730252 1845 1845 F DEBUG : #18 pc 0000000000010dbc /vendor/lib64/libmtkcam_hal_aidl_provider.so (mcam::aidl::AidlCameraProvider::create(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&)+60) (BuildId: a91443ed20389f4ba1d32fd0c4a93e3d) 01-01 08:00:55.730332 1845 1845 F DEBUG : #19 pc 0000000000004990 /vendor/bin/hw/camerahalserver (main+208) (BuildId: 407282559e868e30917bdcc16088a2cf) 01-01 08:00:55.730407 1845 1845 F DEBUG : #20 pc 00000000000848f8 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+120) (BuildId: 566f0fd827eec167190e923129b20248) 01-01 08:00:56.907572 1969 1969 I libpq_sec: mtk-brm-commit-id:a834e9e1cb1e39337de9cde0088583e62f9d140b 01-01 08:00:56.907609 1969 1969 I libpq_sec: mtk-brm-change-id:I7d0fcddf6e326c905b4094dd3c5ed9a9c9312a86 01-01 08:00:56.907614 1969 1969 I libpq_sec: mtk-brm-merge-id:none 01-01 08:00:57.442015 1969 1969 F libc : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 1969 (camerahalserver), pid 1969 (camerahalserver) 01-01 08:00:58.094526 1993 1993 F DEBUG : Process name is /vendor/bin/hw/camerahalserver, uid is 1047, not key_process 01-01 08:00:58.094612 1993 1993 F DEBUG : keyProcess: 0
10-16
/** * 文书删除时的后台Handler处理 */ const noticeRepo = reqlib("/patient/infra/repositories/surgerynotice/request-repo.js"); const patientDomain = reqlib("/patient/domain/patient/patient"); const dateformat = require("dateformat"); const aneTypeDomain = reqlib("/portal/domain/dictionary/anes-type"); const recordRepo = new (reqlib("/anesthesia/infra/repositories/record/record-repo"))(); const requestDomain = reqlib("/patient/domain/surgerynotice/request"); const surgeryExtend = new (reqlib("/patient/domain/patient/surgery-extend-info"))(); const container = reqlib("/container"); exports.beforeHandler = () => { return {}; }; exports.afterHandler = async (docData, data, t, _) => { let [patSurgeryInfo, noticeInfo] = await Promise.all([patientDomain.getfinalSurgeryByPatientId(data.patientId, t), noticeRepo.getNoticeByPatientId(data.patientId)]); let aneRecord = await recordRepo.getAneRecordByPaientId(docData.patientId); let orExtendInfoObj = patSurgeryInfo[0].orExtendInfo || JSON.parse(JSON.stringify(surgeryExtend.getORExtendInfo())); let saveData = { id: patSurgeryInfo[0].id, surgeryEntryTime: null, surgeryLeaveTime: null, surgeryDate: dateformat(noticeInfo[0].surgeryDate, "yyyy-mm-dd HH:MM:ss"), }; let operationName = [], operationObjs = [], surgeryNames = "", surgeryLevels = "", surgeryIncisions = "", surgeryList = [], surgeryLevelArr = [], anesTypeCode = noticeInfo[0].anesTypeCode; if (aneRecord && aneRecord.length > 0) { let extendAttribute = aneRecord[0].extendAttribute; if (extendAttribute && extendAttribute.finalSurgery && extendAttribute.finalSurgery.length > 0) { operationName = extendAttribute.finalSurgery; } anesTypeCode = aneRecord[0].anesTypeCode; } // 还原最终表手术名称 if (operationName.length == 0) { operationName = await recordRepo.getPatientSurgery(docData.patientId); } if (operationName && operationName.length > 0) { operationName.forEach((o) => { operationObjs.push({ SURGERY_CODE: o.code, ICD_CODE: o.icdCode, SURGERY_NAME: o.name, SURGERY_DOCTOR_ID: null, DESCRIPTION: o.description, LEVEL_CODE: o.levelCode, POSITION_CODE: o.positionCode, DIRECTION_CODE: o.directionCode, INCISION_TYPE_CODE: o.incisionCode || null, }); surgeryList.push({ surgeryCode: o.code, icdCode: o.icdCode, surgeryName: o.name, surgeryDoctorId: null, description: o.description, levelCode: o.levelCode, levelName: o.levelName, positionCode: o.positionCode, incisionTypeCode: o.incisionTypeCode || null, incisionTypeName: o.incisionTypeName || null, surgeryDoctorName: null, }); if (surgeryNames && surgeryNames.length > 0) surgeryNames += ";" + o.name; else surgeryNames = o.name; if (surgeryLevels && surgeryLevels.length > 0) surgeryLevels += ";" + o.levelName; else surgeryLevels = o.levelName; if (o.incisionName) { if (surgeryIncisions && surgeryIncisions.length > 0) surgeryIncisions += ";" + o.incisionName; else surgeryIncisions = o.incisionName; } if (o.levelCode) { surgeryLevelArr.push(o.levelName); } }); } // 还原最终表麻醉方式 let anesType = await aneTypeDomain.getAneTypeByCode(anesTypeCode); saveData.anesTypeName = anesType.name; if (anesType.parentCode && anesType.parentCode !== "e4ed3c5675ee11e8b3eb60a44cce202b") { let anesTypeParent = await aneTypeDomain.getAneTypeByCode(anesType.parentCode); saveData.anesTypeParentCode = anesTypeParent.code; saveData.anesTypeParentName = anesTypeParent.name; } else { // 父麻醉节点不存在存自身 saveData.anesTypeParentCode = anesType.code; saveData.anesTypeParentName = anesType.name; } // 删除最终手术信息 await requestDomain.deleteFinalSurgery(patSurgeryInfo[0].id, t); if (operationObjs.length > 0) { // 新增最终手术信息 await patientDomain.saveFinalListInfo([], operationObjs, patSurgeryInfo[0].id, t); saveData.surgeryName = surgeryNames; saveData.surgeryLevel = surgeryLevels; saveData.surgeryIncision = surgeryIncisions; let maxSurgeryLevelStr = surgeryExtend.getMaxSurgeryLevelByNames(surgeryLevelArr) || null; orExtendInfoObj.surgeryList = surgeryList; orExtendInfoObj.surgeryLevel = maxSurgeryLevelStr; saveData.orExtendInfo = orExtendInfoObj; } await patientDomain.updateFinalSurgery(saveData, t); await patientDomain.updateFinalSurgeryState(patSurgeryInfo[0].id, t); return {}; }; function getArrPerson(patientId) { const { database } = container.cradle; let sql = ` SELECT OAU.USER_TYPE, OAU.USER_ID, SU.NAME AS USER_NAME FROM ope_arrangement_user OAU LEFT JOIN ope_arrangement OA ON OA.ID=OAU.ARRANGEMENT_ID LEFT JOIN sys_user SU ON SU.ID=OAU.USER_ID WHERE OA.PATIENT_ID = ? AND OA.VALID=1 `; return database.sequelize .query(sql, { replacements: [patientId], type: database.sequelize.QueryTypes.SELECT, }) .then((result) => { return database.jsonFormat(result); }) .catch((err) => { throw err; }); } 我这样有啥问题嘛
07-04
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值