动态规划

动态规划,真的是刚入门太难了…只能慢慢啃,慢慢悟【多刷题,多总结,多思考】推荐labuladong的动态规划详解
总结一下目前遇到的动态规划题目的主要特点:
1.计数:
eg:有多少种方式走到右下角
2.求最大最小值
eg:最长上升子序列长度
3.求存在性
eg:能不能选出k个数使得和是sum

1.斐波那契数列(easy)

题目:写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1

思路:采用动态规划,由题可知,
状态定义F(n)为数列第n项
状态转移方程为F(N) = F(N - 1) + F(N - 2)

def Fibonacci(self, n):
        if n <2:
            return n
        dp = [0 for _ in range(n+1)]
        dp[1] = 1
        for i in range(2,n+1):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[-1] % 1000000007

2.爬楼梯(easy)

题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1).1 阶 + 1 阶 ; 2) 2阶

思路:这个问题可以被分解为一些包含最优子结构的子问题
爬上 1 阶台阶:有 1 种方式
爬上 2 阶台阶:有 1+1 和 2 两种方式
爬上 3 阶台阶:只能从第 2 阶或者第 1 阶 到达第 3 阶
爬上 第 i 阶可以由以下两种方法得到:
在第 (i-1)阶后向上爬1阶。
在第 (i-2) 阶后向上爬 2 阶。
所以到达第 i 阶的方法总数就是到第 (i-1)阶和第 (i-2) 阶的方法之和。
1.状态定义: dp[i]表示能到达第 i 阶的方法总数:
2.状态转移方程: dp[i]=dp[i−1]+dp[i−2]
3.返回值: 第 i 阶的状态dp[ i ]

 def climbStairs(self, n: int) -> int:
 		if n == 1:				#如果不加这句判断则会出错,因为当n=1时,dp[2]会越界
 			return 1
        dp = [n+1]*(n+1)  
        dp[1] = 1   			#第一层
        dp[2] = 2				#第二层
        for i in range(3,n+1):  #从第三层开始
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n]

3.最大子序和(easy)

题目:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

思路:
1.状态定义:dp[i] 表示以 nums[i] 结尾的连续子数组的最大和
dp[i-1] >= 0: dp[i] = dp[i-1]+nums[i]
dp[i-1] < 0, dp[i] = nums[i]
2.状态转移方程:
dp[i]=max(nums[i], dp[i−1]+nums[i])
3.返回值:求所有连续子数组和中的最大和
max(dp[0], dp[1], …, d[i-1], dp[i])

def maxSubArray(self, nums: List[int]) -> int:
        if len(nums) <1:
            return 0
        dp = [0] *len(nums)  # 初始化dp数组,dp[i]存储以nums[i]为结尾的子数组的和的最大值
        res = nums[0]		
        dp[0] = nums[0]
        for i in range(1,len(nums)):
            dp[i] = max(dp[i-1]+nums[i],nums[i]) # 更新dp[i]
            res = max(res,dp[i]) # #所有连续子数组的最大和中的最大值
        return res

4. 最长上升子序列(medium)

题目:给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

思路:
1.状态定义:
dp[i]的值代表以nums[ i ] 结尾的最长子序列长度。
2.状态转移方程:
设 j 从[0, i],考虑dp[ i ]更新时,j 遍历从[0, i]区间,求其最长子序列长度。
1) nums[i] >nums[j],分为两种情况:
a)nums[i]比前面的所有元素都小,那么dp[i]等于1(即它本身)
b)当nums[i] 前面存在比他小的元素nums[j],但可能不止一个,所以不是dp[j]+1,而是在遍历 j 时,每次都执行 dp[i] = max(dp[i], dp[j] +1)。
2) nums[i] <nums[j], 上升序列不成立,则跳过
因此状态转移方程为:
dp[i] = max(dp[i], dp[j] + 1) for j in [0, i)
3.返回值:dp列表中的最大值

def lengthOfLIS(self, nums: List[int]) -> int:
        if len(nums) <1:
            return 0
        dp = [1] * len(nums)  #dp所有元素置1,每个元素都至少可以单独成为子序列,此时长度都为1
        for i in range(len(nums)):
            for j in range(0, i):  #[0, i]区间求其最长子序列长度
                if nums[j] < nums[i]:
                    dp[i] = max(dp[j]+1,dp[i])
        return max(dp)

5.零钱兑换(medium)

题目:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

思路:采用动态规划自底向上的方法。
要使求 amount = 11 时最少硬币数(原问题),可以通过 amount = 10 的最少硬币数(子问题)来求解,只需要把子问题的答案加一(再选一枚面值为 1 的硬币)就是原问题的答案。也就是每次都求得当前状态所需最少的硬币数
状态转移方程为dp[i] = min (dp[i-coins[0]], dp[i-coins[1]]…)+1
eg,题目为例,F(amount)表示金额为amount 时,所需要的最少硬币数。当F(amount)<0.则忽略
当amount=0,F(0)=0【金额为0,不需要硬币】
当amount=1,需要最少硬币F(1)=1【min((F(1-1),F(1-2),F(1-5))+1】
当amount=2,需要最少硬币F(2)=1【min((F(2-1),F(2-2),F(2-5))+1】
以此类推,每次需要最少的硬币
当amount=11,需要最少硬币F(11)=1【min((F(11-1),F(11-2),F(11-5))+1】

def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [amount + 1] *(amount + 1)	#创建一个数组大小为 amount + 1,初始值也为 amount + 1【 就相当于初始化为正无穷,便于后续取最小值】
        dp[0] = 0						
        for i  in range(1, amount+1): 
            for j in range(len(coins)): #每一个dp[i]都选择遍历一遍coin,不断更新dp[i]
                if i >=coins[j]:		#子问题有解,则更新状态
                    dp[i] = min(dp[i], dp[i-coins[j]]+1)
        if dp[-1] == amount+1:
            return -1
        else:
            return dp[-1]

6.打家劫舍(easy)

今天的每日一题终于一看就想到用动态规划来做,倒吸一口凉气就做完了,cheer up !!

题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

思路:
1.状态定义:
dp[i]的值代表前 i 间房屋能偷窃到的最高总金额(最长不相邻的子序列长度)
2.状态转移方程:
求当前位置 i 房屋可盗窃的最大值,不相邻意思就是要么是 i-1 房屋可盗窃的最大值,要么就是 i-2 房屋可盗窃的最大值加上当前房屋的值,二者之间取最大值
dp[ i ] = max ( dp[ i-1 ], dp[ i-2 ] + num )
3.返回值: dp[ length ]
优化:在计算当前房屋的最高金额dp[ i ] 时,只计算dp[i-1]和 dp[i-2]。dp[i-3]之前的子问题实际上没有用到,可以使用两个变量保存两个子问题的结果,就可以依次计算出所有的子问题

1)
  def rob(self, nums: List[int]) -> int:
        length = len(nums)
        if length==0:
            return 0
        dp = [0] * (length+1)
        dp[0] = 0
        dp[1] = nums[0]		#第一间房屋偷窃的金额,如果采用dp[0]=nums[0],则需要再判断一次前两个房屋能偷窃的最高金额
        for i in range(2, length+1):
            dp[i] = max(dp[i-1], nums[i-1] + dp[i-2])
            # 第i间房屋对应的金额是nums[i-1]
        return dp[length]
 2)
 def rob(self, nums: List[int]) -> int:
        pre,cur = 0,0 
        #cur 表示 dp[i-1],pre 表示 dp[i-2]
        #dp[i] = max(dp[i-1], nums[i-1] + dp[i-2])
        for m  in nums:
            pre,cur = cur, max(cur, pre+m)
         
        return cur  #循环结束后,cur表示 dp[i],pre表示 dp[i-1]

7.打家劫舍 || (medium)

题目:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

思路:跟上一题的区别在于这题是首尾相连的环形,第一个房子和最后一个房子中只能选择一个偷窃。因此原来的问题转化为两个子问题:偷窃1 ~ n-1个房间的最大值以及偷窃 2~n 个房间的最大值。所以偷窃最大金额为以下两种情况中的最大值:max(s1,s2)
1)在不偷窃第一个房子的情况下,则把第一家置为0,最后一家无所谓,即 nums[1:]带入上题求得最大金额是s1
2)在不偷窃最后一个房子的情况下,则把最后一家置为0,即 nums[:n-1]带入上题求得最大金额是 s2
1.状态定义:
dp[i] 代表前 i 个房子在满足条件下的能偷窃到的最高金额
2.状态转移方程:
与上题一致求前 i 间房能偷取到的最高金额 dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])
3.返回值:dp[length],即所有房间的最大偷窃价值

 def rob(self, nums: List[int]) -> int:
        def myrob(nums):
            length = len(nums)
            if length == 0:
                return 0
            dp = [0] * (length+1)
            dp[0] = 0
            dp[1] = nums[0]
            for i in range(2,length+1):
                dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])
            return dp[length]
        if len(nums) ==1:
            return nums[0]
        return max(myrob(nums[:-1]),myrob(nums[1:]))

8.​ 打家劫舍 |||

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

思路:

class Solution:
    def rob(self, root: TreeNode) -> int:
        def robs(root):
            res = [0,0]
            if not root:
                return res
           
            left = robs(root.left)
            right = robs(root.right)
            res[1]+= root.val + left[0] + right[0] 								#表示节点被偷能拿到的最多的钱
            res[0] += max(left[0], left[1]) + max(right[0], right[1])	#表示节点没有被偷能拿到的最多的钱
            return res
        res = robs(root)
        return max(res[0], res[1])

9.通配符匹配

题目:给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘’ 的通配符匹配。
‘?’ 可以匹配任何单个字符。
'
’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
示例 1
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。

思路:
s和p匹配有三种情况:
1)当p[ j ]和s[ i ]都为小写字母且相同,p,s跳过
2)当p[ j ]为’?’,则p,s跳过
3)当p[ j ]为“*”,可以p保持不变,s跳过;也可以是p跳过,s不变

1.状态定义: dp[ i ][ j ]表示 s 的前 i 个字符和 p 的前 j 个字符是否匹配
2.状态转移方程:
第一和第二种情况合并:dp[i][j] = dp[i-1][j-1]
第三种:dp[i][j] = dp[i][j-1] | dp[i-1][j]
3.返回值:dp[m][n] 【m为s的长度,n为p的长度】
考虑边界条件,在状态转移方程中,由于 dp[i][j]对应着 s的前 i个字符和模式 p 的前 j 个字符,因此所有的 dp[0][j]和 dp[i][0] 都是边界条件,因为它们涉及到空字符串或者空模式的情况,这是我们在状态转移方程中没有考虑到的:
dp[0][0] = True ,即s 和p 均为空时,匹配成功;
dp[i][0] =False,即空模式无法匹配非空字符串;
dp[0][j]需要分情况讨论:因为星号才能匹配空字符串,所以只有当p 的前 j 个字符均为星号时,dp[0][j] 才为真。
因此在初始化时将所有状态设置为False

def isMatch(self, s: str, p: str) -> bool:
        m, n = len(s), len(p)
        dp = [[False] * (n + 1) for _ in range(m + 1)]
        dp[0][0] = True
        for i in range(1, n + 1):
            if p[i - 1] == '*':
                dp[0][i] = True
            else:
                break
        
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if p[j - 1] == '*':
                    dp[i][j] = dp[i][j - 1] | dp[i - 1][j]
                elif p[j - 1] == '?' or s[i - 1] == p[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1]
                
        return dp[m][n]

10.不同路径 || (medium)

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

思路:计数问题,当前的路径问题可由之前的路径确定,自然想到用动态规划来求解
1)状态定义dp[ i ][ j ] 为走到第i行j列的路径总数,机器人只能从下或者从右移动一部,因此走到dp[i][j]可以通过dp[i-1][j] 或者dp[i][j-1]走到。当[i,j]位置为障碍物,则走不到此位置。
2)状态转移方程可以归纳为
dp[ i ] [ j ] = dp[ i -1 ][ j ] +dp[ i ][ j-1] 【当前位置[i,j]走得通,走不通则为0】
3)返回值,末状态也就是走到最后的路径总数dp[-1][-1]
考虑边界值:首位置,左边界和上边界即dp的第一行和第一列。
首位置:当首位置无障碍时,则初始状态为1,有障碍时则达到不了后面任一位置故直接输出为0
第一行,dp[0][j] 表示在第j 列的状态只能由前一列决定dp[0][j] = dp[0][j-1]
第一列的情况也同理
优化:采用滚动数组的思想,将状态由二维数组压缩为一维,,逐行计算当前最新路径条数,并覆盖上一行对应的路径条数。采用动态规划只需要考虑当前状态怎么得到,即由前一状态决定还是前前一状态等有关,不需要考虑这两个状态之间怎么变化得到的。

1def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m = len(obstacleGrid)
        n= len(obstacleGrid[0])
        step= [ [0 for _ in range(n) ] for _ in range(m)]
        step[0][0] = 1 if obstacleGrid[0][0]==0 else 0  #首位置
        if step[0][0] == 0: 
            return 0
        for i in range(1,n):		   					#上边界					
            if obstacleGrid[0][i] != 1:
                step[0][i] = step[0][i-1]
        for i in range(1,m):							#左边界
            if obstacleGrid[i][0] !=1:
                step[i][0] = step[i-1][0]
        for i in range(1,m):
            for j in range(1,n):
                if obstacleGrid[i][j] == 0:
                    step[i][j] = step[i-1][j] + step[i][j-1]
        return step[-1][-1]
2def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m = len(obstacleGrid)
        n= len(obstacleGrid[0])
        step = [0]*n
        step[0] = 1 if obstacleGrid[0][0] ==0 else 0  #首位置
        for i in range(m):
            for j in range(n):
                if obstacleGrid[i][j] == 1: 		#当前位置为障碍物
                    step[j] = 0						#此路径行不通为0
                    continue
                if j-1>=0:							#无障碍物且非行首位置
                    step[j] = step[j] + step[j-1]
        return step[-1]


11.不同路径 (medium)

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径

思路:做完了上题,这题就好理解很多了
1)状态定义:dp[ i ][ j ]当前路径总数
2)状态转移方程:由当前位置的左边或者上边可以达到当前位置。dp[ i ] [ j ] = dp[ i -1 ][ j ] +dp[ i ][ j-1]
3)返回值:遍历完网格最后一个状态即右下角时的总路径和
考虑边界问题:第一行和第一列设置为1,因为其只有一种走向可以到达,要么是向右达到要么是向下到达目的位置。
优化:将状态由二维数组压缩为一维
1)状态定义为:dp[i],逐行计算当前最新路径条数
2)状态转移方程:dp[i] = dp[i] + dp[i-1] ,即由前一行和前一列的位置决定
3)返回值:dp[-1]
边界问题。直接初始化为1

1)
 def uniquePaths(self, m: int, n: int) -> int:
        dp = [ [0 for _ in range(n)] for _ in range(m)]
        for i in range(n):
            dp[0][i] = 1
        for i in range(m):
            dp[i][0]=1
        for i in range(1,m):
            for j in range(1,n):
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[-1][-1]
2)
def uniquePaths(self, m: int, n: int) -> int:
        dp = [1]*n
        for i in range(1,m):
            for j in range(1,n):
                dp[j] = dp[j] + dp[j-1]  #前一行和前一列
        return dp[-1]
        
#难以理解的话可看下面这种写法
 def uniquePaths(self, m: int, n: int) -> int:
        pre = [1] * n
        cur = [1] * n
        for i in range(1, m):
            for j in range(1, n):
                cur[j] = pre[j] + cur[j-1]#pre[j]此时表示前一列,cur[j-1]表示前一行
            pre = cur[:]
        return pre[-1]  #注意是pre,因为最后是pre状态更新为当前状态


11.最大正方形 (medium)

题目:在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
示例:
输入:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
输出: 4

思路:
1)定义状态dp[i][j] 表示以 (i, j)( 为右下角,且只包含 1的正方形的边长最大值
2)状态转移方程:dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1) +1 表示从该坐标的左方,上方,左上方中判断是否有0,如果有0 则说明构成的最大正方形边长为1。反之则说明能构成正方形。
3)返回值:用一个变量来更新面积最大值res = max(res,dp[i][j]**2),返回面积最大值res
考虑边界值:在第一行和第一列的,没有左上不满足构成边长大于1的正方形,故dp[i][j]=1

 def maximalSquare(self, matrix: List[List[str]]) -> int:
        res = 0
        row = len(matrix)
        col = len(matrix[0])
        if row==0 or col ==0:
            return 0
        dp = [ [0 for _ in range(col)] for _ in range(row)]
        for i in range(row):
            for j in range(col):
                if matrix[i][j] == '1':
                    if i==0 or j==0:
                        dp[i][j] = 1
                    else:
                        dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
                    res = max(res, (dp[i][j])**2)
                
        return res

12.地图分析 (medium)

题目:你现在手里有一份大小为 N x N 的「地图」(网格) grid,上面的每个「区域」(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋区域,这个海洋区域到离它最近的陆地区域的距离是最大的。
我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。
如果我们的地图上只有陆地或者海洋,请返回 -1

思路:
采用BFS的思想
1)将所有的陆地加入队列
2)每次从陆地的四周扩展到海洋,也就是从该位置的左右上下查找海洋,将遍历到的海洋位置加入队列中,并将其置为-1。每扩展一次,step就加一,直到不能扩展,也就是没有海洋了
3)返回step,因为第一次扩展时只是遍历原本陆地,因此多算了一次step,并考虑到如果全是陆地或者海洋则返回-1,直接将step初始化为-1

 def maxDistance(self, grid: List[List[int]]) -> int:
        from collections import deque
        length = len(grid)
        queue = deque()
        for i in range(len(grid)):  #将陆地加入队列中
            for j in range(len(grid[0])):
                if grid[i][j] == 1:
                    queue.append([i,j])
        
        step =-1
        if len(queue) == 0 or len(queue) == length ** 2: #全是0或者全是1
            return step
        while len(queue)>0:#每次遍历完陆地之后,重新遍历新增的陆地
            for _ in range(len(queue)): 
                node = queue.popleft()
                x = node[0]
                y = node[1]
                dx = [0,0,-1,1]
                dy = [-1,1,0,0]
                for i in range(4):
                    new_x = x+dx[i]
                    new_y = y + dy[i]
                    if new_x>=0 and new_x<length and new_y>=0 and new_y<length and grid[new_x][new_y]==0:   #如果还存在0
                        queue.append([new_x,new_y])  #遍历完的0变成1加入队列中     
                        grid[new_x][new_y] = -1		#将该遍历过的元素0变成-1
        
            step +=1
        return step
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值