• 数据结构入门


    1. 两数之和

     解法1
    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
            //使用暴力搜索做,复杂度(O(n^2))  只想到暴力搜,思维和做题方法和掌握的东西都还是不够啊
            vector<int> res;
            int len = nums.size();
            for(int i = 0; i < len; i++){
                for(int j = i; j < len; j++){
                    if(i != j && (nums[i] + nums[j] == target)){
                        res.push_back(i);
                        res.push_back(j);
                        return res;
                    }
                }
    
            }
            return res;
        }
    };
     解法2

    使用哈希表。

    散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
    给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
    //JAVA
    class Solution {
        public int[] twoSum(int[] nums, int target) {
           //使用哈希表
           Map<Integer, Integer>map=new HashMap<>();//建立元素值和元素位置之间的映射
           map.put(nums[0],0);//记录第一个元素和其下标0
           for(int i = 1; i < nums.length; i++){
               int matchNum = target - nums[i];//寻找nums[i]对应的值
               if(map.containsKey(matchNum)){
                   int idx1 = i;
                   int idx2 = map.get(matchNum);
                   return new int[] {idx1, idx2};
               }else{
                   map.put(nums[i], i);
               }
    
    
           }
           return null;
        }
    }
    //C++
    class
    Solution { public: vector<int> twoSum(vector<int>& nums, int target) { //使用哈希表 vector<int> res; map<int, int> pairs; pairs.insert({nums[0], 0});//记录第一个元素和其下标0 for(int i = 1; i < nums.size(); i++){ int matchNum = target - nums[i];//寻找nums[i]对应的值 for (auto itr = pairs.find(matchNum); itr != pairs.end(); itr++) { res.push_back(i); res.push_back(itr->second); return res; } pairs.insert({nums[i], i}); } return res; } };

    c++中map使用参考:https://www.geeksforgeeks.org/map-associative-containers-the-c-standard-template-library-stl/

    2. 合并两个有序数组

    class Solution {
    public:
        void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
            for (int i = 0; i < n; i++) 
                nums1[m+i] = nums2[i]; 
            sort(nums1.begin(), nums1.end());
        }
    };

    3. 两个数组的交集

     方法一,使用hash表  即map   第一轮循环统计nums1元素出现次数,第二轮与nums2对比找到交集元素,并更新出现的次数
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            //使用hash表
            vector<int> res;
            map<int, int> m;
            //先把nums1 中的所有元素加入到hash表中
            for (auto i : nums1){
                m[i]++;//此时所有的element为key  i 的计数次数
            }
            for (auto i : nums2){
            if (m[i]>0)//如果在m中找到2中的元素i
                {
                    res.push_back(i); //把找此时到的这个加入到输出中
                    m[i]--;//由于nums2中的某个元素计数可能比nums1多,所以就要更新m中的 element
                }
    
            } 
            return res;
    }

    速度还行,就是消耗内存比较大.

    方法二,双指针 暴力搜 速度慢,内存消耗小一点
     vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            //排序 双指针   暴力搜 速度慢很多,内存消耗小一点  不用存所有的元素在新的内存中
           sort(nums1.begin(), nums1.end());
            sort(nums2.begin(), nums2.end());
            int idx1 = 0, idx2 = 0;
            vector<int> ans;
            while (idx1 < nums1.size() && idx2 < nums2.size()) {
                int n1 = nums1[idx1], n2 = nums2[idx2];
                if (n1 == n2) {
                    ans.push_back(n1);
                    idx1 ++;
                    idx2 ++;
                } else if (n1 < n2){
                    idx1 ++;
                } else {
                    idx2 ++;
                }
            }
            return ans;
        }

    4. 买卖股票最佳时机

     使用暴力搜不行的,当数据量大的时候,很可能会花费大量的时间,这样看起来就不是很经济。

    双循环暴力搜(不是好方法)
    int maxProfit(vector<int>& prices) {
            //回忆起返回数组中连续元素最大和
            int res = 0;
            for(int i = 0; i < prices.size(); i++){
                for(int j = i; j < prices.size(); j++){
                    if(prices[j]-prices[i] > res){
                        res = prices[j]-prices[i];
                    }
                }
            }
            return res;
    
        }
     动态规划(dynamic programming)
    • DP状态方程: 第i天的收益 = max(第i-1天的收益+第i天的价格-前i-1天中的最小价格,第i天的价格-前i-1天中的最小价格)
     int maxProfit(vector<int>& prices) {
            //解题思路   DP动态规划,dynamic programming
            int len=prices.size(), dp[len], ret=0;
            dp[0]=0;
            for(int i = 1; i<len; i++){
                //动态规划状态转移方程
                dp[i]=max(dp[i-1] + prices[i] - prices[i-1], prices[i] - prices[i-1]);
                ret = max(ret, dp[i]);//c++中取多个数中最大值没有直接接口但是两个数取大小还是有接口呀 max()  min()
            }
            return ret;
        }
    • DP状态方程: 前i天的最大收益 = max(前i-1天的最大收益,第i天的价格-前i-1天中的最小价格)
      int maxProfit(vector<int>& prices) {
            //解题思路   DP动态规划,dynamic programming
            //前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
            int len=prices.size(), ret=0;
            if(len<=1) return 0;
            int min_ago_iminus1 = prices[0];
            for(int i = 1; i<len; i++){
                //动态规划状态转移方程
                min_ago_iminus1=min(prices[i],min_ago_iminus1);
                ret = max(ret, prices[i]-min_ago_iminus1);
            }
            return ret;

    关于动态规划,hxd说过:(size的扩展get到了吗?)

    5. 存在重复元素

     

    bool containsDuplicate(vector<int>& nums) {
        return set<int>(nums.begin(), nums.end()).size() != nums.size();
        }
    bool containsDuplicate(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());//从小到大
        int j = 0;
        for (int i = 1; i < n; i++) {
            j = i - 1;
            if (nums[i] - nums[j] == 0) {
                return true;
            }
        }
        return false;
        //一行代码实现  简单,but消耗时间多一些些 
    }

    6. 最大子序和

    int maxSubArray(vector<int>& nums) {
        /*
        * 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
        * 输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
        * 输出:6
        * 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
        * 复杂度要为 O(n) 遍历两次复杂度肯定达不到
        */
        if (nums.size() == 0) return NULL;
        int res = INT_MIN;
        int f_n = -1;
        for (int i = 0; i < nums.size(); ++i) {
            f_n = max(nums[i], f_n + nums[i]); //关键在于  连续  每次计算一个值  同返回的最大值比较即可
            res = max(f_n, res);
        }
        return res;
    }

    7. 重塑矩阵

     我的思路:先打散,在动态创建vector重新赋值

    vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
            if(mat.size()*mat[0].size() != r*c){
                return mat;
            }
            vector<int> temp;
            for(int i = 0; i < mat.size(); i++){
                for(int j = 0; j< mat[0].size(); j++){
                    temp.push_back(mat[i][j]);
                }
            }
            vector<vector<int>> ret;
            int sub = 0;
            for(int i = 0; i < r; i++){
                vector<int> single;
                for(int j = 0; j< c; j++){
                    single.push_back(temp[sub]);
                    sub++;
                }
                ret.push_back(single);
            }
            return ret;
        }

    可以尝试根据需求固定创建二维矩阵的方法:

    vector<vector<int>> matrixReshape(vector<vector<int>>& nums, int r, int c) {
            int m = nums.size();
            int n = nums[0].size();
            if (m * n != r * c) return nums;
            
            vector<vector<int>> ret(r, vector<int>(c));
            for (int i = 0; i < m * n; i++) {
                ret[i / c][i % c] = nums[i / n][i % n];
            }
            return ret;
        }
    重新赋值语句:
    ret[i / c][i % c] = nums[i / n][i % n] 

    8. 杨辉三角

     最重要的是每个数是由它左上方和右上方的数的和,这个怎么表达出来:

    temp[j] = ret[i-1][j-1] + ret[i-1][j]
     vector<vector<int>> generate(int numRows) {
            vector<vector<int>> ret;
            for(int i = 0; i < numRows; i++){
                //创建中间vector,全初始化1再做修改
                vector<int> temp(i+1, 1);
                for(int j = 1; j <= i-1; j++){
                    temp[j] = ret[i-1][j-1] + ret[i-1][j];
                }
                ret.push_back(temp);
            }
            return ret;
        }

    9. 有效的数独

     方法一:用空间换时间,内存消耗较大 
       bool isValidSudoku(vector<vector<char>>& board) {
            //使用count就可以记录刚生成的vector有没有重复 中间完成判断,而不是等生成后再判断
            vector<unordered_set<char>> rows(9);
            vector<unordered_set<char>> cols(9);
            vector<unordered_set<char>> boxes(9);
    
            for(int i=0; i<9; i++){
                for(int j=0; j<9; j++){
                    int num = board[i][j];
                    if(num!='.'){
                        int boxes_i = (i/3)*3 + j/3;
    
                        if(rows[i].count(num) || cols[j].count(num) || boxes[boxes_i].count(num))
                            return false;
    
                        rows[i].insert(num);
                        cols[j].insert(num);
                        boxes[boxes_i].insert(num);
                    }
                }
            }
            return true;
        }
    方法二: 位运算

    其中一维数组的每个元素表示一行或一列或一个子数独的值,因为int为32位,数独每行列数独都是9个数,可以用二进制0、1进行存储到一个元素中,通过这种方式压缩空间

     bool isValidSudoku(vector<vector<char>>& board) {
           vector<int> row(9,0);
           vector<int> line(9,0);
           vector<int> son(9,0);
    
            for(int i=0;i<9;i++){
                for(int j=0;j<9;j++){
                    if(board[i][j]=='.')
                        continue;
                    int k=(i/3)*3+j/3;
                    int val=1<<(board[i][j]-'1');
                    if(row[i]&val||line[j]&val||son[k]&val)
                        return false;
                    row[i]|=val;
                    line[j]|=val;
                    son[k]|=val;
                }
            }
            return true;
        }

    10. 矩阵置零

     方法一:

     void setZeroes(vector<vector<int>>& matrix) {
            //先标识要置零的行和列
            set<int> zero_row;
            set<int> zero_column;
            for(int i=0;i<matrix.size();i++){
                 for(int j=0;j<matrix[0].size();j++){
                     if(matrix[i][j]==0){
                         zero_row.insert(i);
                         zero_column.insert(j);
                     }
                 }
            }
            //然后原地修改
            set<int>::iterator itr;
            for (itr = zero_row.begin(); itr != zero_row.end(); itr++)
            {
                for(int i = 0;i<matrix[0].size();i++){
                    matrix[*itr][i] = 0;
                }
            }
            for (itr = zero_column.begin(); itr != zero_column.end(); itr++)
            {
                for(int i = 0;i<matrix.size();i++){
                    matrix[i][*itr] = 0;
                }
            }
        }

    快的很,但是复杂度达不到O(1),为O(m+n)

    方法二:我们只需要常数空间存储若干变量O(1)。

    主要利用由于在原地修改,将要置零的行列信息存储到第一行或第一列,但是首先要用两个变量来存储首行或者首列要不要置零

     void setZeroes(vector<vector<int>>& matrix) {
            if(matrix.size()==0 || matrix[0].size()==0)  return;
            bool rowFlag=false,colFlag=false;
            int rows=matrix.size(),cols=matrix[0].size();
            //看第一列是否是有0
            for(int i=0;i<rows;++i){
                if(matrix[i][0] == 0){
                    colFlag=true;
                    break;
                }
            }
            //看第一行是否有0
            for(int i=0;i<cols;++i){
                if(matrix[0][i]==0){
                    rowFlag=true;
                    break;
                }
            }
            //遍历除第一行第一列以外的矩阵元素,如果有元素为0,则将对应的第一行第一列的位置置为0
            for(int i=1;i<rows;++i){
                for(int j=1;j<cols;++j){
                    if(matrix[i][j]==0){
                        matrix[i][0]=0;
                        matrix[0][j]=0;
                    }
                }
            }
            //同样遍历除第一行第一列以外的矩阵元素,如果matrix[i][j]所在位置的第一行第一列有任意一个位置为0 那么那个位置将被置为0   上一步的反馈
            for(int i=1;i<rows;++i){
                for(int j=1;j<cols;++j){
                    if(matrix[i][0]==0 || matrix[0][j]==0){ 
                        matrix[i][j]=0;
                    }
                }
            }
            //看第一列是否有0  有则将该列置为0
            if(colFlag){
                for(int i=0;i<rows;++i) matrix[i][0] = 0;
            }
            //行同理
            if(rowFlag){
                for(int i=0;i<cols;++i) matrix[0][i] = 0;
            }
        }

    实际上这两个方法内存消耗差不多,只有当矩阵中有非常多0的时候差别才大。

    11. 字符串中第一个唯一字符

     

     思路,和字符出现次数相关,使用hash表:

    int firstUniqChar(string s) {
            unordered_map<char, int> m;
            for (auto &c : s) ++m[c];//初始值默认为0
            for (auto &c : s) if (m[c] == 1) return s.find_first_of(c);
            return -1;
    
        }

    12. 赎金信(一个字符串中是不是有足够的字符构成一个新的串)

     杂志字符串中的每个字符只能在赎金信字符串中使用一次,就是说ransom中的每个字母出现至多和magazine中相等,才可能输出true. 涉及字母频率,

    方法一:使用hash表
     bool canConstruct(string ransomNote, string magazine) {
            //首先对ransom中的字母个数做统计
            unordered_map<char, int> mr;
            for(auto &c : ransomNote) ++mr[c];
            //遍历maganize中的字母,如果count<需要的  则输出false
            unordered_map<char, int> mm;
            for(auto &b : magazine) ++mm[b];
            for(auto itr = mr.begin(); itr != mr.end(); ++itr){
                if((itr->second)>mm[itr->first]){
                    return false;
                }
            }
            return true;
        }
    方法二:数组代替hash表, 由于少了个循环,更快了
    bool canConstruct(string ransomNote, string magazine) {
            vector<int> count(26);
            for(auto &c:magazine)
                ++count[c-'a'];
    
            for(auto &c:ransomNote)
            {
                if(count[c-'a']!=0)
                    --count[c-'a'];
                else
                    return false;
            }
            return true;
        }

    13. 有效的字母异同位

     方法一,hash表:
    bool isAnagram(string s, string t) {
            unordered_map<char, int> ms;
            for(auto &c : s) ++ms[c];
            unordered_map<char, int> mt;
            for(auto &b : t) ++mt[b];
            if(ms.size() != mt.size()) return false;
            for(auto itr = ms.begin(); itr != ms.end(); ++itr){
                if((itr->second) != mt[itr->first]){
                    return false;
                }
            }
            return true;
        }
    方法二:直接用sort函数
        bool isAnagram(string s, string t) {
            sort(s.begin(),s.end());
            sort(t.begin(),t.end());
            if(s==t)
                return true;
            else
                return false;
        }

    这段在python中只要一行:

    return sorted(s)==sorted(t)
    方法三:数组代替hash表
        bool isAnagram(string s, string t) {
            int count[26]={0}; 
            if(s.length()!=t.length())
                return false;
            for(int i=0;s[i]!='';i++){
                count[s[i]-'a']++;
                count[t[i]-'a']--;
               
            }
            for(int i=0;i<26;i++)
                if(count[i]!=0)
                    return false;
            return true;
        }

    14. 环形链表 

    • 快慢指针  快指针走两步,慢指针走一步,如果存在环,一定会在环中相遇
    • 快指针始终一步一步靠近慢指针
    • 一定会在环中相遇 =>有环

    hxd快慢指针题解:https://leetcode-cn.com/problems/linked-list-cycle/solution/141-huan-xing-lian-biao-shuang-zhi-zhen-zhao-huan-/

        bool hasCycle(ListNode *head) {
                ListNode* fast = head;
                ListNode* slow = head;
           // 循环条件注意 为了保证在环中相遇,使用fast做判断
    while(fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; if (slow == fast) return true; } return false; }

    15. 合并两个有序列表

    • 主要是要利用原有链表空间而不是要新建空间
    方法一:递归, 很快啊

    为什么可以用递归?官方题解

    所有的步骤都是选一个较小的,同剩下元素合并的结果。

    忽略边界情况,比如空链表等,则有如下操作:

    •  如果有链表为空,直接返回另一个
    • 否则判断 l1 和 l2 哪一个链表的头节点的值更小,递归的决定下一个添加到结果里的节点
    • 如果两个链表有一个为空,递归结束。
       ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
            if (l1 == nullptr) {
                return l2;
            } else if (l2 == nullptr) {
                return l1;
            } else if (l1->val < l2->val) {
                l1->next = mergeTwoLists(l1->next, l2);
                return l1;
            } else {
                l2->next = mergeTwoLists(l1, l2->next);
                return l2;
            }
        }
    方法二:迭代
    • 首先定义一个哑节点,空间复杂度O(1)
    • 拿两个链表的当前遍历的值和哑节点做对比,如果需要插入,就更新哑节点和当前表示的next
    • 循环条件直到有一个已经遍历到结尾,时间复杂度O(m+n)
    • 将另一个当前的下一项加入即可(表达不清,看代码)
      ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
            ListNode* retHead = new ListNode(-1);  //作为哑节点
            ListNode* prev = retHead;
            ListNode* ret = retHead;
            while(l1!=nullptr && l2!=nullptr){
                if(l1->val < l2->val){
                    prev->next = l1;
                    l1= l1->next;
                }else{
                    prev->next = l2;
                    l2= l2->next;
                }
                prev = prev->next;
    
            }
            prev->next = l1 == nullptr ? l2 : l1;
            ret = retHead->next;
            delete retHead;
            return ret;
        }

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode() : val(0), next(nullptr) {}
     *     ListNode(int x) : val(x), next(nullptr) {}
     *     ListNode(int x, ListNode *next) : val(x), next(next) {}
     * };
     */

    16. 移除链表元素

    此题删除链表中元素是很简单的,只需要让待删节点之前一个节点指向待删节点之后一个节点即可。 此题最大的问题就是,题目要求我们要返回新链表中的头结点,如果我们就采用仅仅复制头结点的方式(用H=head)然后用H进行操作,最后返回head。这样就会导致如果头结点也是我们需要删除的节点就会导致错误。

    为了避免这种错误,创建一个新节点来作为整个链表的头结点。

    迭代方法:
        ListNode* removeElements(ListNode* head, int val) {
            //迭代方法
            //先确定头
            //ListNode* myhead = head
            if(head==NULL) return NULL;
            ListNode* ret = new ListNode(-1);
            ret->next = head;
    
            ListNode* pos = ret;
            ListNode* del_pos = NULL;
            
            while(pos->next != NULL){ //终止条件
                if(pos->next->val == val){
                    del_pos = pos->next;
                    pos->next = pos->next->next;
                    delete del_pos;
                }else{
                    pos = pos->next;
                }
                
            }
    
            return ret->next;
        }
    递归方法:
    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode() : val(0), next(nullptr) {}
     *     ListNode(int x) : val(x), next(nullptr) {}
     *     ListNode(int x, ListNode *next) : val(x), next(next) {}
     * };
     */
    class Solution {
    public:
        ListNode* removeElements(ListNode* head, int val) {
            //递归方法
            if (!head)
                return head;
            head->next = removeElements(head->next, val);
            return head->val == val ? head->next : head;
        }
    };

    17. 反转链表

     

     用双指针迭代:

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode() : val(0), next(nullptr) {}
     *     ListNode(int x) : val(x), next(nullptr) {}
     *     ListNode(int x, ListNode *next) : val(x), next(next) {}
     * };
     */
    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            //利用双指针迭代
            ListNode* pre = nullptr;
            ListNode* cur = head;
            ListNode* temp = new ListNode();
            while(cur){
                
                temp = cur->next;
                cur->next = pre;
                pre = cur;
                cur = temp;
            }
            return pre;
        }
    };

    • 终止条件是当前节点或者下一个节点==null  why?

      在head指向的结点为null或head指向的结点的下一个结点为null时停止,因为在这两种情况下,反转后的结果就是它自己

    • 推公式reverseList的含义是:把拿到的链表进行反转,然后返回新的头结点newHead

    (不需要明白是每一步怎么推的)

    • 接着要做的就是反转结点1,也就是将head指向的结点作为其下一个结点的下一个结点head.next.next=head
    • 最后,将head指向的结点的下一个结点置为null  
    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            //递归解法
            if (!head || !head->next) {
                return head;
            }
            // 想象递归已经层层返回,到了最后一步  
            // 以链表 1->2->3->4->5 为例,现在链表变成了 5->4->3->2->null,1->2->null(是一个链表,不是两个链表)
            // 此时 newHead是5,head是1
    
            ListNode* newHead = reverseList(head->next);
            // 最后的操作是把链表 1->2->null 变成 2->1->null
            // head是1,head.next是2,head.next.next = head 就是2指向1,此时链表为 2->1->2
            head->next->next = head;
            // 防止链表循环,1指向null,此时链表为 2->1->null,整个链表为 5->4->3->2->1->null
            head->next = nullptr;
            return newHead;
        }
    };

    递归就是只要考虑当前节点和后面整体的关系,不要去考虑后面整体内部关系,把整体内部的关系交给递归处理。最后只需要返回最初设想的值…

    hxd说递归:https://leetcode-cn.com/problems/reverse-linked-list/solution/yi-bu-yi-bu-jiao-ni-ru-he-yong-di-gui-si-67c3/

    18. 删除链表中的重复元素

     双指针,很快啊:

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode() : val(0), next(nullptr) {}
     *     ListNode(int x) : val(x), next(nullptr) {}
     *     ListNode(int x, ListNode *next) : val(x), next(next) {}
     * };
     */
    class Solution {
    public:
        ListNode* deleteDuplicates(ListNode* head) {
            if(head==nullptr || head->next==nullptr) return head;
            ListNode* curr = head;
            ListNode* rear = head->next;
    
            while(rear){
                if(curr->val == rear->val){
                    curr->next = rear->next;
                    rear = rear->next;
    
                }else{
                    curr = rear;
                    rear = rear->next;
                }
    
            }
            return head;
    
        }
    };

    注意,c++加上垃圾回收意识就更好了。

    class Solution {
    public:
        ListNode* deleteDuplicates(ListNode* head) {
            if(head==nullptr || head->next==nullptr) return head;
            ListNode* curr = head;
            ListNode* rear = head->next;
    
            while(rear){
                if(curr->val == rear->val){
                    ListNode* temp = rear; //相应地会增加一些开销就是了
                    curr->next = rear->next;
                    rear = rear->next;
                    delete(temp);
    
                }else{
                    curr = rear;
                    rear = rear->next;
                }
    
            }
            return head;
    
        }
    };
  • 相关阅读:
    利用递归分割(Split)字符串
    SQL Server2012 T-SQL基础教程--读书笔记(1-4章)
    kindeditor编辑器的使用
    echarts绘制四川地图
    Windows下搭建PHP开发环境(Apache+PHP+MySQL)+调试工具Xdebug的配置
    给搜索关键字添加高亮,加以颜色区分
    SQL 生成6位随机数并MD5加密输出
    微信小程序登录 .net 后端实现
    钉钉小程序http post 请求
    浅谈Web Api配合SignalR的跨域支持
  • 原文地址:https://www.cnblogs.com/PiaYie/p/14975361.html
Copyright © 2020-2023  润新知