顺序表和链表OJ题记录


在做顺序表和链表的相关OJ题时,很多类似题的解法也比较类似,为了能更好的复习回顾,特别记录一下在做题时的一些思路历程。

顺序表

删除有序数组中的重复项

1、描述:给你一个升序排列的数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持 一致 。

在这里插入图片描述

2、思路: 使得每个元素只出现一次?这要怎样做,有什么方法来判定元素出现的次数?使用双指针。原地删除?也就是说不能引入额外的空间,需要在原数组上操作。定义两个指针firsec,初始都在索引为0的位置,何时需要移动这两个指针呢?看条件,原地删除使得每个元素只出现一次,那就先定义[0,fir]这个区间上的所有元素都是只出现了一次的元素,不断向后移动sec,当此时nums[fir] != nums[sec]时,也就是说此时这两个位置的元素是不重复的,我们再移动fir指针,然后用sec位置的值覆盖fir位置的值,继续保证[0,fir]这个区间上的所有元素始终只出现了一次,直到sec走到null,循环结束,最终得到的[0,fir]之间的所有元素都是满足题意的,新数组的长度就是fir + 1
3、代码实现

public class Num_26_RemoveDuplicates {
    public int removeDuplicates(int[] nums) {
        int fir = 0, sec = 0;
        while (sec< nums.length){
            if(nums[fir]!=nums[sec]){
                fir++;
                nums[fir] = nums[sec];
            }
            //更新循环条件
            sec ++;
        }
        return fir+1;
    }
}

移除元素

1、描述: 给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。不需要考虑数组中超出新长度后面的元素。
在这里插入图片描述

2、思路: 乍一看这个题和26号题类似,等同于原地移除所有数值等于 val 的元素,让它一次都不出现。做法是不是和26号问题一致呢?其实思路基本不变。采用双指针firsec。让sec指针不断向后遍历,还是那个问题,什么时候要移动fir指针呢?如果nums[sec] != val ,要先用nums[sec]的值覆盖当前nums[fir]的值,然后再移动fir指针。因为如果先移动fir,那fir原始为位置可能就是重复元素。需要先覆盖,让它变为不重复元素,然后再移动fir。那还是定义[0,fir]之间的所有元素都是不重复的?这里有点细微的差别,fir位置上的元素可能就是重复出现的元素,所以不重复的区间应该是[0,fir),删除完之后数组的长度就是fir
3、代码实现

public class Num_27_RemoveValue {
    public int removeElement(int[] nums, int val) {
        int fir = 0, sec = 0;
        //nums[0...fir)一定是数组中值不为val的区间
        while(sec< nums.length){
            if (nums[sec]!=val){
                nums[fir]=nums[sec];
                fir ++;
            }
            sec ++;
        }
        return fir;
    }
}

删除有序数组中的重复项II

1、描述: 给你一个有序数组 nums ,请你 原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。
在这里插入图片描述
2、思路:题目要求每个元素最多出现k次,这种问题和26、27是一类问题,核心就是对于指针运动条件的考量,什么时候需要移动index指针?确定指针运动条件,根据index的定义:不重复的元素的右边界,那么当有新的不重复的元素需要加入时index将向右移动。 如何运动呢?这仍然跟指针的定义有关系,用循环不变量来说:就是你得在循环的时候保持变量的定义不变。 因为这里我们定义不重复元素集合是[0,index]左闭右闭这是循环不变量,因此index这个下标是可以用来放新的不重复元素的。同时index++;保持下一个循环时任然是[0,index]左闭右闭。nums[i] != nums[index] 表示当前遍历的位置i和不重复集合的右边界不一样,那么肯定能放入不重复集合了,题目要求每个元素最多出现k次,那么当nums[i] != nums[index-(k-1)] ,最多允许到index-(k-1)的元素与index相同,也就是i最极限的情况与index-(k-1)相同,就可以保证不会存在连续的重复元素超过k个。
3、代码实现

public int removeDuplicates(int[] nums) {
        //定义元素重复的最大次数
        int k = 2;
         // 1.定义[0,index] 是修改后的满足要求的数组区间,这里已经把0,1,2 ...k- 1 ,共k个数放进去了
        int index = k - 1;
        // 2.判断终止条件
        for(int i = k;i < nums.length;i++){
            // 3.指针移动条件
            if(nums[i] != nums[index-k+1]){
                index++;
                nums[index] = nums[i];
            }
        }
        // 4.判断返回值
        return index + 1;
    }
}

合并两个有序数组

1、描述: 给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。请你 合并 nums2nums1 中,使合并后的数组同样按非递减顺序排列。
在这里插入图片描述
2、思路: 先遍历两个子数组,题目要求合并后的数组元素要按非递减顺序排雷,所以通过比较子数组中元素的大小关系来决定tmp数组中元素的值,遍历完之后,将剩下的元素写回tmp数组,由于题目要求是要将nums2合并到nums1中,所以最终再将tmp数组中的所有元素写回nums1
3、代码实现:

public class Num_88_MergeTwoArray {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = 0,j = 0,k = 0;
        int[] tmp = new int[nums1.length];
        //此时两个子数组都还有元素没遍历完
        while(i<m && j<n){
            if(nums1[i]<nums2[j]){
                //将i对应的元素写回tmp
                tmp[k ++] = nums1[i ++];
            }else{
                tmp[k ++] = nums2[j ++];
            }
        }
        //此时一定有一个数组遍历完了
        // 只需要将剩下还没走完的写回tmp
        while(i<m){
            tmp[k++] = nums1[i++];
        }
        while(j<n){
            tmp[k++] = nums2[j++];
        }
        //此时两个数组的所有有效元素全部合并在了tmp数组中,写回nums1
        for (int l = 0; l < nums1.length; l++) {
            nums1[l] = tmp[l];
        }
    }
}

消失的数字

1、描述: 数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。
在这里插入图片描述
2、代码实现

public class Number17_04_Missing_Number {
    //1、排序法
    public int missingNumber(int[] nums) {
        //先将数组转为有序数组
        Arrays.sort(nums);
        //遍历整个数组
        for (int i = 0; i < nums.length; i++) {
            //若当前索引位置的元素是和索引值不同
            if(nums[i]!=i){
                //则当前索引值就是消失的数字,直接返回
                return i;
            }
        }
        //此时消失的数字就是最后一个数字
        return nums.length;
    }
    //2、哈希思想
    public int missingNumber(int[] nums) {
        //创建一个布尔数组
        boolean[] array = new boolean[nums.length + 1];
        //遍历原数组,将出现的值当成布尔数组的索引,并将其置为true
        for (int i = 0; i < nums.length; i++) {
            array[nums[i]] = true;
        }
        //遍历布尔数组,此时值为false位置对应的索引即为消失的数字
        for (int i = 0; i < array.length; i++) {
            if(!array[i]){
                return i;
            }
        }
        return -1;
    }
    //3、异或思想
    public int missingNumber(int[] nums) {
        int ret = 0;
        //遍历整个数组
        for (int i = 0; i < nums.length; i++) {
            //将数组下标和数组的值异或
            ret^=i;
            ret^=nums[i];
        }
        //与数组的长度异或
        ret^=nums.length;
        //相同的值异或为0,异或完的结果就是消失的数字
        return ret;
    }
}

轮转数组

1、描述: 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
在这里插入图片描述

2、思路: 这个题的核心在于k只要求了非负数,如果k大于了数组长度,该如何表示。其实i+k超出数组长度,移位数等效于(i+k)%nums.length
3、代码实现

    public void rotate(int[] nums, int k) {
        //开辟一个新数组,和元素组长度一样
        int[] array = new int[nums.length];
        //遍历原数组,将原数组中的每个元素右移k位之后放入新数组
        //i+k超出数组长度,移位数等效于(i+k)%nums.length
        for (int i = 0; i < nums.length; i++) {
            array[(k+i)%nums.length] = nums[i];
        }
        //将得到的新数组覆盖掉原数组
        for (int i = 0; i < nums.length; i++) {
            nums[i] = array[i];
        }
    }

链表

删除排序链表中的重复元素

1、描述: 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回已排序的链表 。
在这里插入图片描述

2、思路: 这个题目和顺序表部分的第一个题目很类似,在这里我们也可以借助双引用的思想,但更简洁易懂的方法是递归法,先把子链表中所有重复的节点递归的删除,只保留一次,最后再判断头节点是不是待删除的节点。在使用递归方法的过程中,重点在于边界条件的把握。
3、代码实现

    public ListNode deleteDuplicates(ListNode head) {
        //base case
        if(head==null || head.next==null){
            return head;
        }
        //链表一定至少2个节点
        ListNode dummyhead = new ListNode(-101);
        dummyhead.next = head;
        ListNode prev = dummyhead;
        ListNode cur = prev.next;
        while (cur!=null){
            if(prev.val == cur.val){
                prev.next = cur.next;
            }else{
                //只有当两个值不相等时才能移动prev的指向
                prev = prev.next;
            }
            //不管重复与否,要更新cur
            cur = cur.next;
        }
        return dummyhead.next;
    }
    //递归法
    public ListNode deleteDuplicates(ListNode head) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        //先把子链表中所有重复节点删除,只保留一次
        head.next = deleteDuplicates(head.next);
        //再判断头节点是否是重复节点
        if(head.val == head.next.val){
            return head.next;
        }
        return head;
    }
}

删除排序链表中的重复元素II

1、描述: 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回已排序的链表 。
在这里插入图片描述

2、思路: 采用递归的思想。
3、代码实现

    //方法一
    public ListNode deleteDuplicates(ListNode head) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        //定义虚拟头节点
        ListNode dummyhead = new ListNode(-101);
        //prev一定要指向不是重复的节点
        dummyhead.next = head;
        ListNode prev = dummyhead;
        ListNode cur = prev.next;
        while (cur!=null){
            ListNode sec = cur.next;
            if(sec == null){
                break;
            }
            if(cur.val!= sec.val){
                //只有当cur和sec的值不相等时才能移动prev
                prev = prev.next;
            }else{
                //此时cur和sec相等
                while(sec!=null && cur.val == sec.val){
                    //此时移动sec引用直到cur的值和sec的值不同
                    sec = sec.next;
                }
                //此时sec一定走到第一个和cur不相等的节点
                //prev。。。sec之间全都是待删除的节点
            }
            //更新cur,指向sec
            cur = sec;
        }
        return dummyhead.next;
    }
    //方法二
    //递归实现
    //传入一个以head为头节点的链表,就能删除其中所有的重复元素,重复元素一个都不保留
    public ListNode deleteDuplicates(ListNode head) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        if(head.val!=head.next.val){
            head.next = deleteDuplicates(head.next);
            //此时head本身不会重复
            return head;
        }else{
            //head节点是重复节点
            //先处理完头节点的情况
            ListNode newHead  = head.next;
            while (newHead!=null && newHead.val== head.val){
                //将newHead移动到不是重复节点的位置
                newHead = newHead.next;
            }
            //走出循环,newHead一定不是待删除节点,最终传入整个函数
            return deleteDuplicates(newHead);
        }
    }

移除链表元素

1、描述: 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点 。
在这里插入图片描述

2、思路: 采用递归的思想,先把以head.next为头节点的子链表中所有值为val的删除完毕,最后处理头节点。
3、代码实现:

    //1、虚拟头节点法
    public ListNode removeElements(ListNode head, int val) {
        //base case
        if(head == null){
            return null;
        }
        //定义虚拟头节点
        ListNode dummyhead = new ListNode(val);
        //将虚拟头节点和原链表建立联系
        dummyhead.next = head;
        //prev一定指向值不为val的节点
        ListNode prev = dummyhead;
        while(prev.next!=null){
            if(prev.next.val == val){
                prev.next = prev.next.next;

            }else{
                //后一个节点不是要删除的节点,prev继续向后移动
                prev = prev.next;
            }
        }
        return dummyhead.next;
    }
    //2、递归实现
    public ListNode removeElements(ListNode head, int val) {
        //base case
        if(head == null){
            return null;
        }
        //先把以head.next为头节点的子链表中所有值为val的删除完毕
        head.next = removeElements(head.next,val);
        //最后处理头节点是否需要删除即可
        return head.val==val?head.next:head;
    }

合并两个有序链表

1、描述: 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
在这里插入图片描述
2、思路: 仍然是递归的思想。
3、代码实现

    //方法一 虚拟头节点法
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //base case
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode dummyhead = new ListNode();
        //定义尾节点
        ListNode tail = dummyhead;
        //当list1和list2都不为空时,取较小值拼接在dummyhead之后
        while(list1 != null && list2 != null){
            if(list1.val <= list2.val){
                tail.next = list1;
                //更新tail
                tail = list1;
                //list1继续向下走
                list1 = list1.next;
            }else {
                tail.next = list2;
                tail = list2;
                list2 = list2.next;
            }
        }
        //循环结束之后至少有一个链表为空,此时拼接不为空的链表即可
        if(list1 == null){
            tail.next = list2;
        }
        if(list2 == null){
            tail.next = list1;
        }
        return dummyhead.next;
    }
    //方法二
    //递归实现
    //传入两个以list1和list2为头节点的链表,就能合并两个链表并返回新链表的头节点
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //base case
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        if(list1.val <= list2.val){
            //合并list1从第二个节点开始的链表和整个list2,并拼接在list1头节点的后面
            list1.next= mergeTwoLists(list1.next,list2);
            return list1;
        }else {
            list2.next = mergeTwoLists(list1, list2.next);
            return list2;
        }
    }

链表的中间节点

1、描述: 给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
在这里插入图片描述

2、思路: 快慢指针。让慢指针一次走一步,快指针一次走两步,循环结束之后慢指针恰好在中间位置。
3、代码实现

    //方法一
    public ListNode middleNode(ListNode head) {
        if(head==null || head.next == null){
            return head;
        }
        int length = 0;
        ListNode newhead = head;
        while(newhead!=null){
            length++;
            newhead = newhead.next;
        }
        for (int i = 0; i < length/2; i++) {
            head = head.next;
        }
        return head;
    }
    //方法2;快慢指针
    public ListNode middleNode(ListNode head) {
        ListNode low = head,fast = head;
        while (fast!=null && fast.next!=null){
            //让慢指针一次走一步,快指针一次走两步
            low = low.next;
            fast = fast.next.next;
        }
        //循环结束,此时low恰好在中间位置
        return low;
    }

链表的倒数第K个节点

1、描述: 输入一个链表,输出该链表中倒数第k个结点。
在这里插入图片描述
2、思路: 快慢指针。让快指针先走k步,然后同时移动快指针和慢指针,当快指针指向空时,此时慢指针就是所求的节点。
3、代码实现

    public ListNode FindKthToTail(ListNode head,int k) {
        //base case
        if(head == null || k<=0){
            return null;
        }
        ListNode fast = head, low = head;
        //先让fast走k步
        for (int i = 0; i < k; i++) {
            if(fast==null){
                return null;
            }
            fast = fast.next;
        }
        //同时移动fast和low,当fast为空时,此时low即为所求节点
        while (fast != null){
            fast = fast.next;
            low = low.next;
        }
        return low;
    }

分隔链表

1、描述: 给你一个链表的头节点 head 和一个特定值 x ,对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。保留两个分区中每个节点的初始相对位置。
在这里插入图片描述
2、思路: 这个题的做法和我们的直观做法一样,唯一需要注意的是,当我们根据元素大小关系将原链表的元素拆分开,然后将小链表的尾部和大链表的头部拼接时,此时大链表的尾部可能还有链接,所以需要断开此连接。
3、代码实现

    public ListNode partition(ListNode head, int x) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        //定义两个链表
        ListNode smallhead = new ListNode(), bighead = new ListNode();
        ListNode smalltail = smallhead,bigtail = bighead;
        //遍历原链表,比较与x的大小关系,将小于x的节点拼接在smallhead之后,>=x的节点拼接在bighead之后
        while (head != null){
            if(head.val < x){
                smalltail.next = head;
                smalltail = head;
                head = head.next;
            }else {
                bigtail.next = head;
                bigtail = head;
                head = head.next;
            }
        }
        //循环结束将小链表的尾部与大链表的头部拼接
        //注意,此时大链表的尾部可能还有连接,需要断开此连接
        smalltail.next = bighead.next;
        bigtail.next = null;
        return smallhead.next;
    }

反转链表

1、描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
在这里插入图片描述
2、思路: 这个题目的方法有很多种,可以用虚拟头节点法,也可以双引用法,或者是简洁明了的递归法。
3、代码实现

    //方法一 虚拟头节点
    public ListNode reverseList(ListNode head) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        //定义虚拟头节点
        ListNode dummyhead = new ListNode();
        //不断遍历原链表,产生新节点,头插到新链表
        while (head!=null){
            ListNode node = new ListNode(head.val);
            node.next = dummyhead.next;
            dummyhead.next = node;
            head = head.next;
        }
        return dummyhead.next;
    }
    //方法二
    public ListNode reverseList(ListNode head) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        //定义两个引用
        ListNode prev = null;
        ListNode cur = head;
        while(cur!=null){
            //先暂存一下cur位置的下一个节点的地址
            ListNode next = cur.next;
            //将原链表中的prev->cur变为cur->prev
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        //此时新链表恰好是以prev为头节点的链表
        return prev;
    }
    //方法3、递归
    public ListNode reverseList(ListNode head) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        //先暂存一下头节点的下一个节点,防止转完之后丢失
        ListNode next = head.next;
        //反转以head.next为头节点的子链表,返回反转后链表的头节点
        ListNode newhead = reverseList(head.next);
        //将原链表的头节点与反转后的子链表拼起来
        head.next= null;
        next.next= head;
        return newhead;
    }

相交链表

1、描述: 给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
在这里插入图片描述
2、思路: 这个题非常有意思,引入双引用,核心问题在于判断他们何时相较。其实这是一个数学问题。在遍历两个链表的过程中,我们让L1走完自己本身之后倒过头来走L2,同样让L2走完自己本身之后倒过头来走L1,数学上可表示为:X + Y + Z = Z + Y + X,恒成立
在这里插入图片描述3、代码实现

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //引入两个引用l1和l2,同时开始遍历
        //让l1走完自己的路之后倒过来走一遍l2,x + z + y = y + z + x
        ListNode l1 = headA,l2 = headB;
        while (l1!=l2){
            l1 = l1 == null?headB:l1.next;
            l2 = l2 == null?headA:l2.next;
        }
        return l1;
    }

判断回文链表

1、描述: 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false
在这里插入图片描述
**2、思路:**这个题目主要用到前面的找链表的中间节点方法和反转链表的方法。先找到链表的中间节点,然后将后半部分链表反转,与前半段链表进行比较,一旦有元素值不相同,即为false。
3、代码实现

    public boolean isPalindrome(ListNode head) {
        //base case
        if(head==null && head.next == null){
            return true;
        }
        //先找到链表的中间节点
        ListNode middlenode = middleNode(head);
        //将后半段链表反转
        ListNode l2 = reverseList(middlenode);
        while(head!=null && l2 != null){
            if(head.val != l2.val){
                return false;
            }
            head = head.next;
            l2 = l2.next;
        }
        return true;
    }
    public ListNode middleNode(ListNode head) {
        ListNode low = head,fast = head;
        while (fast!=null && fast.next!=null){
            //让慢指针一次走一步,快指针一次走两步
            low = low.next;
            fast = fast.next.next;
        }
        //循环结束,此时low恰好在中间位置
        return low;
    }

    public ListNode reverseList(ListNode head) {
        //base case
        if(head == null || head.next == null){
            return head;
        }
        //先暂存一下头节点的下一个节点,防止转完之后丢失
        ListNode next = head.next;
        //反转以head.next为头节点的子链表,返回反转后链表的头节点
        ListNode newhead = reverseList(head.next);
        //将原链表的头节点与反转后的子链表拼起来
        head.next= null;
        next.next= head;
        return newhead;
    }

判断环形链表

1、描述: 给你一个链表的头节点 head ,判断链表中是否有环。如果链表中存在环 ,则返回 true 。 否则,返回 false
在这里插入图片描述
2、思路: 这道题也是一道数学题,重点在于寻找链表有环的条件。采用快慢指针,遍历整个链表,让快指针每次走两步,慢指针每次走一步,若链表有环,总会在某一个时刻相遇。
3、代码实现

    public boolean hasCycle(ListNode head) {
        //快慢指针
        ListNode fast = head,low = head;
        //遍历整个链表,让快指针每次走两步,慢指针每次走一步,若链表有环,总会相遇
        while (fast!= null && fast.next!= null){
            low = low.next;
            fast = fast.next.next;
            if(low == fast){
                return true;
            }
        }
        //循环结束还没有找到
        return false;
    }

环形链表II

1、描述: 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。不允许修改链表。
在这里插入图片描述
2、思路: 双指针思想。这个题的核心在于环形链表环的入口的寻找方法,本质也是一个数学上的问题。通过数学证明,可以得到一个结论:当快慢指针重合时,引入一个新的节点从链表的头节点开始走,最终新节点和慢指针一定会在环的入口相遇
3、代码实现

    public ListNode detectCycle(ListNode head) {
        //结论:当快慢指针重合时,引入一个新的节点从链表的头节点开始走,最终新节点和慢指针一定会在环的入口相遇
        ListNode low = head,fast = head;
        while (fast!=null && fast.next != null){
            low = low.next;
            fast = fast.next.next;
            if (low == fast){
                ListNode third = head;
                while (third!=low){
                    third = third.next;
                    low = low.next;
                }
                //走出这个循环此时third和low重合,相遇在环的入口
                return third;
            }
        }
        return null;
    }

总结

在单链表的常见问题中,主要采用以下三种思想:
1、虚拟头节点法: 牵扯到链表的插入和删除时;
2、双指针、三指针: 牵扯到元素之间的比较问题。
3、快慢指针: 牵扯到寻找链表中某个特殊节点的问题;
4、递归: 牵扯到问题能拆分,则这个链表问题一定可以用递归来简化操作。

明天也需要继续加油!!!
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值