zoukankan      html  css  js  c++  java
  • 一些笔试算法

    1.数组中的数分为两组,让给出一个算法,使得两个组的和的差的绝对值最小 

      数组中的数的取值范围是0<x<100,元素个数也是大于0, 小于100 
      比如a[]={2,4,5,6,7},得出的两组数{2,4,6}和{5,7},abs(sum(a1)-sum(a2))=0;
      比如{2,5,6,10},abs(sum(2,10)-sum(5,6))=1,所以得出的两组数分别为{2,10}和{5,6}。

      1 /**
      2  * 数组中的数分为两组,让给出一个算法,使得两个组的和的差的绝对值最小 
      3  * 数组中的数的取值范围是0<x<100,元素个数也是大于0, 小于100 
      4      比如a[]={2,4,5,6,7},得出的两组数{2,4,6}和{5,7},abs(sum(a1)-sum(a2))=0;
      5       比如{2,5,6,10},abs(sum(2,10)-sum(5,6))=1,所以得出的两组数分别为{2,10}和{5,6}。
      6  * @author hasee
      7  *
      8  */
      9 public class cutArray {
     10     
     11     /**
     12      * isOK[i][v]表示是否可以找到i个数,使得他们之和等于v
     13      * 动态规划 使用前一个isOK计算后一个
     14      * if(v>=arr[k] && isOK[i-1][v-arr[k]]) 
     15      *     isOK[i][v] = true;
     16      */
     17     static boolean isOK[][];
     18     static int sum;
     19     static{
     20         isOK = new boolean[20][100];
     21         isOK[0][0] = true;
     22         for (boolean[] bs : isOK) {
     23             for (boolean b : bs) {
     24                 b = false;
     25             }
     26         }
     27     }
     28     public static void cutBySum(int[] arr){
     29         for (int i = 0; i < arr.length; i++) {//计算总和
     30             sum+=arr[i];
     31         }
     32         //总和分成两个和,小的那边肯定在(0,sum/2)之间
     33         //只用计算一边的和,两边的差值 = sum - (onePlus<<2);
     34         for (int v = 1; v <= sum/2; v++) { 
     35             //一边的个数最多有length-1个,最少有一个
     36             for (int i = arr.length-1; i >= 1; i--) { 
     37                 //遍历数组中所有元素
     38                 for (int k = 1; k < arr.length; k++) {
     39                      if(v>=arr[k] && isOK[i-1][v-arr[k]]) 
     40                          isOK[i][v] = true;
     41                 }
     42             }
     43         }
     44     }
     45     
     46     /**
     47      * 递归,遍历解决 所有集 2^n
     48      */
     49     static List<Integer> list1 = new ArrayList<Integer>();//临时数组1
     50     static List<Integer> list2 = new ArrayList<Integer>();//临时数组2
     51     static Integer[] final1;//结果数组1
     52     static Integer[] final2;//结果数组2
     53     static int dValue=Integer.MAX_VALUE;//结果差值
     54     public static void cut(int[] arr, int cur){
     55         if (cur == arr.length) { //递归边界,数组里所有数都已确定位置
     56             int a=0,b=0;
     57             for (Integer i : list1) { //计算和
     58                 a+=i;
     59             }
     60             for (Integer i : list2) {//计算和
     61                 b+=i;
     62             }
     63             int c = a-b < 0 ? b-a : a-b;
     64             if(c<dValue){ //生成最后结果数组
     65                 dValue=c;
     66                 final1=(Integer[])list1.toArray(new Integer[list1.size()]);
     67                 final2=(Integer[])list2.toArray(new Integer[list2.size()]);
     68             }
     69             return;
     70         }
     71         list1.add(arr[cur]);//加入数组1的情况
     72         cut(arr, cur+1);
     73         list1.remove((Integer)arr[cur]);//还原
     74         
     75         list2.add(arr[cur]);//加入数组2的情况
     76         cut(arr, cur+1);
     77         list2.remove((Integer)arr[cur]);//还原
     78     }
     79     
     80     public static void main(String[] args) {
     81         int[] arr = {1,3,5,8,6,2,9,4,5,1,2,5};
     82         
     83         //方法一 2^n
     84         cut(arr, 0);
     85         for (int i : final1) {
     86             System.out.println("final1: "+i);
     87         }
     88         for (int i : final2) {
     89             System.out.println("final2: "+i);
     90         }
     91         System.out.println(dValue);
     92         
     93         //方法2 n^2*sum
     94         cutBySum(arr);
     95         dValue=Integer.MAX_VALUE;
     96         int onePlus=0;
     97         for (int i = 0; i < arr.length/2; i++) {
     98             for (int v = 0; v < sum; v++) {
     99                 if (isOK[i][v]) {
    100                     int tempDValue = sum - (v<<1);
    101                     if (tempDValue<dValue) {
    102                         dValue = tempDValue;
    103                         onePlus = v;
    104                     }
    105                 }
    106             }
    107         }
    108         System.out.println(dValue+"d和p"+onePlus);
    109     }
    110 }
    View Code

      这里提供了一种思路,当想使用动态规划时,但是Sk 和 Sk-1 的关系不能完全直接关联时,要换一种表示方法,可以尝试使用数组存储找的到还不是找不到

    2.两个数组的交集

      这个其实就是数据库的join(连接)

      先来看看ORACLE的做法:

      *嵌套循环连接(nested loop)

      1、 Oracle首先选择一张表作为连接的驱动表,这张表也称为外部表(Outer Table)。由驱动表进行驱动连接的表或数据源称为内部表(Inner Table)。 
      2、 提取驱动表中符合条件的记录,与被驱动表的连接列进行关联查询符合条件的记录。
      事实就是用驱动表来查询外部表,因为外部表经常有索引,所以查询(使用B树或二分查找)比较快。复杂度为0(nlog(n)+mlog(n))。
      *排序合并连接(merge sort join)
      排序合并连接的方法非常简单。在排序合并连接中是没有驱动表的概念的,两个互相连接的表按连接列的值先排序,排序完后形成的结果集再互相进行合并连接提取符合条件的记录。相比嵌套循环连接,排序合并连接比较适用于返回大数据量的结果。   
      MSJ其实就是排序后,用两个指针指向两个数组,逐个比较,不算排序,复杂度为0(mlog(m)+nlog(n)+n+m)。
     *
    哈希连接(Hash Join)
      当内存能够提供足够的空间时,哈希(HASH)连接是Oracle优化器通常的选择。哈希连接中,优化器根据统计信息,首先选择两个表中的小表,在内存中建立这张表的基于连接键的哈希表;优化器再扫描表连接中的大表,将大表中的数据与哈希表进行比较,如果有相关联的数据,则将数据添加到结果集中。
      典型的空间换时间的做法,对小表建立HASH,然后大表直接对照HASH,就能找到交集,算法复杂度为O(m+n)。  
      算法实现:
      1 /**
      2  * 求两个数组的交集
      3  * @author jslee
      4  *
      5  */
      6 public class Intersection {
      7 
      8     public static int[] result=new int[1000];
      9     static{
     10         for (int i =0; i<result.length; i++) {
     11             result[i]=Integer.MAX_VALUE;
     12         }
     13     }
     14     public static void main(String[] args) {
     15         int[] a = {5,7,9,3,176,7,4};
     16         int[] b = {8,7,1,3,14,56,23};
     17         interByHJ(a,b);
     18         for (int i = 0; i < result.length; i++) {
     19             if(result[i]==Integer.MAX_VALUE)
     20                 return;
     21             System.out.println(result[i]);
     22         }
     23     }
     24     
     25     //嵌套循环连接(nested loop)
     26     public static void interByNL(int[] a, int[] b) {
     27         if (a==null || b==null) 
     28             return;
     29         int[] bigArr,smallArr;
     30         //选择大表(外部表)排序,小表做驱动表
     31         if (b.length > a.length) {
     32             bigArr=b;
     33             smallArr=a;
     34         }else {
     35             bigArr=a;
     36             smallArr=b;
     37         }
     38         Arrays.sort(bigArr);
     39         int number=0;
     40         for (int i = 0; i < smallArr.length; i++) {
     41             if(binaryFind(bigArr,smallArr[i])){
     42                 result[number++]=smallArr[i];
     43             }
     44         }
     45     }
     46     
     47     //排序合并连接(merge sort join)
     48     public static void interByMSJ(int[] a, int[] b) {
     49         if (a==null || b==null) 
     50             return;
     51         Arrays.sort(a);
     52         Arrays.sort(b);
     53         int i=0,j=0;
     54         int number=0;
     55         while (i<a.length && j<b.length) {
     56             if (a[i]>b[j])
     57                 j++;
     58             else if (a[i]<b[j]) 
     59                 i++;
     60             else{
     61                 result[number++]=a[i];
     62                 i++;j++;
     63             }
     64         }
     65     }
     66     
     67     //哈希连接(Hash Join)
     68     public static void interByHJ(int[] a, int[] b) {
     69         if (a==null || b==null) 
     70             return;
     71         int[] bigArr,smallArr;
     72         //选择小表做Hash表
     73         if (b.length > a.length) {
     74             bigArr=b;
     75             smallArr=a;
     76         }else {
     77             bigArr=a;
     78             smallArr=b;
     79         }
     80         boolean[] hash = new boolean[1000];
     81         for (int i : smallArr) 
     82             hash[i]=true;
     83         int number=0;
     84         for (int i : bigArr) {
     85             if (hash[i]==true) {
     86                 result[number++]=i;
     87             }
     88         }
     89     }
     90     
     91     //二分查找
     92     public static boolean binaryFind(int[] arr, int k) {
     93         int i=0,j=arr.length-1;
     94         int mid = 0;
     95         while(i<j){
     96             mid = i+((j-i)>>>1);
     97             if (arr[mid]>k) 
     98                 j=mid-1;
     99             else if (arr[mid]<k)
    100                 i=mid+1;
    101             else
    102                 return true;
    103         }
    104         if (arr[i]!=k) 
    105             return false;
    106         else
    107             return true;
    108     }
    109 
    110 }
    View Code

    3.子数组和最大值

      1).分治法:分成前半段和后半段,然后最大值则是以下三种情况的最大值:

      *(start,mid-1)的最大子数组和

      *(mid+1,end)的最大子数组和

      *跨过前半和后半数组的子数组,也就是说包含 mid 的值。

      前两个递归求,第三个只需遍历一遍数组即可求组。

      2).动态规划 -> 递归

      start[i] :arr[i] - arr[end] 的包含arr[i]的最大子数组和

      all[i] :arr[i] - arr[end] 的最大子数组和

      因为子数据具有连续性,所以构建一个新变量 start[i] 来动态规划!

      start[i] = max(arr[i], arr[i]+start[i+1]);

      all[i] = max(start[i], all[i+1]);

      由于只需算出最大的值,所以直接优化成两个变量即可。

     1     public static int max(int a,int b){
     2         return a>b ? a : b;
     3     }
     4     public static int subMaxByBinary(int[] arr, int start, int end){
     5         if (arr==null) 
     6             return -1;
     7         if (end == start) 
     8             return arr[end];
     9         int mid = start+(end-start>>>1);
    10         int midSum=arr[mid];
    11         int tempSum=arr[mid];
    12         for (int i = mid-1; i >= start; i--) {
    13             tempSum += arr[i];
    14             if (tempSum > midSum) 
    15                 midSum = tempSum;
    16         }
    17         tempSum=midSum;
    18         for (int i = mid+1; i <= end; i++) {
    19             tempSum += arr[i];
    20             if (tempSum > midSum) 
    21                 midSum = tempSum;
    22         }
    23         return max(max(subMaxByBinary(arr,start,mid-1),
    24                 subMaxByBinary(arr,mid+1,end)),midSum);
    25     }
    26     
    27     public static int subMaxByDP(int[] arr){
    28         if (arr==null) 
    29             return -1;
    30         int start = arr[arr.length-1];
    31         int all = start;
    32         for (int i = arr.length-2; i >= 0; i--) {
    33             start = max(arr[i],arr[i]+start);
    34             all = max(start, all);
    35         }
    36         return all;
    37     }
    View Code

    4.最大出现次数的数(超过一半)

      方法有多种:*排序后选,*快速排序的思路,*数组特点

      这里选得利用数组特点的方法:

     1     public static int findMax(int[] arr) {
     2         if (arr == null) 
     3             return Integer.MIN_VALUE;
     4         //使用两个变量记录当前存活的数字, 和 出现的次数
     5         int num=arr[0],time=1;
     6         //超过一半的数字肯定要比其它数字的总和多
     7         //所以最后存活的肯定是那个数字
     8         for (int i = 1; i < arr.length; i++) {
     9             /*if (time==0) {  //前面判断和后面判断一样的效果
    10                 num = arr[i];
    11                 time = 1;
    12             }*/
    13             if (arr[i]==num)  //次数加1
    14                 time++;
    15             else
    16                 time--;    //不一样次数减1,所以其它数肯定减不过那个数
    17             if (time==-1) { //time次数为负时,此时应该更换当前数字
    18                 num = arr[i];
    19                 time = 1;
    20             }
    21         }
    22         return num;
    23     }
    View Code

    5.约瑟夫环问题

      约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

      eg:n = 9,k = 1,m = 5【解答】出局人的顺序为5,1,7,4,3,6,9,2,8。

      1).直接先是用boolean数组来模拟整个游戏过程

     1     public static void countJos(int n, int m,int k) {
     2         boolean[] result = new boolean[n+1];//排列的最后值,为了下标加1,好处理
     3         int remain=n;        //剩下的总数
     4         int count=0,index=k;
     5         while(true){
     6             if (index==n+1)     //越界了
     7                 index=1;
     8             if (result[index]) { //已被选过 就跳过了
     9                 index++;
    10                 continue;
    11             }
    12             if (remain==1) {  //最后一个了。
    13                 System.out.println(index);
    14                 break;
    15             }
    16             ++count;    
    17             if (count == m) {     //数到要求了
    18                 count=0;
    19                 result[index] = true; //标记它为已被选状态
    20                 System.out.println(index);//打印 
    21                 remain--;
    22             }
    23             index++;
    24         }
    25     }
    View Code

      2).数学策略

     递推公式
    f[1]=0;
    f[i]=(f[i-1]+m)%i;  (i>1)
     递推后最后一个为最后留下的值。
    1     public static void countJosBymath(int n, int m) {
    2         int s=0;
    3         for (int i=2; i<=n; i++)
    4             s=(s+m)%i;
    5         s++;
    6         System.out.println("最后一个为"+s);
    7     }
    View Code

      这种有重复子问题的问题,一定要往递归和数学方向想!。

    6.字符串剔除

      两个字符串A、B。从A中剔除存在于B中的字符。比如A=“hello world”,B="er",那么剔除之后A变为"hllowold"。空间复杂度要求是O(1),时间复杂度越优越好。

      位Hash映射的方法:

     1     /**
     2      * 两个字符串A、B。从A中剔除存在于B中的字符。
     3      * 比如A=“hello world”,B="er",那么剔除之后A变为"hllowold"。
     4      * 空间复杂度要求是O(1),时间复杂度越优越好。
     5      * @param str1
     6      * @param str2
     7      * @return
     8      */
     9     public static String elimChar(String str1, String str2) {
    10         long[] hash=new long[2]; //两个long构成的128的位图空间
    11         for (int i = 0; i < str2.length(); i++) { //hash映射
    12             byte b = (byte) str2.charAt(i);
    13             if (b<=64)                 //java的默认运算是int
    14                 hash[0] |= (long)1<<b;//注意1必须强制转换为long
    15             else
    16                 hash[1] |= (long)1<<(b-64);
    17         }
    18         
    19         int index=0;    //使用一个index记录现有多少元素
    20         char[] arr = new char[str1.length()];//结果数组
    21         for (int i = 0; i < str1.length(); i++) {//计算是否有包含
    22             byte b = (byte) str1.charAt(i);
    23             if (b<=64) {
    24                 if (((hash[0]>>>b)&1) == 0) { //注意为无符号位移
    25                     arr[index] = str1.charAt(i);
    26                     index++;
    27                 }
    28             }
    29             else{
    30                 if (((hash[1]>>>(b-64))&1) == 0) {
    31                     arr[index] = str1.charAt(i);
    32                     index++;
    33                 }
    34             }
    35         }
    36         return new String(arr);
    37     }
    View Code

    7.trie树

      Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

      建立+查询在trie中是可以同时执行的(可以看到,建树和查询极其相似),建立的过程也就可以成为查询的过程。

      关于节点,如果只有26个字母,可以使用一个26的长度的数组来表示子节点(类似Hash)。

     1 /*
     2  * trie树  建树,查找的实现
     3  */
     4 public class Trie {
     5     //树节点
     6     public class Node{
     7         public char key; //char值
     8         public List<Node> next = new ArrayList<Node>();//儿子们
     9         public int time = 1; //此值为此节点重复次数
    10         public Node(char key) {
    11             this.key = key;        
    12         }
    13     }
    14     //建树
    15     public Node buildTrie(String[] strArr, Node preRoot) {
    16         Node root = preRoot; //可以使用preRoot来扩建树
    17         if(root == null)    //也可以新建树根节点,根节点为127
    18             root = new Node((char)127);
    19         for (String str : strArr) {
    20             Node node = root;    //循环中的当前节点
    21             for (int i = 0; i < str.length(); i++) {
    22                 char c = str.charAt(i);
    23                 boolean bool = true;    //此值为是否树中已有节点
    24                 for (Node iter : node.next) {//遍历当前节点儿子
    25                     if (iter.key == c) {//节点已存在,不用新建节点
    26                         node = iter;
    27                         node.time++;//增加重复值
    28                         bool = false;
    29                         break;
    30                     }
    31                 }
    32                 if (bool) {//新建节点
    33                     Node temp = node;
    34                     node = new Node(c);
    35                     temp.next.add(node);//添加为当前节点的儿子
    36                 }
    37             }
    38         }
    39         return root;
    40     }
    41     //深度遍历
    42     public void depthLook(Node node){
    43         if (node == null) return;
    44         if (node.key != (char)127) //不为根节点,打印
    45             System.out.print(node.key);
    46         if (node.next.size() == 0) { //没儿子了,为叶节点,打印换行
    47             System.out.println();
    48             return;
    49         }
    50         for (Node iter : node.next) {//递归
    51             depthLook(iter);
    52         }
    53     }
    54     //查找
    55     public boolean findStr(String str, Node root){
    56         Node node = root;
    57         for (int i = 0; i < str.length(); i++) {
    58             char c = str.charAt(i);
    59             boolean bool = true;//此值为是否树中已有节点
    60             for (Node iter : node.next) {
    61                 if (iter.key == c) {
    62                     node = iter;
    63                     bool = false;
    64                     break;
    65                 }
    66             }
    67             if (bool) {//如果没有节点,直接返回没找到
    68                 return false;
    69             }
    70         }
    71         return true;
    72     }
    73     public static void main(String[] args) {
    74         Trie tr = new Trie();
    75         String[] strArr = {"hello","goodbye","happy","yesterday"};
    76         Node root = tr.buildTrie(strArr,null);
    77         tr.depthLook(root);
    78         boolean bool = tr.findStr("happya", root);
    79         System.out.println(bool ? "找的到" : "没找到啊");
    80     }
    81 }
    View Code

    8.字符串的模拟加减法

      1).题目描述(50分): 
      通过键盘输入100以内正整数的加、减运算式,请编写一个程序输出运算结果字符串。
      输入字符串的格式为:“操作数1 运算符 操作数2”,“操作数”与“运算符”之间以一个空格隔开。
       补充说明:
      1. 操作数为正整数,不需要考虑计算结果溢出的情况。
      2. 若输入算式格式错误,输出结果为“0”。
      要求实现函数: 
      void arithmetic(const char *pInputStr, long lInputLen, char *pOutputStr);
      【输入】 pInputStr:  输入字符串
               lInputLen:  输入字符串长度         
      【输出】 pOutputStr: 输出字符串,空间已经开辟好,与输入字符串等长;
      【注意】只需要完成该函数功能算法,中间不需要有任何IO的输入输出
      2).当看到这个道题,第一反应很简单。。可是就是这种字符串的题,反而容易出错,主要是数组的越界检查

       注意代码中的错误注释

      1 package arithmetic;
      2 
      3 /**
      4  * 通过键盘输入100以内正整数的加、减运算式,请编写一个程序输出运算结果字符串。
      5     输入字符串的格式为:“操作数1 运算符 操作数2”,“操作数”与“运算符”之间以一个空格隔开。
      6     补充说明:
      7     1. 操作数为正整数,不需要考虑计算结果溢出的情况。
      8     2. 若输入算式格式错误,输出结果为“0”。
      9     要求实现函数: 
     10     void arithmetic(const char *pInputStr, long lInputLen, char *pOutputStr);
     11     【输入】 pInputStr:  输入字符串
     12          lInputLen:  输入字符串长度         
     13     【输出】 pOutputStr: 输出字符串,空间已经开辟好,与输入字符串等长;
     14     【注意】只需要完成该函数功能算法,中间不需要有任何IO的输入输出
     15     示例 
     16     输入:“4 + 7”  输出:“11”
     17     输入:“4 - 7”  输出:“-3”
     18     输入:“9 ++ 7”  输出:“0” 注:格式错误
     19  * @author hasee
     20  *
     21  */
     22 public class Arithmetic {
     23 
     24     public static void main(String[] args) {
     25         char[] in = "9 ++ 7".toCharArray();
     26         char[] out = new char[5];
     27         arith(in, out);
     28         for (char c : out) {
     29             System.out.print(c);
     30         }
     31         System.out.println();
     32     }
     33     
     34     public static void arith(char[] in, char[] out){
     35         try {    //错误!!没有考虑每一步都有可能数组越界
     36             if (in==null||out==null) 
     37                 return;
     38             int index=0;
     39             int a,b;
     40             char sign;
     41             //计算a,各种检查
     42             if (in[index]<'0'||in[index]>'9') {
     43                 out[0] = '0';
     44                 return;
     45             }
     46             a = in[index++] - '0';
     47             if (in[index]!=' ') {
     48                 if (in[index]<'0'||in[index]>'9') {
     49                     out[0] = '0';
     50                     return;
     51                 }
     52                 a = a*10 + in[index++]-'0';
     53             }
     54             //空格
     55             if (in[index] != ' ') {
     56                 out[0] = '0';
     57                 return;
     58             }
     59             index++;
     60             //sign符号
     61             if (in[index] != '+' && in[index] != '-') {
     62                 out[0] = '0';
     63                 return;
     64             }
     65             sign = in[index++];
     66             //空格
     67             if (in[index] != ' ') {
     68                 out[0] = '0';
     69                 return;
     70             }
     71             index++;
     72             //b数字计算,各种检查
     73             if (in[index]<'0'||in[index]>'9') {
     74                 out[0] = '0';
     75                 return;
     76             }
     77             
     78             b = in[index] - '0';
     79             //错误! 没有考虑数组过界,这种情况越界不算错误,10以下的时候
     80             if (in.length-1 > index) {  
     81                 index++;
     82                 if (in[index]!=' ') {
     83                     if (in[index]<'0'||in[index]>'9') {
     84                         out[0] = '0';
     85                         return;
     86                     }
     87                     b = b*10 + in[index]-'0';
     88                 }
     89             }
     90             
     91             //计算最终结果
     92             int c;
     93             if (sign == '+') 
     94                 c = a+b;
     95             else
     96                 c = a-b;   
     97             //错误!!没有考虑String.valueOf(c).toCharArray()返回的新对象,
     98             //out为临时指针,函数完了就销毁。
     99             char[] temp = String.valueOf(c).toCharArray();  
    100             for (int i = 0; i < temp.length; i++) {
    101                 out[i] = temp[i];
    102             }
    103             return;
    104         } catch (ArrayIndexOutOfBoundsException e) {
    105             out[0] = '0';
    106             return;
    107         }
    108     }
    109     
    110     
    111 }
    View Code

    参考:编程之美

  • 相关阅读:
    Logstash配置文件介绍
    ElasticSearch搜索介绍四
    ElasticSearch文档操作介绍三
    ElasticSearch集群介绍二
    ElasticSearch入门介绍一
    Curl中的参数知多少
    sed命令使用介绍(转载)
    实例方法、类方法、静态方法
    函数概述,参数,可变参数,关键字参数,组合参数,递归函数
    startswith()函数与endswith()函数判断文件的开头和结尾
  • 原文地址:https://www.cnblogs.com/jslee/p/3446593.html
Copyright © 2011-2022 走看看