移动应用开发实验室三面题分析

目录

第一题:20. 有效的括号

题目

解题思路

代码实现

第二题:2810. 故障键盘

题目

解题思路

代码实现

第三题:15. 三数之和

题目

解题思路

代码实现

第四题:21. 合并两个有序链表

题目

解题思路

代码实现

第五题:14. 最长公共前缀

题目

解题思路

代码实现

第六题:LCR 086. 分割回文串

题目

解题思路

代码实现


第一题:20. 有效的括号

题目

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

解题思路

题目要求我们验证一个字符串中的括号是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类型的左括号。例如,字符串 "()" 、"()[]{}"和"([{}])"是有效的,但 "(]" 、"([)]"和"[]{}))" 是无效的。

1.使用栈的数据结构

栈是一种先进后出的数据结构,非常适合用于匹配括号的场景。当遇到左括号时,将其压入栈中;当遇到右括号时,检查栈顶元素是否与之匹配。

2.遍历字符串

从字符串的开头到结尾遍历每个字符。

3.处理左括号

如果当前字符是左括号('('、'[' 或 '{'),则将其压入栈中。

4.处理右括号

如果当前字符是右括号(')'、']' 或 '}'),需要检查栈是否为空以及栈顶的左括号是否与当前右括号匹配。

如果栈为空,说明没有匹配的左括号,因此返回 false。

如果栈不为空,但栈顶的左括号与当前右括号不匹配,也返回 false。

如果匹配,则将栈顶的左括号弹出。

5.检查栈是否为空

遍历完整个字符串后,如果栈为空,说明所有左括号都找到了匹配的右括号,因此返回 true。

如果栈不为空,说明还有未匹配的左括号,因此返回 false。

代码实现

// 判断括号是否不匹配  
int notMatch(char par, char* stack, int stackTop) {  
    switch(par) {  
        case ']': // 如果是右方括号  
            return stack[stackTop - 1] != '['; // 判断栈顶元素是否为左方括号,如果不是则返回1表示不匹配  
        case ')': // 如果是右圆括号  
            return stack[stackTop - 1] != '('; // 判断栈顶元素是否为左圆括号,如果不是则返回1表示不匹配  
        case '}': // 如果是右大括号  
            return stack[stackTop - 1] != '{'; // 判断栈顶元素是否为左大括号,如果不是则返回1表示不匹配  
    }  
    return 0; // 如果par不是右括号,则返回0表示匹配  
}  
  
// 判断字符串中的括号是否有效  
bool isValid(char* s) {  
    int strLen = strlen(s); // 获取字符串的长度  
    char stack[5000]; // 创建一个栈,用于存放左括号  
    int stackTop = 0; // 栈顶指针,初始化为0  
  
    // 遍历字符串中的每个字符  
    for(int i = 0; i < strLen; i++) {  
        char tempChar = s[i]; // 取出当前字符  
  
        // 如果当前字符是左括号,则压入栈中  
        if(tempChar == '(' || tempChar == '[' || tempChar == '{')  
            stack[stackTop++] = tempChar;  
          
        // 如果当前字符是右括号  
        else if(stackTop == 0 || notMatch(tempChar, stack, stackTop)) {  
            // 如果栈为空或当前右括号与栈顶左括号不匹配,返回false  
            return false;  
        }  
          
        // 当前字符与栈顶元素为一对括号,将栈顶元素出栈  
        else  
            stackTop--;  
    }  
  
    // 如果遍历完字符串后栈为空,表示所有括号都匹配,返回true;否则返回false  
    return stackTop == 0;  
}

第二题:2810. 故障键盘

题目

你的笔记本键盘存在故障,每当你在上面输入字符 'i' 时,它会反转你所写的字符串。而输入其他字符则可以正常工作。

给你一个下标从 0 开始的字符串 s ,请你用故障键盘依次输入每个字符。

返回最终笔记本屏幕上输出的字符串。

示例 1:

输入:s = "string"
输出:"rtsng"
解释:
输入第 1 个字符后,屏幕上的文本是:"s" 。
输入第 2 个字符后,屏幕上的文本是:"st" 。
输入第 3 个字符后,屏幕上的文本是:"str" 。
因为第 4 个字符是 'i' ,屏幕上的文本被反转,变成 "rts" 。
输入第 5 个字符后,屏幕上的文本是:"rtsn" 。
输入第 6 个字符后,屏幕上的文本是: "rtsng" 。
因此,返回 "rtsng" 。

示例 2:

输入:s = "poiinter"
输出:"ponter"
解释:
输入第 1 个字符后,屏幕上的文本是:"p" 。
输入第 2 个字符后,屏幕上的文本是:"po" 。
因为第 3 个字符是 'i' ,屏幕上的文本被反转,变成 "op" 。
因为第 4 个字符是 'i' ,屏幕上的文本被反转,变成 "po" 。
输入第 5 个字符后,屏幕上的文本是:"pon" 。
输入第 6 个字符后,屏幕上的文本是:"pont" 。
输入第 7 个字符后,屏幕上的文本是:"ponte" 。
输入第 8 个字符后,屏幕上的文本是:"ponter" 。
因此,返回 "ponter" 。

提示:

  • 1 <= s.length <= 100
  • s 由小写英文字母组成
  • s[0] != 'i'

解题思路

1.函数定义和变量初始化:

reverse函数是用来反转字符串s中从start到end位置的字符的。它使用了两个指针start和end,通过交换两个指针所指向的字符来实现反转。

finalString函数用来处理输入字符串s,并返回最终的结果字符串。在函数内部,首先获取输入字符串的长度len,然后初始化一个用于记录结果字符串中已存储字符数的变量n。

使用malloc函数为结果字符串result分配足够的内存空间,大小为len + 1(包括字符串结束符'\0')。

2.遍历输入字符串s:

使用一个循环来遍历输入字符串s的每个字符。在每次循环中,检查当前字符是否是'i'。

3.处理字符'i':

如果当前字符是'i',需要调用reverse函数来反转result中从字符串开始到当前'i'字符之前(不包括'i')的所有字符。注意,这里使用n - 1作为reverse函数的end参数,因为n是下一个字符应该存放的位置,所以n - 1是当前'i'字符之前的位置。

4.处理非'i'字符:

如果当前字符不是'i',将其添加到结果字符串result中,并更新n的值,以便在下次循环中知道下一个字符应该存放在哪里。

5.添加字符串结束符并返回结果:

在遍历完输入字符串s后,在结果字符串result的末尾添加字符串结束符'\0',以标识字符串的结束。最后,返回处理后的结果字符串result。

代码实现

void reverse(char* s, int start, int end) {
    // 定义两个指针,一个指向字符串开始位置,一个指向字符串的end位置
    for (; start < end; start++, end--) { // 当start小于end时,循环继续
        char t = s[start]; // 临时存储start指针指向的字符
        s[start] = s[end]; // 将end指针指向的字符放到start指针的位置
        s[end] = t;        // 将临时存储的字符放到end指针的位置
    }
}
char* finalString(char* s) {
    int len = strlen(s); // 获取输入字符串的长度
    int n = 0; // n用于记录结果字符串中当前已存储的字符数
    char* result = (char*)malloc((len + 1) * sizeof(char)); // 分配足够的空间来存储结果字符串
    for (int i = 0; i < len; i++) { // 遍历输入字符串s的每个字符
        if (s[i] == 'i') {          // 如果遇到字符'i'
            reverse(result, 0, n - 1); // 反转结果字符串result中i之前部分
        } else {
            result[n] = s[i]; // 将当前字符添加到结果字符串result中
            n++;              // 更新已存储的字符数
        }
    }
    result[n] = '\0'; // 在结果字符串result的末尾添加字符串结束符
    return result;    // 返回处理后的结果字符串
}

第三题:15. 三数之和

题目

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

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

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

解题思路

先对数组进行排序,然后使用双指针法来寻找满足条件的三元组。

1.排序

首先对数组 nums 进行排序。排序的目的是为了后续能够利用双指针法有效地找到和为0的三元组,并且方便跳过重复的元素。

2.遍历数组

在排序后的数组中,固定一个元素,然后使用双指针法在剩余的元素中寻找两个数,使得它们的和与固定元素的和为0。在遍历过程中,需要注意跳过重复的元素以避免重复的三元组。

3.双指针法

对于每个 nums[i],我们设置两个指针 left 和 right,分别指向 i 的下一个位置和数组的末尾。这两个指针用于找到与 nums[i] 相加和为0的两个数。

如果三个数的和小于0,说明 left 指针所指的数太小,我们需要将 left 向右移动以增大和。

如果三个数的和大于0,说明 right 指针所指的数太大,我们需要将 right 向左移动以减小和。

如果三个数的和恰好等于0,那么我们找到了一个符合要求的三元组,将其添加到结果中,并移动 left 和 right 指针以跳过重复的元素。

4.跳过重复元素

在遍历数组和使用双指针法的过程中,需要特别注意跳过重复的元素。因为如果数组中有重复的元素,那么这些重复的元素可能会生成重复的三元组。为了避免这种情况,在遍历数组时,如果当前元素与前一个元素相同,就跳过它。同样,在移动 left 和 right 指针时,如果它们所指的元素与前一个元素相同,也跳过它们。

5、处理结果

当我们找到一个符合要求的三元组时,需要将其存储起来。由于题目要求返回的是一个二维数组,需要为每个三元组分配内存,并将其添加到结果数组中。同时,还需要记录每个三元组的长度,通过 returnColumnSizes 来完成。

6、返回结果

最后,我们返回结果数组、结果数组的长度以及每个三元组的长度。

代码实现

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume
 * caller calls free().
 */
int cmp(const void* a, const void* b) {
    // 比较函数,用于 qsort 排序
    return (*(int*)a - *(int*)b);
}

int** threeSum(int* nums, int numsSize, int* returnSize,
               int** returnColumnSizes) {
    if (numsSize < 3) {
        // 如果数组元素少于3个,则无法构成三元组,直接返回空
        *returnSize = 0;
        return NULL;
    }

    // 先对数组进行排序
    qsort(nums, numsSize, sizeof(int), cmp);

    // 初始化结果数组和对应的大小数组
    int** result = (int**)malloc(sizeof(int*) * numsSize * numsSize);
    *returnColumnSizes = (int*)malloc(sizeof(int) * numsSize * numsSize);
    *returnSize = 0;

    for (int i = 0; i < numsSize - 2; i++) {
        // 跳过重复元素
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }

        int left = i + 1;
        int right = numsSize - 1;

        while (left < right) {
            int sum = nums[i] + nums[left] + nums[right];

            if (sum == 0) {
                // 当找到和为0的三元组时,分配内存并存储
                result[*returnSize] = (int*)malloc(sizeof(int) * 3);
                result[*returnSize][0] = nums[i];
                result[*returnSize][1] = nums[left];
                result[*returnSize][2] = nums[right];
                (*returnColumnSizes)[*returnSize] = 3;
                (*returnSize)++;

                // 跳过重复元素,防止生成重复的三元组
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }

                // 移动指针继续寻找
                left++;
                right--;
            } else if (sum < 0) {
                // 和太小,左指针右移增大和
                left++;
            } else {
                // 和太大,右指针左移减小和
                right--;
            }
        }
    }
    return result;
}

第四题:21. 合并两个有序链表

题目

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列

解题思路

1.创建哑节点:

在合并两个链表时,为了方便处理边界情况(例如,当其中一个链表为空时),通常会引入一个哑节点(dummy node)。哑节点本身不存储任何有意义的数据,它的主要作用是作为合并后链表的起始点,从而简化链表头部的操作。

2.初始化指针:

创建一个current指针,并将其指向哑节点。这个指针将用于遍历合并后的链表,并在合适的位置插入来自list1和list2的节点。

3.比较并合并:

使用while循环遍历list1和list2,直到其中一个链表为空。在每次循环中,比较list1和list2当前节点的值:

如果list1的当前节点值小于list2的当前节点值,则将list1的当前节点连接到current之后,并将list1的指针向前移动一位。

否则,将list2的当前节点连接到current之后,并将list2的指针向前移动一位。

不论哪种情况,都将current指针向前移动一位,以便在下一次循环中连接下一个节点。

4.处理剩余部分:

当while循环结束时,至少有一个链表已经为空。此时,将另一个链表(如果有剩余节点)的剩余部分直接连接到current之后。这是因为在前面的循环中,已经确保了已连接的节点是按升序排列的,所以直接将剩余部分连接到末尾不会破坏顺序。

5.返回结果:

最后,返回哑节点的下一个节点作为合并后链表的头节点。哑节点本身不包含在合并后的链表中,它只是一个辅助工具。

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    // 创建一个哑节点,用于保存合并后链表的头节点
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    // 创建一个指针,用于遍历合并后的链表
    struct ListNode* current = dummy;

    // 当两个链表都不为空时,进行合并操作
    while (list1 != NULL && list2 != NULL) {
        // 比较两个链表当前节点的值,将较小的节点添加到合并后的链表中
        if (list1->val < list2->val) {
            current->next = list1;
            list1 = list1->next;
        } else {
            current->next = list2;
            list2 = list2->next;
        }
        // 移动 current 指针到下一个节点
        current = current->next;
    }

    // 当其中一个链表为空时,将另一个链表的剩余部分添加到合并后的链表中
    if (list1 != NULL) {
        current->next = list1;
    } else {
        current->next = list2;
    }

    // 返回合并后的链表头节点
    return dummy->next;
}

第五题:14. 最长公共前缀

题目

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

示例 1:

输入:strs = ["flower","flow","flight"]
输出:"fl"

示例 2:

输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。

提示:

  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] 仅由小写英文字母组成

解题思路

1.边界情况处理

如果输入的字符串数组strs为空(即strsSize为0),那么自然没有公共前缀,直接返回空字符串。

2.初始化遍历

我们选择数组中的第一个字符串strs[0]作为基准字符串,然后遍历它的每个字符。因为要找的是公共前缀,所以只需要找到一个前缀,使得所有字符串都包含这个前缀即可。

3.字符比较

对于strs[0]中的每个字符,使用内层循环遍历剩余的字符串strs[1]到strs[strsSize-1]。在内层循环中,比较当前位置上的字符是否相同。如果找到了一个位置,使得某个字符串的字符与strs[0]中的字符不同,那么就找到了最长公共前缀的终点。

4.截断并返回

一旦发现最长公共前缀的终点,就将strs[0]的当前字符设置为字符串结束符'\0',以截断它。然后返回截断后的strs[0],它就是最长公共前缀。

5.正常结束

如果内外层循环都正常结束,说明遍历完了strs[0]的所有字符,并且没有找到与strs[0]中字符不同的位置。这意味着strs[0]本身就是最长公共前缀(可能是因为所有字符串都相同,或者有些字符串本身就是strs[0]的前缀)。在这种情况下,直接返回strs[0]。

代码实现

char* longestCommonPrefix(char** strs, int strsSize) {
    // 如果字符串数组为空,则没有公共前缀,返回空字符串
    if (strsSize == 0) {
        return "";
    }

    // 外层循环:遍历第一个字符串的每个字符
    for (int i = 0; i < strlen(strs[0]); i++) {
        // 内层循环:遍历剩余的字符串,与第一个字符串的当前字符进行比较
        for (int j = 1; j < strsSize; j++) {
            // 如果某个字符串的当前字符与第一个字符串的当前字符不同
            if (strs[0][i] != strs[j][i]) {
                // 将第一个字符串的当前字符设置为字符串结束符,即截断第一个字符串
                strs[0][i] = '\0';
                // 返回截断后的第一个字符串,它就是最长公共前缀
                return strs[0];
            }
        }
    }
     // 如果循环正常结束,说明第一个字符串就是最长公共前缀(所有字符串都相同或为空) 
     return strs[0];
}

第六题:LCR 086. 分割回文串

题目

给定一个字符串 s ,请将 s 分割成一些子串,使每个子串都是 回文串 ,返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

示例 1:

输入:s = "google"
输出:[["g","o","o","g","l","e"],["g","oo","g","l","e"],["goog","l","e"]]

示例 2:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 3:

输入:s = "a"
输出:[["a"]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

解题思路

采用回溯法来遍历所有可能的子串组合,并检查每个组合是否都是回文串。

1.初始化:

定义全局变量S存储输入的字符串,LEN存储字符串的长度。定义全局变量Path用于存储当前正在构建的回文子串组合的边界索引,Path_Index表示当前Path中的索引数量。定义全局变量Result用于存储最终的回文子串组合结果,定义全局变量RSIZE和RCOLSIZES用于返回结果数组的大小和每个子串组合的长度。

2.辅助函数isPalindrome:

定义一个辅助函数isPalindrome来判断一个字符串的子串(从索引l到r)是否是回文串。使用双指针方法,一个指针从l开始,另一个指针从r开始,逐步向中间移动,比较对应的字符是否相等。如果所有字符都相等,则返回true,否则返回false。

3.回溯函数Back_Track:

这是一个递归函数,用于遍历所有可能的子串组合。从字符串的起始位置offset开始,尝试每一个可能的结束位置i(从offset到LEN-1)。对于每个结束位置i,检查从offset到i的子串是否是回文串(使用isPalindrome函数)。如果是回文串,则将结束位置i添加到Path中,并递归调用Back_Track(i + 1)来继续寻找下一个回文子串。在递归返回之前,需要将Path_Index减1,以撤销对当前结束位置i的选择,进行下一个可能的选择。当offset等于LEN时,说明已经找到了一个完整的回文子串组合,此时调用Duplicate函数将当前组合复制到Result中,并更新结果数组的大小和每个子串组合的长度。

4.复制函数Duplicate:

这个函数用于将当前Path中存储的回文子串组合复制到Result中。根据Path_Index分配足够的空间来存储子串组合的指针数组。遍历Path,计算每个子串的长度和起始位置,并将子串内容复制到新的字符数组中。每个子串的末尾添加字符串结束符\0。

5.主函数partition:

初始化全局变量和动态分配Path的空间。调用Back_Track(0)开始回溯过程,从字符串的起始位置开始寻找回文子串组合。返回Result指针,即最终的回文子串组合结果。

代码实现

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
// 判断给定字符串s从l到r的部分是否为回文串  
bool isPalindrome(char* s, int l, int r) {  
    while(l <= r) {  
        if(s[l] != s[r]) {  
            return false; // 如果字符不相等,则不是回文串  
        }  
        l++;  
        r--;  
    }  
    return true; // 字符都相等,是回文串  
}  
  
// 全局变量,用于存储输入的字符串、结果大小、结果列大小、当前字符串长度、路径数组等  
char* S = NULL;  
int* RSIZE = NULL;  
int RCOLSIZES[32768];  
  
int LEN = 0;  
  
int* Path = NULL;  
int Path_Index = 0;  
  
char** Result[32768];  
  
// 复制当前路径所代表的字符串到结果数组中  
void Duplicate(char*** des) {  
    *des = (char**)malloc(sizeof(char*) * Path_Index); // 分配结果数组的空间  
    for(int i = 0; i < Path_Index; i++) {  
        int sublen = Path[i] - (i ? Path[i - 1] : -1); // 计算当前子串长度  
        int start = i ? (Path[i - 1] + 1) : 0; // 计算子串起始位置  
  
        (*des)[i] = (char*)malloc(sizeof(char) * (sublen + 1)); // 分配子串空间  
        for(int j = 0; j < sublen; j++) {  
            (*des)[i][j] = S[start + j]; // 复制子串到结果数组  
        }  
        (*des)[i][sublen] = '\0'; // 添加字符串结束符  
    }  
}  
  
// 回溯函数,用于查找字符串s的所有回文子串组合  
void Back_Track(int offset) {  
    if(offset == LEN) { // 遍历到字符串末尾  
        Duplicate(Result + *RSIZE); // 复制当前路径到结果数组  
        RCOLSIZES[*RSIZE] = Path_Index; // 记录当前路径长度  
        (*RSIZE)++; // 结果数组大小加1  
        return;  
    }  
  
    for(int i = offset; i < LEN; i++) { // 从offset开始遍历到字符串末尾  
        if(isPalindrome(S, offset, i)) { // 判断从offset到i的子串是否为回文串  
            Path[Path_Index] = i; // 将i添加到路径中  
            Path_Index++; // 路径长度加1  
            Back_Track(i + 1); // 继续向下回溯  
            Path_Index--; // 回溯,路径长度减1  
        }  
        else {  
            continue; // 如果不是回文串,跳过当前循环,继续下一个循环  
        }  
    }  
}  
  
// 主函数,返回字符串s的所有回文子串组合  
char*** partition(char* s, int* returnSize, int** returnColumnSizes) {  
    S = s; // 赋值全局变量S  
    RSIZE = returnSize; // 赋值全局变量RSIZE  
    *returnColumnSizes = RCOLSIZES; // 赋值全局变量RCOLSIZES  
  
    *RSIZE = 0; // 初始化结果数组大小为0  
  
    LEN = strlen(s); // 获取字符串长度  
  
    Path = (int*)malloc(sizeof(int) * LEN); // 分配路径数组空间  
    Path_Index = 0; // 初始化路径长度为0  
  
    Back_Track(0); // 从字符串开头开始回溯  
  
    return Result; // 返回结果数组  
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值