• 常见算法总结(1)


    大约两个月前一位朋友问我一道他同事的面试题目:一个含有无重复元素的集合,找出它所有的子集。例如{1,2}的所有集合是{}, {1}, {2}, {1, 2}.

    当时我预料到了这道题目的算法时间复杂度为O(2^n), 但是并没有写出代码来。前两天无意间又试着做了一下这道题目,然后接受查找的资料,原来这是一个专门的算法问题。学名为powerset.

    代码如下:

     1 /*
     2      * 该算法的基本原理是:将集合分成前后两个部分:
     3      * 前一部分是已经固定了的,后一部分为即将固定的,
     4      * 然后将即将固定的添加到已经固定的集合的后面。
     5      * 然后采用递归的方式,欲求{1,n-1},必先求{2, n-2},
     6      * 之后仿效类推,直到先求出{n-1, 1}
     7      */
     8     public static <T> Set<Set<T>> getPowerSet(Set<T> originalSet) {
     9         Set<Set<T>> sets = new HashSet<Set<T>>();
    10         if (originalSet.isEmpty()) {
    11             sets.add(new HashSet<T>());
    12             return sets;
    13         }
    14         List<T> list = new ArrayList<T>(originalSet);
    15         T head = list.get(0);
    16         Set<T> rest = new HashSet<T>(list.subList(1, list.size()));
    17         for (Set<T> set : getPowerSet(rest)) {
    18             Set<T> newSet = new HashSet<T>();
    19             newSet.add(head);
    20             newSet.addAll(set);
    21             sets.add(newSet);
    22             sets.add(set);
    23         }
    24         return sets;
    25     }
    View Code

    这个算法采用了泛型的方式,可以求整型、字符串和字符数组的子组合问题。

    然后自己还写了个判断一个数字是否是素数的程序,之所以再写了一个是因为自己以前写的算法时间表复杂度为O(n^1/2),这个还是有可以优化的地方,比如下面这种算法:

     1 public static boolean isPrime(long n) {
     2         if(n < 2) {
     3             return false;
     4         }
     5         if(n == 2 || n== 3){
     6             return true;
     7         }
     8         if(n%2 == 0 || n%3==0){
     9             return false;
    10         }
    11         long sqrt=(long) (Math.sqrt(n)+1);
    12         for (long i = 6L; i <=sqrt; i+=6L) {
    13             if(n%(i-1) == 0 || n%(n+1)==0){
    14                 return false;
    15             }
    16         }
    17         return true;
    18     }
    View Code

    这种算法使用了判断诸如2, 3, 5, 7, 11, 13...这些比较小的素数是否是n的因子来判断n是否是素数,明显地提升了算法的时间复杂度。

    还有一道题目是求数组的最大和的问题。常见的解法是采用动态规划跟记忆算法,采用O(n^2)的时间来循环求解,但是这里有一个复杂度为O(n)的解法,代码如下:

     1 /*该算法的时间复杂度为: O(n)*/
     2     public static int maxSubsum(int[] array) {
     3         int maxSum=0;
     4         int tempSum=0;
     5         for (int i = 0; i < array.length; i++) {
     6             tempSum+=array[i];
     7             if(tempSum > maxSum){
     8                 maxSum=tempSum;
     9             }else if (tempSum < 0) {
    10                 tempSum=0;
    11             }
    12         }
    13         return maxSum;
    14     }
    View Code

    的确,这种解法真的非常巧妙。

    今天对这个解法进行了一些功能扩充,就是要求将具有最大各的子数组的起始位置找出来,具体代码如下:

     1 /* 该算法的时间复杂度为: O(n) */
     2     public static int maxSubArraySum2(int[] array) {
     3         if (array == null) {
     4             return -1;
     5         }
     6         int maxSum = 0;
     7         int tempSum = 0;
     8         int start = 0, end = 0, curStart = 0;
     9         for (int i = 0; i < array.length; i++) {
    10             if (tempSum < 0) {
    11                 tempSum = array[i];
    12                 curStart = i;
    13             } else {
    14                 tempSum += array[i];
    15             }
    16             if (tempSum > maxSum) {
    17                 maxSum = tempSum;
    18                 start = curStart;
    19                 end = i;
    20             }
    21         }
    22         System.out.println("Start---" + start + ",  End---" + end);
    23         return maxSum;
    24     }
    View Code

    思路是一样的,只是因为要找出子数组的索引,所以对代码的基本结构进行了一些调整,但逻辑跟时间复杂度并没有发生变化。

    还有一个算法题目是:自己实现一个power函数。在java的Math库的,有pow(double base, double exp)方法,返回base的exp次方。一般有两种算法,先看一下只对int进行处理的循环和递归算法:

     1 /* when exp is bigger than zero */
     2     public static int ipow1(int base, int exp) {
     3         int result = 1;
     4         while (exp != 0) {
     5             if ((exp & 1) == 1) {
     6                 result *= base;
     7             }
     8             exp >>= 1;
     9             base *= base;
    10         }
    11         return result;
    12     }
    13 
    14     /* when exp is bigger than zero */
    15     public static long ipow2(long base, long exp) {
    16         if (exp == 0) {
    17             return 1;
    18         }
    19         if (exp == 1) {
    20             return base;
    21         }
    22 
    23         if (exp % 2 == 0) {
    24             long half = ipow2(base, exp / 2);
    25             return half * half;
    26         } else {
    27             long half = ipow2(base, (exp - 1) / 2);
    28             return base * half * half;
    29         }
    30     }
    View Code

    以上的两种方法都是在base和exp不小于0的情况下进行的讨论。

    后者是递归的实现方式:分成了exp是奇数还是偶数两种情况进行讨论。关于递归,有一个经典名言:“In order to understand recursion, you must first understand recursion.”。就像GNU的定义一样,自己去理解吧。

    下面看一下对base和exp可以是任意数值(exp是int)的情况下循环方式的实现:

     1 /* when exp or base could be smaller than zero */
     2     public static double ipow3(double base, int exp) {
     3         if (base == 0.0D && exp < 0) {
     4             throw new ArithmeticException("Dividend can't be zero");
     5         }
     6         int tmpExp = Math.abs(exp);
     7         double temBase = Math.abs(base);
     8         double tempResult = 1;
     9         while (tmpExp != 0) {
    10             if ((tmpExp & 1) == 1) {
    11                 tempResult *= temBase;
    12             }
    13             tmpExp >>= 1;
    14             temBase *= temBase;
    15         }
    16         boolean isBaseNegative = base < 0;
    17         boolean isExpNegative = exp < 0;
    18         boolean isExpEven = (exp % 2 == 0);
    19         if (!isBaseNegative && !isExpNegative) {
    20             return tempResult;
    21         } else if (!isBaseNegative && isExpNegative) {
    22             return 1 / tempResult;
    23         } else if (isBaseNegative && !isExpNegative) {
    24             if (isExpEven) {
    25                 return tempResult;
    26             } else {
    27                 return -tempResult;
    28             }
    29         } else {
    30             if (isExpEven) {
    31                 return 1 / tempResult;
    32             } else {
    33                 return -1 / tempResult;
    34             }
    35         }
    36     }
    View Code

     这里增加了对base和exp为负数时的讨论,以及base=0而exp<0的异常抛出。

    暂时只想到了这么多,以后再想到了会及时的添加上去。

  • 相关阅读:
    redis命令
    linux命令行任务管理
    tomcat修改内存
    Python调用shell
    取消myeclipse自动进入workspace
    解决Myeclipse编译不生成.class文件问题
    Manacher回文串算法学习记录
    青少年如何使用 Python 开始游戏开发
    对 Linux 专家非常有用的 20 个命令
    对中级 Linux 用户非常有用的 20 个命令
  • 原文地址:https://www.cnblogs.com/littlepanpc/p/3811373.html
Copyright © 2020-2023  润新知