leetcode总结 DAY3 - DAY7 链表,哈希表

参考文章:https://programmercarl.com/

DAY 3 链表1

203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

虚拟头结点

    public ListNode removeElements(ListNode head, int val) {
        ListNode dummyHead = new ListNode();
        dummyHead.next = head;
        ListNode pre = dummyHead;
        while(pre.next!=null){
            if(pre.next.val==val){
                pre.next = pre.next.next;
            }else{
                pre = pre.next;
            }
        }
        return dummyHead.next;
    }

不使用虚拟头结点

    public ListNode removeElements(ListNode head, int val) {
        while(head!=null && head.val == val){
            head = head.next;
        }
        ListNode pre = head;
        while(pre!=null && pre.next!=null){
            if(pre.next.val==val){
                pre.next = pre.next.next;
            }else{
                pre = pre.next;
            }
        }
        return head;
    }

707 设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:valnextval 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

Tips:

​ 1) 使用虚拟头结点

​ 2)addAtHead 和 addAtTail 是 addAtIndex 的两种特殊情况

​ 3)外部类可以使用内部类的私有成员变量

单向链表

class MyLinkedList {
    class Node{
        int val;
        Node next;
        public Node(){}
        public Node(int val){
            this.val = val;
        }
    }
    int size;
    Node dummyHead;
    public MyLinkedList() {
        dummyHead = new Node();
        size = 0;
    }

    private Node getNode(int index){
        Node cur = dummyHead;
        while(index>-1){
            cur = cur.next;
            index--;
        }
        return cur;
    }

    public int get(int index) {
        if(index<0 || index>=size){
            return -1;
        }
        return getNode(index).val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        if(index>size){
            return;
        }
        Node node = new Node(val);
        if(index<0){
            index = 0;
        }
        Node pre = getNode(index-1);
        node.next = pre.next;
        pre.next = node;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if(index<0 || index>=size){
            return;
        }
        Node pre = getNode(index-1);
        pre.next = pre.next.next;
        size--;
    }
}

双向链表

1)不用虚拟尾结点

class MyLinkedList {
    class Node{
        int val;
        Node next;
        Node prev;
        public Node(){}
        public Node(int val){
            this.val = val;
        }
    }

    int size;
    Node dummyHead;
    public MyLinkedList() {
        dummyHead = new Node();
        size = 0;
    }

    private Node getNode(int index){
        Node cur = dummyHead;
        while(index>-1){
            cur = cur.next;
            index--;
        }
        return cur;
    }

    public int get(int index) {
        if(index<0 || index>=size){
            return -1;
        }
        return getNode(index).val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        if(index>size){
            return;
        }
        Node node = new Node(val);
        if(index<0){
            index = 0;
        }
        Node preNode = getNode(index-1);
        node.next = preNode.next;
        preNode.next = node;
        node.prev = preNode;
        if(index !=0 && index !=size){
            node.next.prev = node;
        }
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if(index<0 || index>=size){
            return;
        }
        Node preNode = getNode(index-1);
        preNode.next = preNode.next.next;
        if(index != size-1){
            preNode.next.prev = preNode;
        }
        size--;
    }
}

2)使用虚拟尾结点

class MyLinkedList {
    class Node{
        int val;
        Node next;
        Node prev;
        public Node(){}
        public Node(int val){
            this.val = val;
        }
    }

    int size;
    Node dummyHead, dummyTail;
    public MyLinkedList() {
        dummyHead = new Node();
        dummyTail = new Node();
        dummyHead.next = dummyTail;
        dummyTail.prev = dummyHead;
        size = 0;
    }

    private Node getNode(int index){
        if(index==-1){
            return dummyHead;
        }
        Node cur = dummyHead.next;
        if(index<size/2){
            while(index>0){
                cur = cur.next;
                index--;
            }
        }else{
            cur = dummyTail;
            index = size-index;
            while(index>0){
                cur = cur.prev;
                index--;
            }
        }
        return cur;
    }

    public int get(int index) {
        if(index<0 || index>=size){
            return -1;
        }
        return getNode(index).val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        if(index>size){
            return;
        }
        Node node = new Node(val);
        if(index<0){
            index = 0;
        }
        Node preNode = getNode(index-1);
        node.next = preNode.next;
        node.next.prev = node;
        preNode.next = node;
        node.prev = preNode;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if(index<0 || index>=size){
            return;
        }
        Node preNode = getNode(index-1);
        preNode.next = preNode.next.next;
        preNode.next.prev = preNode;
        size--;
    }
}

206 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

双指针法

    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        while(head!=null){
            ListNode nextNode = head.next;
            head.next = pre;
            pre = head;
            head = nextNode;
        }
        return pre;
    }

递归法

每次输入一个头结点就能返回该链表反转后新的头结点

1)终止条件:链表为空或链表只有一个头结点时,返回

2)过程:除head结点外,后边的链表已经反转好,此时链表的最后一个结点就是head.next,再将head结点添加至反转好链表末尾即可,即令 head.next.next = head, 尾结点指向空head.next = null;

    public ListNode reverseList(ListNode head) {
        if(head==null || head.next == null){
            return head;
        }
        ListNode rHead = reverseList(head.next);
		head.next.next = head;
        head.next = null;
        return rHead;
    }

DAY 4 链表2

24 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

虚拟头结点

    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode();
        dummyHead.next = head;
        ListNode pre = dummyHead;
        while(pre.next!=null && pre.next.next!=null){
            ListNode one = pre.next;
            ListNode two = one.next;
            one.next = two.next;
            two.next = one;
            pre.next = two;
            pre = pre.next.next;
        }
        return dummyHead.next;
    }

递归

    public ListNode swapPairs(ListNode head) {
        if(head==null || head.next==null){
            return head;
        }
        ListNode rHead = swapPairs(head.next.next);
        ListNode two = head.next;
        head.next = rHead;
        two.next = head;
        return two;
    }

19 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

快慢指针

    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode();
        dummyHead.next = head;
        ListNode slowNode = dummyHead;
        ListNode fastNode = slowNode;
        while(n>=0){
            fastNode = fastNode.next;
            n--;
        }
        while(fastNode!=null){
            slowNode = slowNode.next;
            fastNode = fastNode.next;
        }
        slowNode.next = slowNode.next.next;
        return dummyHead.next;
    }

面试题 02.07. 链表相交

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

利用 headA.len + headB.len = headB.len + head.len

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pointA = headA;
        ListNode pointB = headB;
        boolean changA = false, changB = false;
        while(pointA!=null && pointB!=null){
            if(pointA == pointB){
                return pointA;
            }
            if(pointA.next==null && !changA){
                pointA = headB;
                changA = true;
            }else{
                pointA = pointA.next;
            }
            if(pointB.next==null && !changB){
                pointB = headA;
                changB = true;
            }else{
                pointB = pointB.next;
            }
        }
        return null;
    }
}

一些优化,headA.len+1 + headB.len = headB.len +1+ head.len (1代表链表结尾的null)

当两个指针同时为空说明已经两链表中无相交元素(同时为空,要么两链表长度相等,要么已经两个指针均已遍历完两个链表中所有结点)

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pointA = headA;
        ListNode pointB = headB;
        while(pointA!=pointB){
            pointA = pointA!=null?pointA.next:headB;
            pointB = pointB!=null?pointB.next:headA;
        }
        return pointA;
    }

142 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

解题思路:

​ 1)用快慢指针判断是否有环:若相遇,则有环;若快指针走到null,无环

​ 2)在有环的情况下确定环的入口:利用2(x+y) = x+y + n(y+z) 可推得 x = (n-1)(y+z) + z

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9Z6SFnF-1667716332451)(img/image-20221104193334649.png)]

    public ListNode detectCycle(ListNode head) {
        // 1. 利用快慢指针判断有无环
       ListNode slow = head;
       ListNode fast = head;
       while(fast!=null && fast.next!=null){
           slow = slow.next;
           fast = fast.next.next;
           if(slow==fast){
               break;
           }
       } 
       if(fast==null || fast.next==null){
           return null;
       }
       // fast == slow 相遇,有环
       // 2. 在有环的情况下确定环的入口
       ListNode index = head;
       while(fast!=index){
           fast = fast.next;
           index = index.next;
       }
       return index;
    }

DAY 6 哈希表1

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法

但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!

242 有效的字母异位词

给定两个字符串 *s**t* ,编写一个函数来判断 *t* 是否是 *s* 的字母异位词。

**注意:**若 *s**t* 中每个字符出现的次数都相同,则称 *s**t* 互为字母异位词。

    public boolean isAnagram(String s, String t) {
        if(s.length() != t.length()) return false;
        Map<Character, Integer> map = new HashMap<>();
        for(int i=0;i<s.length();i++){
            char cs = s.charAt(i);
            if(map.containsKey(cs)) map.put(cs,map.get(cs)+1);
            else map.put(cs,1);

            char ct = t.charAt(i);
            if(map.containsKey(ct)) map.put(ct,map.get(ct)-1);
            else map.put(ct,-1);
        }
        for(Integer i: map.values()){
            if(i!=0){
                return false;
            }
        }
        return true;
    }

由于小写字母是有限的26个,因此可以用定长数组代替map。数组的index相当于key,arr[index]相当于value。数组的定位和值修改起来更加简便。

使用数组来做哈希的题目,是因为题目都限制了数值的大小

    public boolean isAnagram(String s, String t) {
        if(s.length() != t.length()) return false;
        int[] arr = new int[26];
        for(int i=0;i<s.length();i++){
            char cs = s.charAt(i);
            arr[cs-'a'] += 1;

            char ct = t.charAt(i);
            arr[ct-'a'] -= 1;
        }
        for(Integer i: arr){
            if(i!=0){
                return false;
            }
        }
        return true;
    }

349 两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

**Tips:**Set转Array

        //将结果几何转为数组
       res.stream().mapToInt(x -> x).toArray();

具体实现:

    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> res = new HashSet<>();
        Set<Integer> set = new HashSet<>();
        for(int a : nums1){
            set.add(a);
        }
        for(int b: nums2){
            if(set.contains(b)){
                res.add(b);
            }
        }
        int[] arr = new int[res.size()];
        int i = 0;
        for(Integer num : res){
            arr[i++] = num;
        }
        return arr;
    }

202 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

    public boolean isHappy(int n) {
        Set<Integer> set = new HashSet<>();
        while(n!=1){
            int sum = 0;
            while(n != 0){
               sum += (int)Math.pow(n%10,2);
               n /= 10;
            }
            if(set.contains(sum)){
                return false;
            } 
            set.add(sum);
            n = sum;
        }
        return true;
    }

一些代码优化

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> record = new HashSet<>();
        while (n != 1 && !record.contains(n)) {
            record.add(n);
            n = getNextNumber(n);
        }
        return n == 1;
    }

    private int getNextNumber(int n) {
        int res = 0;
        while (n > 0) {
            int temp = n % 10;
            res += temp * temp;
            n = n / 10;
        }
        return res;
    }
}

1 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

思路:想明白

  • 为什么会想到用哈希表:当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
  • 哈希表为什么用map:不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适
  • 本题map是用来存什么的:map存放的就是我们访问过的元素
  • map中的key和value用来存什么的:{key:数据元素,value:数组元素对应的下标}
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            int temp = target-nums[i];
            if(map.containsKey(temp)){
                res[0] = map.get(temp);
                res[1] = i;
                break;
            }
            map.put(nums[i],i);
        }
        return res;
    }

DAY 7 哈希表2

454 四数相加 II

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

思路:

暴力解法需要四层循环遍历四个数组求出不同组合的和,时间复杂度为O(n^4)。

为减少时间复杂度,我们先求出nums1和nums2的不同组合之和,放入map中(key:sum;value:和为sum的不同组合个数),时间复杂度为O(n^2)。再求出 nums3和nums4 的sum,看map中是否存在一个key == -(nums3[k]+nums[l])。

    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int n = nums1.length;
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                int sum = nums1[i]+nums2[j];
                if(map.containsKey(sum)) map.put(sum,map.get(sum)+1);
                else map.put(sum,1);
            }
        }
        int count = 0;
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                int sum = -(nums3[i]+nums4[j]);
                if(map.containsKey(sum)) count += map.get(sum);
            }
        }
        return count;
    }

383 赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

思路:

因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数。

然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。

在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!

    public boolean canConstruct(String ransomNote, String magazine) {
        if(ransomNote.length()>magazine.length()){
            return false;
        }
        int[] arr = new int[26];
        for(char c : magazine.toCharArray()){
            arr[c-'a'] += 1;
        }
        for(char c : ransomNote.toCharArray()){
            arr[c-'a'] -= 1;
            if(arr[c-'a']<0){
                return false;
            }
        }
        return true;
    }

15 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

双指针法:

一个指针固定的情况下,两个指针不断变化找结果

i:指向第一个数字 left:指向第二个数字 right:指向第三个数字
目的是令 nums[i] + nums[left] + nums[right] == 0

1)对数组进行排序

2)令i指针遍历整个数组

3)i固定,即第一个数固定时,找第二第三个数:从 left=i+1 和 right=nums.length-1 开始判断nums[i] + nums[left] + nums[right],并逐渐缩小[left,right]的范围,直到 left>=right为止
nums[i] + nums[left] + nums[right] < 0,left指针右移

​ nums[i] + nums[left] + nums[right] > 0,right指针左移

​ nums[i] + nums[left] + nums[right] == 0,left 和 right指针同时向对方移动(继续考虑其他组合情况)

**Tips:**将数组变为动态数组的方法

Arrays.asList(nums[i],nums[left++],nums[right--]);

具体实现:

    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        // 由于仅需要三个数字和为0,而不关心每个数字对应的下标,因此数组内元素位置可以改变,支持排序
        Arrays.sort(nums);
        for(int i=0;i<nums.length;i++){
            // 1)剪枝:
            // 由于nums已经是一个排序数组了,且left和right指针在i指针之后,因此若nums[i]>0,则 nums[left]>0, nums[right]>0, 从i开始到数组结束的组合中不存在答案
            if(nums[i]>0){
                return res;
            }
            // 2)给nums[i]去重
            // 不可用nums[i]==nums[i+1]条件是因为:指针在i情况和i+1情况都没有考虑过,且i+1包含在left遍历的范围中,满足条件的结果数组内元素可以重复(即第一二三个数可以重复),如[-1,-1,2]。若此时去重,去的是结果数组内部的重
            // 用nums[i]==nums[i-1]条件的原因: 由于i-1在i左边,已经考虑过第一个数是nums[i-1]的情况,因此若nums[i]==nums[i-1],可以跳过
            if(i>0 && nums[i]==nums[i-1]){
                continue;
            }
            int left = i+1;
            int right = nums.length-1;
            while(left<right){
                int sum = nums[i] + nums[left] + nums[right];
                if(sum<0){
                    left++;
                }else if(sum>0){
                    right--;
                }else{
                    // sum==0
                    // 3)nums[left] 和 nums[right]去重:若left和right与已经处理过的情况相同,就跳过。对left来说left-1已经处理过,对right来说right+1已经处理过。
                    res.add(Arrays.asList(nums[i],nums[left++],nums[right--]));
                    while(left<right && nums[left] == nums[left-1]){
                        left++;
                    }
                    while(left<right && nums[right] == nums[right+1]){
                        right--;
                    }
                }
            }

        }
        return res;
    }

18 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

**双指针法:**思路和三数之和相同,仅剪枝情况不同

两个指针固定的情况下,两个指针不断变化找结果

i:指向第一个数字 j:指向第一个数字 left:指向第二个数字 right:指向第三个数字
目的是令 nums[i] + nums[j] + nums[left] + nums[right] == target

由于target可能为负,而负数相加,越加越小,因此要分情况剪枝

    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for(int i=0;i<nums.length;i++){
            // 剪枝
            // 注意:target可能为负,此时两个比target大的负数相加有可能等于target
            if((target<0 && nums[i]>=0)||(target>=0 && nums[i]>target)){
                break;
            }
            // 去重
            if(i>0 && nums[i]==nums[i-1]){
                continue;
            }
            for(int j=i+1;j<nums.length;j++){
                // 2级剪枝
                if((target<0 && nums[i]+nums[j]>=0) || (target>=0 && nums[i]+nums[j]>target)){
                    break;
                }
                // 去重
                if(j>i+1 && nums[j]==nums[j-1]){
                    continue;
                }
                int left = j+1;
                int right = nums.length-1;
                while(left<right){
                    long sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if(sum>target){
                        right--;
                    }else if(sum<target){
                        left++;
                    }else{
                        res.add(Arrays.asList(nums[i],nums[j],nums[left++],nums[right--]));
                        while(left<right && nums[left]==nums[left-1]){
                            left++;
                        }
                        while(left<right && nums[right]==nums[right+1]){
                            right--;
                        }
                    }
                }
            }
        }
        return res;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值