算法汇总(长期更新)

       前端工作中很少用到算法,但是面试或多或少都会问到这方面,尤其最近在学习Java,如果要学全栈,那么算法就是必须要迈过去的一道坎.

开始总结掌握的算法吧家人们。。。

先来个简单的

整数反转

        核心思路如下:

   while(num!==0){
        result=result*10+num%10;
        num= (num/10)|0;
    }

1. 扁平化数组转换为树形结构

思路:

        递归  父级id

    let transArr=(arrs) =>{
        let res=[];
        let go=(val,arrs,Arr)=>{
            arrs.forEach(ele => {
                if(ele.parent_id == val){
                    Arr.push(ele);
                }
            });
            Arr.forEach(ele=>{
                ele.children=[];
                go(ele.id,arrs,ele.children);
            })
        }
        go(0,arrs,res);
        return res;s
    }
       console.log(transArr(menu_list)); 

2.扁平化对象转换为树形对象

    思路:

        指针,引用类型赋值操作的浅拷贝,递归

     let obj = {
            "a.b.c": 1,
            "a.d": 2,
            "e.f.g": 3
        }
        let TansObject=(obj)=>{
            let result = {};
            let cur;
            for (const key in obj) {
              let keyList = key.split(".");
              let val = obj[key];
              cur = result;
              for (let i = 0; i < keyList.length-1; i++) {  //这里遍历keyList 要少遍历一个节点,为了最后获取值
                    cur[keyList[i]] =  cur[keyList[i]] || {};
                    cur = cur[keyList[i]];  //在这里改变指向
              }
              cur[keyList[keyList.length-1]] = val;
            }
            return result;
        }
        console.log(TansObject(obj)); 

3.把上面的转换的对象再变回来~(字节面试题是上面那道,我自己研究了一下如何转换回来

思路:

        递归,初始值,实例判断

这道题有难度,要注意递归函数中传递的keys参数,在方法体内部判断时需要对keys的值进行判断(null or string)

        let tansObj = (obj) => {
            let result = {};
            let go = (object, keys) => {
                for (const key in object) {
                    if (object[key] instanceof Object) {
                        if (keys == null) {
                            go(object[key], key)
                        } else {
                            go(object[key], keys +"."+ key)
                        }
                    } else {
                        if(keys==null){
                            result[key] = object[key];
                        }else{
                            result[keys +"."+ key] = object[key];
                        }
                     
                    }
                }
            }
            go(obj, null);
            return result;
        }
        console.log(tansObj(obj2));

 4.最长无重复子串

        思路:

        处理字符串需要split为数组,在合适的时候使用while循环

        let str = "aabbsd";
        let getMaxStr=(str)=>{
            let tempArr=[];
            let maxLen = 0;
            let strArr= str.split("");
            strArr.forEach(ele => {
                while(tempArr.includes(ele)){
                    tempArr.shift();
                }
                tempArr.push(ele);
                maxLen = Math.max(maxLen,tempArr.length);
            });
            return maxLen;
        }
        console.log(getMaxStr(str));

5.二分查找

 思路:

        while循环中动态获取middle,根据目标值改变下一次循环开始和结束的索引

function search(list,item){
      count =1;//计数出现的次数 
      start  = 0;
      end  = list.length-1;
    while(start<=end){
          middle =Math.floor((start+end)/2);   //取中间下标
          guess = list[middle];  
        if(guess==item){
            return middle; //返回位置
        }
        if(guess>item){
            end = middle;
        }else{
            start = middle+1
        }
        count++;
    }
    return  "查不到";
}
let  result = search(list,4);

6.打家劫舍

         这个方法空间复杂度和时间复杂度几乎最优,重要的是无需递归

      let rob=(arrs)=>{
            if(arrs.length==1){return arrs}
            if(arrs.length==2){return Math.max(arrs[0],arrs[1])}
            let result =[];
            for (let i = 2; i < arrs.length; i++) {
               result[i] = Math.max(arrs[i]+arrs[i-2],arrs[i-1])
            }
            return result[arrs.length-1]
        }

7.最长递增子序列 

        给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

dp存放的是递增数据的长度

/**
 * @param {number[]} nums
 * @return {number}
 */
var findLengthOfLCIS = function(nums) {
    const dp = [1]

    for(let i=1;i<nums.length;i++){
        if(nums[i]>nums[i-1]){
            dp[i]=dp[i-1]+1
        }else{
            dp[i]=1
        }
    }

    return Math.max(...dp)
};

8. 买彩票的最佳时机

思路:

(这道题很难,巨难,难的一批!动态规划求解)

     /*
        第一天没买,收入为0
        第一天买了,收入--
        */
      dp[i][j][0] = 0;

      dp[i][j][1] = -prices[i];


        /*
            第i天没买,两种情况取最优
                    1.昨天也没买
                    2.昨天买了,但是卖出去了,收入++

            第i天买了,两种情况取最优
                    1.昨天也买了
                    2.昨天没买,今天第一次买,收入--
        */

      dp[i][j][0] = Math.max(
              dp[i - 1][j][0],
              dp[i - 1][j][1] + prices[i]
            );

      dp[i][j][1] = Math.max(
              dp[i - 1][j - 1][0] - prices[i],
              dp[i - 1][j][1]
            );
 var maxProfit = function (prices) {
        let len = prices.length;
        let k = 2;
        if (len == 0) {
          return;
        }
        let dp = Array.from(new Array(len), () =>
          new Array(k + 1).fill(0).map(() => new Array(2).fill(0))
        );
        for (let i = 0; i < len; i++) {
          for (let j = k; j > 0; j--) {
            if (i == 0) {
              dp[i][j][0] = 0;
              dp[i][j][1] = -prices[i];
              continue;
            }
            dp[i][j][0] = Math.max(
              dp[i - 1][j][0],
              dp[i - 1][j][1] + prices[i]
            );
            dp[i][j][1] = Math.max(
              dp[i - 1][j - 1][0] - prices[i],
              dp[i - 1][j][1]
            );
          }
        }
        return dp[len - 1][k][0];
      };
      maxProfit([3, 3, 5, 0, 0, 3, 1, 4]);

Z字形变换

思路:

第一行和最后一行按照 row*2-2 的索引进行递增

其余行按照 n+((row*2)-2)-1*i进行递增

n 代表当前行字符索引,i代表行数 ,row为总行数

 let TransStr=(str,row)=>{
      let result='';
      for (let i = 0; i < row; i++) {
        if(i==0){
          for (let k = 0; k < str.length; k+=(row*2)-2) {
            result+= str.charAt(k);
          }
        }else if(i=row-1){
          for (let j = row-1; j < str.length; j+=(row*2)-2) {
            result+= str.charAt(j);
          }
        }else{
          for (let k = i; k < str.length; k+=(row*2)-2) {
            result= result+str.charAt(k)+str.charAt(k+((row*2)-2)-2*i)
          }
        }
      }
      return result;
    }

盛最多水的容器

思路:

        比较左右两侧大小,小的乘以数组长度得到水的容积

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let start =0;
    let end=height.length-1;
    let max=0;
    while(start<end){
        max=Math.max(max,(end-start)*height[start]<height[end]?height[start++]:height[end--])
    }
    return max;
};

整数转换为罗马数字

        思路:  有一个由大到小的整数构成的数组以及对应的罗马字符数组,依次遍历数组的索引,根据索引找到对应数字,当num大于索引对应数字时,num-数字,res+字符

  var intToRoman = function (num) {
        let intArr = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
        let RomanArr = [
          "M",
          "CM",
          "D",
          "CD",
          "C",
          "XC",
          "L",
          "XL",
          "X",
          "IX",
          "V",
          "IV",
          "I",
        ];
        let index = 0;
        let res='';
        while(index<13){
          while (num>=intArr[index]) {
            res+=RomanArr[index];
            num-= intArr[index]
          }
          index++;
        }
        return res;
      };
       console.log(intToRoman(300)); 

罗马数字转换为整数

思路:

        建立罗马字符和value的映射,

        遍历罗马字符,当前字符代表值和下一个字符的值比较,大的res+=value,小的res-=value

const romanToInt = s => {
    let map = new Map([['I', 1], ['V', 5], ['X', 10], ['L', 50], ['C', 100], ['D', 500], ['M', 1000]])
    let res = 0;
    for (let i = 0; i < s.length; i++) {
        let left = map.get(s[i]);
        let right = map.get(s[i + 1]);
        res += left < right ? -left : left
    }
    return res
};

最接近目标值的三数之和

思路:

        给定一个暂定值,可以是Number类的最大值,也可以是随便一个值,

判断三数之和和target之间的绝对值和暂定值与target绝对值的大小,越小说明越接近目标值

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var threeSumClosest = function(nums, target) {
    let result =Number.MAX_SAFE_INTEGER;
    nums.sort((a,b)=>a-b);
    for(let i=0;i<nums.length;i++){
        let left = i+1;
        let right = nums.length-1;
        while(left<right){
            let sum = nums[i]+nums[left]+nums[right];
            if(Math.abs(target-sum)<Math.abs(target-result)){
                result = sum;
            }
            if(sum>target){
                right--;
            }else if(sum<target){
                left++;
            }else{
                return sum;
            }
        }
    }
    return result
};

最接近目标值的n数之和

思路:

        两数之和可以采用双指针来找到最接近的两数,三数之和在此基础上,target-nums[i] 等于两数的target,求得两数之和和nums[i]相加,四数之和以此类推,从而获取状态转移方程

for(let i= start; i<len;i++){
  let res= getNumSum(arrs,start++,n-1,target-num[i]);
  res.forEach((item)=>{
    item.push(num[i]);
    res.push(item);
    })  
}

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
var fourSum = function (nums, target) {
  // 先排序
  nums.sort((a, b) => a - b);
  /*
  注意:调用这个函数之前一定要先给 nums 排序
  n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和
   */
  const nSumTarget = (nums, n, start, target) => {
    let size = nums.length;
    let res = [];
    // 至少是 2Sum,且数组大小不应该小于 n
    if (n < 2 || size < n) return res;
    // 2Sum 是 base case
    if (n == 2) {
      // 双指针那一套操作
      let lo = start,
        hi = size - 1;
      while (lo < hi) {
        let sum = nums[lo] + nums[hi];
        let left = nums[lo],
          right = nums[hi];
        if (sum < target) {
          while (lo < hi && nums[lo] == left) lo++;
        } else if (sum > target) {
          while (lo < hi && nums[hi] == right) hi--;
        } else {
          res.push([left, right]);
          while (lo < hi && nums[lo] == left) lo++;
          while (lo < hi && nums[hi] == right) hi--;
        }
      }
    } else {
      // n > 2 时,递归计算 (n-1)Sum 的结果
      for (let i = start; i < size; i++) {
        let sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
        for (let arr of sub) {
          arr.push(nums[i]);
          res.push(arr);
        }
        while (i < size - 1 && nums[i] == nums[i + 1]) i++;
      }
    }
    return res;
  };
  // n 为 4,从 nums[0] 开始计算和为 target 的四元组
  return nSumTarget(nums, 4, 0, target);
};

最长公共字符串

         思路:找第一个字符串当对比,用数组其余字符串与其进行比较(从第一个字符开始)

  相同则使用substring拼接

function longestCommonPrefix(strs: string[]): string {
    if(strs.length ==0){return null}
    let result:string = strs[0];
    for(let i=1;i<strs.length;i++){
        let j=0;
        let len = result.length;
        for(;j<len;j++){
            if(result[j]!=strs[i][j]) break;    
        }
    result = result.substring(0,j)
    }
    return result;
};

合并两个有序链表 

        链表真的很抽象,我理解不了...

思路:

        边界:其中一个链表为null时返回另一个。 两个都为null则返回null

        判断链表头部val,将小的返回

        小的节点的next指向 新的递归 (再次调用合并函数,将val值较小的链表剩余节点和 另一个链表传入)

  let mergeListNode=(L1,L2)=>{
        if (L1==null) {
          return L2;
        }
        if(L2==null){
          return L1;
        }
        if(L1.val>L2.val){
          L2.next = mergeListNode(L1,L2.next);
          return L2;
        }else{
          L1.next = mergeListNode(L1.next,L2);
          return L1;
        }
      }

生成指定对数的括号(随意排列)

 指定对数 n,代表最后返回的括号字符串长度为n*2

左右括号的长度分别为n

两种递归条件: 

当左括号存在,递归拼接左括号

右括号存在,递归拼接右括号

var generateParenthesis = function (n) {
  const res = [];

  const dfs = (lRemain, rRemain, str) => { // 左右括号所剩的数量,str是当前构建的字符串
    if (str.length == 2 * n) { // 字符串构建完成
      res.push(str);           // 加入解集
      return;                  // 结束当前递归分支
    }
    if (lRemain > 0) {         // 只要左括号有剩,就可以选它,然后继续做选择(递归)
      dfs(lRemain - 1, rRemain, str + "(");
    }
    if (lRemain < rRemain) {   // 右括号比左括号剩的多,才能选右括号
      dfs(lRemain, rRemain - 1, str + ")"); // 然后继续做选择(递归)
    }
  };

  dfs(n, n, ""); // 递归的入口,剩余数量都是n,初始字符串是空串
  return res;
};

链表两两反转 

思路:

第二个节点指向第一个节点,再把第一个节点指向后面剩余节点,

利用递归以及指针改变链表的next实现反转(链表是一个很抽象的内容,实在是理解困难..)

var swapPairs = function(head) {
    if(head==null||head.next==null){
        return head
    }
    let newHead= head.next;
    head.next = swapPairs(newHead.next);
    newHead.next = head;
    return newHead
};

以K为组进行链表反转

思路: 从head开始依次取出链表K个元素并用stack作为栈来存放,取出后node指向下一个

var reverseKGroup = function(head, k) {
    if (!head) return head;
    if (k < 2) return head;
    //边界
    let newHead = head;
    const travel = (node, preNode) => {
        let i = 0;
        let stack = [];
        while (node && i < k) {
            stack.push(node);
            node = node.next;  // 每添加一个元素到栈,node就改变下一个指向
            i++;
        } 
        let cur = stack.pop();  // 后入先出,此刻从后面获取
        if (i === k) {
            if (newHead === head) newHead = cur;
            if (preNode) preNode.next = cur;
            while (stack.length) {
                cur.next = stack.pop();
                cur = cur.next;  // 节点反转的同时要改变cur的next指向
            }
            cur.next = node; // 反转好的链表和剩余节点连接
        }
        if (node) {
            return travel(node, cur);  //把剩余node和 反转后链表的尾节点再次递归
        }
    }
    travel(head, null);
    return newHead;
};

链表反转 

思路: 给定res做最后节点,每次反转相当于将当前节点的next指向res,,之后将该节点赋值给res

let reverseList = (list)=>{
    let res= null;
    let cur = list;
    while(cur){
        let next = cur.next;
        cur.next = res;
        next.next = cur;
        res = cur;
    }
    return res;
    
    }

和为K的子数组个数 

思路: 前缀和思路  

        sum[0] = nums[0]

        sum[1] = nums[0]+nums[1],    

        sum[2] = nums[0]+nums[1]+nums[2]  ...以此类推

以上求出的sum数组为                                                                             前缀和数组,

        根据规律可求得 N数和 = sum[n]   ,n 到 m 的和 sum[m] - sum[n] 

目标值为K,代表N数之和,求得公式   sum[i] - sum[j-1] =K;

利用哈希表和前缀和求得题解

·        

class Solution {
    public int subarraySum(int[] nums, int k) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        int pre = 0;
        int ans = 0;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);//错误:少了这行代码
        for(int i = 0; i < nums.length; i++){
            pre += nums[i];
                //遍历求得前i数之和
            int target = pre - k;
                //求得差值
            ans += map.getOrDefault(target, 0);
            map.put(pre, map.getOrDefault(pre, 0) + 1);
            //统计满足条件的前缀和出现次数,最后通过ans返回
        }
        return ans;
    }
}

 给定除数和被除数,不用运算符求出商

思路:

        用map存放除数和对应数字, 递增实现阶乘  c+=c  

var divide = function(dividend, divisor) {
	// 获取符号
	let a = dividend>=0,b = divisor>=0
	// 全部转正数处理
	dividend = Math.abs(dividend)
	divisor = Math.abs(divisor)
	//  对除数进行翻倍 存储待用
	// 最终 map =  [...., [4,divisor*4] , [2,divisor*2] , [1,divisor*1]]
	let c = 1
    let map = []
	let temp = divisor
	while(dividend >= temp){
		map.unshift([c,temp])
		temp += temp
		c += c
	}  
	//  让被除数不断减去map中小于它的除数 统计次数 则为结果
	let sum = 0
	for(let i = 0;i< map.length && dividend > 0;i++){
		let [c,divisor] = map[i]
		if(dividend >= divisor){
			dividend-=divisor
			sum +=c
		}
	}
	
	return  a == b?Math.min(result,2147483648-1):-Math.min(result,2147483648)
};

下一个排列 

        思路: 数组全排列之后,找出给定排列的下一个排列

双指针法,两个相邻指针,从后往前进行遍历,找到数组中第一个升序排列的两个元素,

再找到从尾指针到数组末尾中最小的比头指针大的元素并调换位置,之后将尾指针之后的数组元素反转,得到下一个排列。

var nextPermutation = function(nums) {
    if(nums.length == 1) return nums;//如果长度为1,直接返回
    let i = nums.length - 2;
    let j = nums.length - 1;
    let k = nums.length - 1;
    while(nums[i] >= nums[j] && i >= 0) {//找到相邻两位为升序排列的nums[i],nums[j]
        i--;
        j--;
    }
    if(i < 0) return nums.reverse();//如果i<0,表示从后往前,左边每一位数字都大于等于右边相邻数字,当前排列是最大值,反转数组返回最小值
    while(nums[i] >= nums[k]) {//从右往左找到第一个比nums[i]大的数字
        k--;
    }
    [nums[i], nums[k]] = [nums[k], nums[i]];//交换
    for(let l = nums.length - 1; l > j; l--, j++) {//从nums[j]到nums[nums.length - 1]都是降序,反转使其变成升序
        [nums[l], nums[j]] = [nums[j], nums[l]];
    }
    return nums;
};

最长有效括号 

        思路:用栈的思想来做,当出现开口,push,当出现闭口pop,判断栈的长度,来获取最长有效括号

var longestValidParentheses = function (s) {
    let maxLen = 0
    let stack = []
    stack.push(-1) // 初始化一个参照物
    for (let i = 0; i < s.length; i++) {
        if (s[i] === '(') {
            // ( 入栈   )出栈
            stack.push(i)
        } else {
            // )的情况 出栈
            stack.pop()
            if (stack.length) {
                // 每次出栈 计算下当前有效连续长度
                // 如何计算连续长度 当前位置 - 栈顶下标
                maxLen = Math.max(maxLen, i - stack[stack.length - 1])
            } else {
                stack.push(i) //栈为空时 放入右括号参照物 表示从这个下标开始 需要重新计算长度
            }
        }
    }
    return maxLen
};

 数组查找(哈希法)

var search = function(nums, target) {
     let map = new Map;
     for(let i = 0;i < nums.length;i++)
        map.set(nums[i],i);
     if(map.has(target)) return map.get(target);
     else return -1;
 };

查找数组中元素出现的第一个和最后一个位置 

        思路:二分查找

判断条件有两种,左边界为第一个等于target的数字,右边界为第一个大于target的数字索引减一

解法一:

 const findIndex = (nums, target, ifLeft) => {
     let left = 0;
     let right = nums.length - 1;
     let ans = nums.length;
     while(left <= right) {
         const mid = (left + right) >> 1;
         //nums[mid] > target用于找到大于目标值的下标,(ifLeft && nums[mid] >= target)用于找到大于等于目标值的下标
         if(nums[mid] > target || (ifLeft && nums[mid] >= target)) {
             right = mid - 1;//缩小范围,帮助确认ans的最小值,即第一个符合条件的下标
             ans = mid;//更新下标值
         } else {
             left = mid + 1;//缩小范围,帮助确认ans的最小值,即第一个符合条件的下标
         }
     }
     return ans;
 }
var searchRange = function(nums, target) {
    let ans = [-1, -1];
    const leftIndex = findIndex(nums, target, true);//第一个大于等于目标值的下标
    const rightIndex = findIndex(nums, target, false) - 1;//第一个大于目标值的下标 - 1
    //防止leftIndex和rightIndex不合理,比如[5,7,7,8,8,10],target值是6,rightIndex是1;需要再次确定
    if(leftIndex <= rightIndex && rightIndex < nums.length && nums[leftIndex] == target && nums[rightIndex] == target) {
        ans = [leftIndex, rightIndex];
    }
    return ans;
}

解法二: 

var searchRange = function(nums, target) {
  let ans = [-1, -1],
      len = nums.length,
      left = 0,
      right = len - 1;
  
  // 迭代查找右边界
  while (left <= right) {
    let mid = left + Math.floor((right - left) / 2);
    
    if (nums[mid] === target && (mid === len - 1 || nums[mid + 1] > target)) {
      ans[1] = mid;
      break;
    }
    
    if (nums[mid] <= target) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }
  
  // 迭代查找左边界
  left = 0;
  while (left <= right) {
    let mid = left + Math.floor((right - left) / 2);
    
    if (nums[mid] === target && (mid === 0 || nums[mid - 1] < target)) {
      ans[0] = mid;
      break;
    }
    
    if (nums[mid] >= target) {
      right = mid - 1;
    } else {
      left = mid + 1;
    }
  }
  
  return (ans[0] === -1 || ans[1] === -1) ? [-1, -1] : ans;
};

给定一个数,求对应的描述数组的内容 

        思路: 描述数组第一项为1,  第二项 11(一个一) ,第三项  21(两个一),第四项 1211(一个二一个一) ...

        每一项都是前一项内容的描述,使用动态规划来求解

let getDiscribeNmm = (n) =>{
       let dp = ["1"];
       for(let i=1;i<n;i++){
        dp[i] = currentNum(dp[i-1]);   
       }
}

let currentNum=(cur)=>{
    let res = '';
    let left =0;
    while(left<cur.length){
        let sum =1;
        while(cur[left] == cur[left+1]{
            left++;
            sum++
        }
        res = res + sum + cur[left];

    }
    return res;
}

跳跃游戏

给定一个数组,每一位代表可以跳跃的位数,初始为0,如果可以跳到最后一位返回true

思路:

        使用动态规划求解,从倒数第二位开始,当前位的数字大于当前索引到结尾的长度即可,改变尾指针指向,指向当前索引。

var canJump = function(nums) {
    // 必须到达end下标的数字
    let end = nums.length - 1;

    for (let i = nums.length - 2; i >= 0; i--) {
        if (end - i <= nums[i]) {
            end = i;
        }
    }

    return end == 0;
};

区间合并

        思路: 按照每个数组的首个元素进行排序,之后遍历通过指针进行合并

var merge = function(intervals) {
  if (intervals.length === 0) return [];
  // 按每个区间的开头大小排序
  intervals.sort((a, b) => {
    return a[0] - b[0];
  });
  let res = [];
  let tmp = intervals[0];
  for (let interval of intervals) {
    // 有重叠,可以合并
    if (interval[0] <= tmp[1]) {
      tmp = [ tmp[0], Math.max(interval[1], tmp[1]) ];
        // 指针永远指向数组合并后的容器
    } else { // 无重叠, tmp是独立的区间,记录到结果中
      res.push([].concat(tmp)); //push进当前指向的容器
      tmp = interval;  //改变指向到当前数组
    }
  }
  res.push(tmp);// 最后的区间

  return res;
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值