一个忽视特殊情况和数学规律而引发的血案—自实现pow函数

  Leetcode上遇到的一题。初学C语言的时候写过,但当时写得当然是最简单的了:

double pow(double x, int n)
{
    double r=1.;
    while(n--) r*=x;
    return r; 
}

    是的,虽然这代码是现在写的,但是初学C的我绝对写得出这样的代码。下面考虑特殊情况:

1  判断两个double型变量是否相等不能直接用==,而是应该这么写:

inline bool is0(double a)
{
    return a<1E-10 && -a<1E-10;
}

    从而a=b即is0(a-b). 至于1E-10合适呢?从6到10的我倒是都见过。就这题来说,设成10就够通过OJ。

2  x=0时结果为0.

3  x=1时结果为1.

4  x=-1时结果为1或-1. 实际上笔者做题时漏掉了这一项 = = |||

5  n=0时结果为1或-1.

否则,老老实实算吧。但是注意:

6  对于小数的幂会越算越接近0,如果足够小了,就可以停止了。换而言之,如果收敛了就可以停止了,如何判断收敛呢?简单,看r*=x前后有没有明显不同。(其实笔者还漏掉了这个 囧|||)

于是:

 1     double pow(double x, int n) {
 2         if(is0(x)) return 0;
 3         if(is0(x-1.)) return 1;
 4         if(is0(x+1.))
 5             if(n & 1) return -1;
 6             else return 1;
 7         bool sgn = 1;
 8         if(x<-1E-9 && (n & 1)) sgn = 0;
 9         if(n==0)
10             if(sgn) return 1;
11             else return 0;
12         double r = 1.;
13         double t;
14         if(n>0)
15         {
16             while(n-- && !is0(t-r)){
17                 t = r;
18                 r*=x;
19             }
20             return r;
21         }
22         else
23         {
24             while(n++ && !is0(t-r)){
25                 t = r;
26                 r/=x;
27             }
28             return r;
29         }
30     }

提交,通过,看下runtime是128ms!大家的CPP程序都是10以内的成绩,128那是python跑出来需要的时间呢。点开tag发现,这题最终应该用二分的方法解:

n是偶数时:

a^n = a^{n/2} * a^{n/2};

n是奇数时:

a^n = a^{(n-1)/2} * a^{(n-1)/2} * a; 

写成递归再简单不过:

double powd(double x, int n)
{
    if(n==0) return 1;
    if(n==1) return x;
    double r = powd(x, n/2) * powd(x, n/2);
    if(n%2) return  r * x;
    return r;
}

递归毕竟太慢而且附带着空间上的开销,写成非递归吧。

考虑到n是整型,实际上也就是

a^n = a^{n/2} * a^{n/2} * a; 

总而言之:

r = a^{n/2} * a^{n/2},如果n是奇数,则r*=a;

于是:

    double powb(double x, int n)
    {
        double r = 1.;
        while(n)
        {
            if(n & 1) r*=x;
            x*=x;
            n/=2;
        }
        return r;
    }
    
    double pow(double x, int n) {
        if(is0(x)) return 0;
        if(is0(x-1.)) return 1;
        if(is0(x+1.))
            if(n & 1) return -1;
            else return 1;
        bool sgn = 1;
        if(x<-1E-9 && (n & 1)) sgn = 0;
        if(n==0)
            if(sgn) return 1;
            else return 0;
        double r = powb(x, abs(n));
        if(n>0)
        {
            return r;
        }
        else
        {
            return 1/r;
        }
    }

    O(lgn). 提交,16ms走起!

    是的~ 我就是对那段特殊情况的判断还恋恋不舍~ 毕竟那是血案得来的教训~ 前期对于特殊情况的考虑一个都不能少!

    此外,对于算法问题:

                                        能二分的尽力想如何二分;

                                        能用非递归的绝不用递归,除非非递归实在太麻烦;

                                        能不用回溯的尽量不用(比如八皇后问题,虽然回溯和非回溯两个算法都是O(n^3))

                                        回溯能剪枝就剪枝(比如N-Sum问题)

                                        用DP的时候,数组能有多小用多小。DP的每次迭代都不复杂,往往只需要用到少数几个数

                                        ... 以上!

posted @ 2014-12-18 21:02  chng  阅读(113)  评论(0编辑  收藏  举报
BackToTop