【前端算法系列】动态规划

斐波那契数列和爬楼梯问题

两者区别在于:dp[0]边界的处理,斐波那契dp[0]=0,爬楼梯问题中dp[0]=1

// 关于:% (1e9 +7)对质数取模的话,能尽可能地避免模数相同的数之间具备公因数,来达到减少冲突的目的

// 傻递归 f(n) = f(n-1) + f(n-2), f(1)=1, f(0)=0
const fib=(n)=>{
	if(n<=0) return 0 // 爬楼梯可以改为 1
	if(n==1) return 1
	return fib(n) = fib(i-1) + fib(i-2)
}

/* 优化1:记忆存储(记忆化搜索,自顶向下解决问题)
在缓存的时候,需要用到f(4)再去想办法缓存f(4)的值,需要用到f(3)再去想办法缓存f(3)的值 </br>
当我要用它的时候再去检查它有没有被缓存,如果没有的话,就计算缓存
*/
const fib = function(n) {
  if(n<=1) return n // 把0和1两种状态都处理了
  const cache = []
  cache[0]=0  // 爬楼梯问题可以改为1
  cache[1]=1

  /* 检查创建的cacahe里面有没有momoize里要找的数,
      如果有就直接return里面的值,如果没有就再用f(n-1) + f(n-2)
  */
  function momeize(num){
      if(cache[num]!==undefined){
          return cache[num]
      }
      cache[num] =( momeize(num-1)+momeize(num-2)) % (1e9 +7)
      return cache[num] 
  }
  return momeize(n)
}

// 优化2:把递归转化成顺推形式 O(n)
const fib=(n)=>{
	if(n<=1) return n 
	let cache= [0, 1] // 爬楼梯可以改为 [1, 1]
	for(let i=2;i<=n;i++){
		 cache[i] = (cache[i - 1] + cache[i - 2]) % (1e9 +7);
	}
	return cache[n]
}

// 优化3:降维度, 空间复杂度的优化(降维变成了两个变量)
const fib = function(n) {
    if(n<=1) return n 
    let prev2 = 0 // 倒数第二个数    爬楼梯问题可以改为1
    let prev1 = 1 // 倒数第一个数
    let result = 0  
    for(let i=2;i<=n;i++){
        // result等于倒数第一个数+倒数第二个数
        result = prev1 + prev2
        // 让prev1、prev2都往前进一个值
        prev2 = prev1 // 旧的prev2等于新的prev1
        prev1 = result // 旧的prev1等于新的result, 即下一个result=上一个result+prve1
    }
    return result
}

198.打家劫舍

// 一维dp
dp[i]的状态定义:从0到i个房子最多能打劫多少钱,i可能被打劫也可能没 ==> nums[i]
dp[i]表示前i个房间的最大金额,nums[i]表示第i个房间的金额
dp[i] = max(dp[i-1], dp[i-2]+nums[i])  // 第3个房屋 = f(3-1)第2个房屋钱,f(3-2)第1个房屋钱 + 第3个房屋的钱

// 二维dp
dp[i][0]表示第i天没有偷房子  0没偷 1偷了
dp[i][0] = max(dp[i-1][0], dp[i-1][1]) // 当天偷了/没偷 取最大值
dp[i][1] = dp[i-1][0] + nums[i] // 昨天没偷,nums[i]表示今天偷的金额
/** 时间复杂度:O(n) for循环
 *  空间复杂度:O(n) 用了数组,数组也是线性增长(还可以降维度优化)
 */
var rob = function(nums) {
    if(nums.length === 0) return 0;
    const dp= [0, nums[0]] // nums[0] 为第一个房屋存放的金额
    for(let i=2;i<=nums.length;i+=1){
        // nums[i-1] 数组下标从0开始,想求第一个房间金额就传0进去,求第二个金额传1进去
        dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i-1])  
    }
    return dp[nums.length]
}

零钱兑换题

硬币都是无限的,所以嵌套循环

1. 零钱兑换(求最少硬币个数)

不同面额硬币coins=[1, 2, 5],总金额amount 11
11 = 5+5+1 ;结果输出3

可以分解为求单个dp[i]的最少硬币个数

状态转移:
f(x) 1≤x ≤s(总金额)
f(11) = min{f(10), f(9), f(6)} +1
// f(10)\f(9)\(f6) 表示凑成10元、9元、6元分别的硬币最少个数
// 1 表示(10+1=9+2=6+5)只需要1枚1的硬币、1枚2的硬币、1枚5的硬币

dp[i] = min{ dp[i-1], dp[i-2], dp[i-5] } +1 
// dp[i] 组成金额i的最少硬币个数
var coinChange = function(coins, amount) {
    const dp=[]  // 也可以直接把dp定义成[12, 12, .....],就不需要每次初始化为正无穷
    dp[0] = 0
    for(let i=1;i<=amount;i++){
        dp[i] = amount+1 // 每次进来都初始化为正无穷,便于后续取最小值  
        for(let j=0; j<coins.length; j++){
            if(i - coins[j]<0) continue // 假设硬币面值20比11大,跳出
            dp[i] = Math.min(dp[i], dp[i-coins[j]]+1 )
        }
    }
    // console.log(dp) 每个金额对应的组成金额的最小次数 [0, 1, 1, 2, 2, 1, 2, 2, 3, 3, 2, 3]
    return dp[amount] > amount ? -1:dp[amount]
}

零钱兑换2(求方法种类)

零钱兑换 II:每种面额的硬币有无限个,比如总额为5,面额coins=[1, 2, 5]
5=5;5=2+2+1;5=2+1+1+1;5=1+1+1+1+1;结果输出4

可以分解为求单个dp[i]的方法种类

分析: amount = 7  coins = [1, 3]  自上而下递推
凑成7   7-1= 6   // 动态转移为求6
			6-1=5  // 动态转移为求5
				5-1=4
				5-3=2
			6-3=33-1=23-3=07-3=4    // 动态转移为求4
		  	4-1=3  
		  	4-3=1
所以dp[7]种类 = dp[6]种类+dp[4]种类
dp[n] += dp[n-coins[i]]

条件,如果conis里面的比amount大,那我就不管
let amount = 5, coins = [1, 2, 5]
var change = function(amount, coins) {
	var dp = new Array(amount + 1).fill(0) // [0, 0, 0, 0, 0, 0] 表示取0、1、2、3、4、5等金额对应的多少种方式
	dp[0] = 1
	for (let i = 0; i < coins.length; i++) { // 循环coins[i]的每一种情况
	  // for(let j=1; j<amount; i++){ if(j>=coins[i]){// 才执行...}}  // 比如coins[i]为20,j等于5,就不管,即j要大于coins
	  for (let j = coins[i]; j < amount + 1; j++) {  // 优化成这种,让j必须等于大于coins[i]
	      dp[j] += dp[j - coins[i]]  // 3 - 1, 3-2
	    }
	}
	// console.log(dp) // [1, 1, 2, 2, 3, 4] 金额为5时有4种情况,为4有3种情况
	return dp[amount]
}
console.log(change(amount, coins))

股票问题

121 (只有一次买卖)
1)记录最小值
2)遍历,让当天值-最小值 = 收益,然后更新记录最大收益
 [7,1,5,3,6,4] ,比如用Math,min找出最小值1,再遍历让每个值减1取最大收益(但是前面值比1大的不能减1122 可以买和买无数次(即今天买,明天卖,后天买,大后天卖)
 [7,1,5,3,6,4]  在7处买,1处卖;5处买,3处卖;6处买,4处卖,即(7-1)+(5-3)+(6-4),
 每次买卖值再加起来,但是要判断前面的值比我小就不买(比如 15),前面的值比我还大我就不卖(比如 71123188 最多只能交易k次
dp[i] 表示第i天的最大收益,i天有三个状态:不动、买入、卖出;k表示交易多少次;0当天没股票,1当天有股票
// 结果是0,即今天没有股票 = 前一天不动,前一天卖出,所以+prices[i]
dp[i][k][0] = Math.max(dp[i-1][k][0],dp[i-1][k][1] + prices[i]) 
// 结果是1,即今天有股票 = 前一天不动,前一天买入,所以-prices[i]
dp[i][k][1] = Math.max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])

 
309 冷冻期    
状态: [买入, 卖出, 冷冻期, 买入, 卖出]

714 手续费
买入-卖出 就产生一次手续费,所以买入-手续费或者卖出-手续费都可以
// 找出最小值,遍历所有值-最小值,得到最大利润
var maxProfit = function(prices) {
    const len = prices.length
    let min = prices[0]
    let maxprofit = 0
    for(let i=0;i<len;i++){
        min = Math.min(min, prices[i])
        maxprofit = Math.max(maxprofit, prices[i]-min)
    }
    return maxprofit
};
/** 时间复杂度:O(n) for循环
 *  空间复杂度:O(1) 没有线性增长,是常量
 */
// 定义变量做累加,如果今天比昨天高就昨天买入,今天卖,累加利润
var maxProfit = function(prices) {
    let profit = 0
    for(let i=1;i<prices.length; i+=1){
        // 如果今天比昨天高,就昨天买,今天卖
        if(prices[i]>prices[i-1]){ 
            profit += prices[i]-prices[i-1]
        }
    }
    return profit
};
/* 每一个都有三种状态:不动、买、卖
	dp[i][k][0] i表示第几天、k表示第几笔交易、0表示没股票、1表示有股票
	定义一个三维数组,dp[i][k][0]
*/
var maxProfit = function(prices) {
    let n = prices.length;
    if(n == 0){
        return 0;
    }
    let maxTime = 2;
    let dp = Array.from(new Array(n),() => new Array(maxTime+1));
    for(let i = 0;i < n;i++){
        for(let r = 0;r <= maxTime;r++){
            dp[i][r] = new Array(2).fill(0);
        }
    }
    console.log(dp)
    for(let i = 0;i < n;i++){
        for(let k = maxTime;k >= 1;k--){
        	// 处理边界,0表示没股票,1表示有股票,即第0天卖出
            if(i == 0){ 
                dp[i][k][0] = 0;
                dp[i][k][1] = -prices[i];
                continue;
            }
            // 结果是0,即没有股票 = 前一天不动,前一天卖出,所以+prices[i]
            dp[i][k][0] = Math.max(dp[i-1][k][0],dp[i-1][k][1] + prices[i]) 
            // 结果是1,即有股票 = 前一天不动,前一天买入,所以-prices[i]
            dp[i][k][1] = Math.max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])
        }
    }
    return dp[n-1][maxTime][0]
}
console.log(maxProfit([3,3,5,0,0,3,1,4]))
var maxProfit = function(k, prices) {
    let n = prices.length;
    let maxTime = k;
    if(n == 0){
        return 0;
    }
    let dp = Array.from(new Array(n),() => new Array(maxTime+1));
    for(let i = 0;i < n;i++){
        for(let r = 0;r <= maxTime;r++){
            dp[i][r] = new Array(2);
        }
    }
    for(let i = 0;i < n;i++){
        for(let k = maxTime;k >= 1;k--){
            if(i == 0){
                dp[i][k][0] = 0;
                dp[i][k][1] = - prices[i];
                continue;
            }
            dp[i][k][0] = Math.max(dp[i-1][k][0],dp[i-1][k][1] + prices[i]);
            dp[i][k][1] = Math.max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i]);
        }
    }
    return dp[n-1][maxTime][0];
}
var maxProfit = function(prices) {
    let n = prices.length;
    if(n == 0){
        return 0;
    }
    let dp = Array.from(new Array(n),() => new Array(2));
    for(var i = 0;i < n;i++){
        if(i == 0){
            dp[0][0] = 0;
            dp[0][1] = -prices[i];
            continue;
        }else if(i == 1){
            dp[1][0] = Math.max(dp[0][0],dp[0][1]+prices[i]);
            dp[1][1] = Math.max(dp[0][1], - prices[i]);
            continue;
        }
        dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i]);
        dp[i][1] = Math.max(dp[i-1][1],dp[i-2][0] - prices[i]);
    }
    return dp[n-1][0]
}
var maxProfit = function(prices, fee) {
    let n = prices.length;
    if(n == 0){
        return 0;
    }
    let dp = Array.from(new Array(n),() => new Array(2));
    for(let i = 0;i < n;i++){
        if(i == 0){
            dp[0][0] = Math.max(0,-Infinity+prices[0]);
            dp[0][1] = Math.max(-Infinity,0 - prices[0] - fee);
            continue;
        }
        dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i]);
        dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0] - prices[i] - fee);
    }
    return dp[n-1][0]
}

减维版的股票系列
参考

路径问题

合并子问题 = 向下子问题 + 向右子问题 ⇒ 向上子问题 + 向左子问题
dp[i][j] = dp[i-1][j] + dp [i][j-1]
var uniquePaths = function(m, n) {
    const dp = Array.from({length:m}, ()=> new Array(n).fill(0))
    // 边界条件是第一行和第一列都是1
    for (let i = 0; i < m; i++) dp[i][0] = 1 
    for (let j = 0; j < n; j++) dp[0][j] = 1 
    for(let i=1;i<m;i++){
        for(let j=1;j<n;j++){
            dp[i][j] = dp[i-1][j] + dp[i][j-1]
        }
    }
    return dp[m-1][n-1]
}
  • 63. 不同路径 II
    每一步状态:有障碍物 为1(不能通过)和 没障碍物 为0(能通过)
var uniquePathsWithObstacles = function(obstacleGrid) {
    if(obstacleGrid[0][0]==1) return 0 // 出发点就被障碍堵住
    const m = obstacleGrid.length
    const n = obstacleGrid[0].length
    const dp = Array.from({length:m},()=>new Array(n))
    // 处理边界条件
    dp[0][0] = 1       // 终点就是出发点
    for(let i=1;i<m;i++) dp[i][0] = obstacleGrid[i][0] == 1 || dp[i - 1][0] == 0 ? 0 : 1
    for (let i = 1; i < n; i++) dp[0][i] = obstacleGrid[0][i] == 1 || dp[0][i - 1] == 0 ? 0 : 1
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i - 1][j] + dp[i][j - 1]
        }
    }
  return dp[m - 1][n - 1]
}
[ 
	[2],   =>  [11] // 2选9和10里的9
   [3,4],  =>  [9, 10] // 3选7和6里的6;4选6和10里的6
  [6,5,7], =>  [7, 6, 10] // 6选4和1里的1;5选1和8里的1;7选8和3里的3
 [4,1,8,3] =>  选最小1
]
分析:2只能加3或加4,选最小当然是3,到3的时候的时候选最小的5,到5的时候选最小的1
所以自顶向下:2+3+5+1 = 11

可以逆向从底层往上推,最底层选最小的
定义状态:dp[i][j] 到ij的最小值
状态方程:dp[i][j] = min(dp[i+1][j], dp[i+1][j+1])+t[i][j] // 它两之间的最小值再加上我自己本身(比如2)
var minimumTotal = function(triangle) {
    let dp=triangle
    //最后一行已经初始化了,所以从第二行开始
    for(let i = dp.length-2;i>=0 ;i--){ // 从倒数第二行[6, 5, 7]开始 
        for(let j=0;j<dp[i].length;j++){ // 遍历当前行的数值个数6、5、6,3个
            dp[i][j]= Math.min(dp[i+1][j], dp[i+1][j+1]) + dp[i][j] // 选下一个的最小的,比如6选[4,1]里最小的1,再加上自己6
        } 
    }
    return dp[0][0]
}


// 压缩内存空间
var minimumTotal = function(triangle) {
    var dp = new Array(triangle.length+1).fill(0) // 初始化为[0, 0, 0, 0, 0]
    for(var i = triangle.length-1;i >= 0;i--){ 
        for(var j = 0;j < triangle[i].length;j++){
          /* dp[4,1,8,3,0]中,triangle[i][j]为[6,5,7]   4和1比较 + 6   1和8比较 +5  8和3比较+7  => 新dp[7, 6, 10, 3, 0]
             dp[7, 6, 10, 3, 0]中,triangle[i][j]为[3, 4]   7和6比较 + 3   6和10比较 +4   => 新dp[9, 10, 10, 3, 0]
             dp[9, 10, 10, 3, 0]中,triangle[i][j]为[2]  9和10比较 +2 [11, 10, 10, 3, 0] 最少路径dp[0]
          */
            dp[j] = Math.min(dp[j],dp[j+1]) + triangle[i][j] 
        }
    }
    return dp[0]
}

剪绳问题

var cuttingRope = function(n) {
	let dp = Array.from({length: n+1},()=>1);
	if (n == 2) return 1
	if (n == 3) return 2  // 分为1*2 或者1*1*1断,取最大乘积为2
	dp[1] = 1
	dp[2] = 2
	dp[3] = 3 // 分为3断
	for (let k = 1; k <= n; k++) {
	    for (let i = 1; i <= k / 2; i++) { // k/2是为了裁剪小段,复用缓存
	        dp[k] = Math.max(dp[k], dp[i] * dp[k - i]) // f(8) = max(f(8), f(2)f(6)) 
	    }
	}
	return dp[n]
}
var cuttingRope = function(n) {
	if (n == 2)  return 1
	if (n == 3)   return 2
	if (n == 4)   return 4
	let res=1
	while(n>4){
	    res*=3 // 每段剪为3,所以这里累积乘以3
	    res=res%1000000007
	    n-=3
	}
	return res*n%1000000007
}

最大最小

// 因为有复数有,又是取最大和(累加自身)
// 所以遍历每个数值,跟0作比较取最大数值,再累加当前的数值
var maxSubArray = function(nums) {
    if(nums == null) return 0
    let dp=[]
    dp[0]=nums[0]
    let max=dp[0]
    for(let i=1;i<nums.length;i++){
        dp[i] = Math.max(0, dp[i-1]) + nums[i] // 找出前面大于0的,和自身累加
        /* dp[0] = max(0, dp[0-1]) + nums[0] 0+-2    dp=[-2] 
            dp[1] = max(0, dp[0]) + nums[1]  0+1   dp=[-2, 1]
            dp[2] = max(0, dp[1]) + nums[2]  1+-3   dp = [-2, 1, -2]
            dp[3] = max(0, dp[2]) + nums[3]  0+4  dp = [-2, 1, -2, 4]
            dp[4] = max(0, dp[3]) + nums[4]  4+-1  dp = [-2, 1, -2, 4, 3]
            // ...
        */
        max = Math.max(dp[i], max)
        console.log(dp)
    }
    return max
}
// 若遇到了一个比当前元素小的值就累加1
var lengthOfLIS = function(nums) {
    const len = nums.length
    if(len==0) return 0
    let maxLen=1
    let dp = new Array(len).fill(1)
    for(let i=1;i<len;i++) {
       for(let j=0;j<i;j++) {  
          if(nums[i]>nums[j]) {
              dp[i] = Math.max(dp[i], dp[j] + 1)  
          }
      }
        if(dp[i]>maxLen){
            maxLen = dp[i]
        }
    }
    return maxLen
};
var longestCommonSubsequence = function(text1, text2) {
   let m=text1.length
   let n=text2.length
   const dp=Array.from({length:m+1},()=>new Array(n+1).fill(0))
    dp[0][0]=0
    for(let i=1;i<=m;i++){
        for(let j=1;j<=n;j++){
            if(text1[i-1]==text2[j-1]){ // 它们是从下标0开始,所以要-1
                dp[i][j] = dp[i-1][j-1]+1 // 如果有,就找到前一个值+1
            }else{
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) // 没有就找到上一个或者前一个中最大的
            }
        }
    }
    return dp[m][n]
}
/* 状态转移方:dp[i]=dp[i−1]∗nums[i]
    注意:负负得正
   dp[i][0]: 从第0项到第i项范围内的子数组的最小乘积
   dp[i][1]: 从第0项到第i项范围内的子数组的最大乘积

    对于以i项为末尾项的子数组能产生的最小积,它有3种情况:
    不和别人乘,就它自己
    自己是负数,希望乘上前面的最大积
    自己是正数,希望乘上前面的最小积

    dp[i][0]取三种情况中的最小值:
    dp[i][0] = min(dp[i−1][0]∗nums[i], dp[i−1][1]∗nums[i], nums[i])

    类似的,dp[i][1]值取三种情况中的最大值
    dp[i][1] = max(dp[i−1][0]∗nums[i], dp[i−1][1]∗nums[i], nums[i])
 */
var maxProduct = function(nums) {
    let res = nums[0]
    let preMin = nums[0]
    let preMax = nums[0]
    let temp1=0, temp2 = 0
    for(let i=1;i<nums.length;i++){
        temp1 = preMin*nums[i]
        temp2 = preMax*nums[i]
        preMin = Math.min(temp1, temp2, nums[i])
        preMax = Math.max(temp1, temp2, nums[i])
        res = Math.max(preMax, res)
    }
    return res
}

参考

//dp[i][j] = max{dp[i - 1][j], dp[i][j - 1]} + data[i][j]
// 自顶向下
var maxValue = function(grid) {
    const m = grid.length;
    if(!m) return 0
    const n = grid[0].length;
    let dp = Array.from({length:m+1}, () => new Array(n+1).fill(0));
    for(let i = 0; i < m; i ++) {
        for(let j = 0; j < n; j ++) {
            dp[i + 1][j + 1] = Math.max(dp[i][j + 1], dp[i + 1][j]) + grid[i][j]  // 取右边或者下边最大值,再加上当前自身
        }
    }
    return dp[m][n];
}


// 自底向上
var maxValue = function(grid) {
    const m = grid.length;
    if(!m) return 0
    const n = grid[0].length;
    let dp = Array.from({length:m+1}, () => new Array(n+1).fill(0));
    for(let i=0; i<m; i++){
        for(let j=0; j<n; j++){
            if (i==0 && j==0){
                dp[i][j] = grid[i][j];
            }else if(i==0){
                dp[i][j] = dp[i][j-1] + grid[i][j]
            }else if(j==0){
                dp[i][j] = dp[i-1][j] + grid[i][j]
            }else{
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j]  // 取右边或者下边最大值,再加上当前自身
            }
        }
    }
    return dp[m-1][n-1];
}

背包问题

有 n 件物品,物品体积用一个名为 w 的数组存起来,物品的价值用一个名为 value 的数组存起来;每件物品的体积用 w[i] 来表示,每件物品的价值用 value[i] 来表示。现在有一个容量为 c 的背包,问你如何选取物品放入背包,才能使得背包内的物品总价值最大? 注意:每种物品都只有1件

for(let i=1;i<=n;i++) {
    for(let v=w[i]; v<=c;v++) {
      // 状态:拿到的物品没有放到包里,有放到包里
      dp[i][v] = Math.max(dp[i-1][v], dp[i-1][v-w[i]]+value[i]) 
    }
}

// 降维:i只是一个索引,可以省略dp[i][v]更新维dp[v],把用不到的i去掉
// dp[v]是当前背包的最大价值
dp[v] = Math.max(dp[v], dp[v-w[i]] + value[i]) 
// n物品的个数 c容量 w物品的重量 value价值数组
function knapsack(n, c, w, value) {
    // dp是动态规划的状态保存数组
    const dp = (new Array(c+1)).fill(0)  
    // res 用来记录所有组合方案中的最大值
    let res = -Infinity
    for(let i=1;i<=n;i++) {
        for(let v=c;v>=w[i];v--) {
            dp[v] = Math.max(dp[v], dp[v-w[i]] + value[i])  // 写出状态转移方程:取到没放入背包,去到的有放入背包
            if(dp[v] > res) {
                res = dp[v]  // 即时更新最大值
            }
        }
    }
    return res
}  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值