leetcode002-数值处理Reverse Integer,Pow(x,n), atoi, Divide Two Integers

反转正数和求指数幂的算法都挺简单的,写这两道题是为了记录一些在进行数值处理时要注意的边界条件和细节。

Reverse Integer

Reverse digits of an integer.

Example1: x = 123, return 321
Example2: x = -123, return -321

基本思路:通过取模运算每次得到数字最右边的一位a,然后将之前得到的数字通过乘10向左移位,最后加上a。
在做题之前有几个问题是要考虑的:
1.如果输入的是负数,算法的正确性还可以保证吗?
2.如果输入后的整数反转后溢出怎么办?

针对第一个问题,我用c++实验了一下,负数取模结果的正负号和被除数保持了一致,-123按照以上算法三次取模的结果分别是-3,-2,-1.按这么解释,输入即使是负数也不影响。
针对第二个问题,如 2147483647 反转后结果是7463847412会超出int的表示范围,最直接的办法是用long long保存累加的结果,如果一旦超出INT_MAX或者超出INT_MIN,就返回0。
成功提交后,发现提供的答案里是直接用int存储结果的,答案里是利用abs(目前暂时的累加结果)和(INT_MAX/10)比较大小来判断溢出,因为每次的循环里是需要将结果扩大10倍的。之后在其他博客里看到一个挺有趣的做法,就是如果一个结果乘10后再除以10不等于原来的结果的话就说明发生了溢出。



class Solution {
public:
    int reverse(int x) {
        int ans = 0;
        while (x!=0) {
            int temp = ans*10+x%10;
            if (temp/10 != ans) return 0;
            ans = temp;
            x = x/10; 
        }
        return ans;
    }
};


Pow(x, n)

Implement pow(xn).


基本思路:当n为偶数时,x^n = x^(n/2)*x^(n/2). 当n为奇数时,x^n = x^(n/2)*x(n/2)*x

在做题之前有几个问题是要考虑的:

1.0的0次方是什么……我百度了一下好像是不存在的……

2.n有可能是负数……(我一开始就忘了)

3.n是个int,如果它是负数的话,直接取反会溢出!(INTMAX:2147483647 INT_MIN:-2147483648


class Solution {
public:
    double myPow(double x, int n) {
        if (n == 0) return 1.0;
        if (n < 0) {
            if (n == INT_MIN) 
            return 1.0/(helper(x,-(n+1))*x);
            else
            return 1.0/helper(x,-n);
        } else {
            return helper(x,n);
        }
        return 0;
    }
    double helper(double x, int n) {
        if (n == 0) {
            return 1.0;
        }
        else if (n % 2 == 0) {
            double half = helper(x,n/2);
            return half*half;
        } else {
            double half = helper(x,(n-1)/2);
            return half*half*x;
        }
    }
};

String to Integer (atoi)

Implement atoi to convert a string to an integer.


基本思路:取出合法的数字串,从数字串最后一位向前遍历,然后根据它们所在的位数累加结果。

在做题之前有几个问题是要考虑的:

1.在字符串最开头的无效空格要去掉

2.字符串代表的数字的正负

3.由于返回的结果是整型,所以要考虑溢出

4.不能够被转换的类型,举几个代表性的例子:"-+2","-a234". 以下可能会有人以为不行但这道题是可以转换:"-2abc"->-2 


以下是我AC的答案,但这个其实写得挺糟糕,因为很冗余并且效率不高,就一个个部分按部就班的完成。尤其是我在判断溢出的部分相当累赘,我利用的是字符串的字典序去判断的。并且其实这些步骤其实是可以在一趟内完成的……

***我在discuss板块里看见一个很精简的答案,主要是如果在转换数字时,如果从第一位开始遍历,那么每次只要把目前累加的结果乘以10再加上当前数字就可以了,那么就可以用到Reverse Integer里面提到的对溢出的判断。建议大家看一下https://leetcode.com/problems/string-to-integer-atoi/discuss/

int myAtoi(string str) {
        if (str.size() == 0) {
             return 0;
        }
        
        int i = 0;
        bool neg = false;
        int ans = 0;
        //  去掉第一个非空格前的所有空格
        bool allspace =false;
        while (str[i] == ' ') {
            if (i+1 < str.size()) {
                i++;
            } else {
                allspace = true;
                break;
            }
        }
        if (allspace) return 0;
        //  判断数字的正负
        if (str[i] == '-') {
            neg = true;
            i++;
        } else if(str[i] == '+') {
            i++;
        }
        //  截出能够成功转换为数字的部分
        string numpart = "";
        while (i < str.size() && str[i] >= '0' && str[i] <= '9' ) {
            numpart += str[i];
            i++;
        }
        if (numpart == "") return 0;
        //  去掉一串数字前无效的0
        int j = 0;
        while ( j< numpart.size() && numpart[j] == '0') {
            j++;
        }
        if (j == numpart.size()) return 0;
        //  处理溢出
        if (numpart.size() <= 10) {
            if (neg && numpart.size() == 10  &&numpart.substr(j) >= "2147483648") {
                return INT_MIN;
            }
            if (!neg && numpart.size() == 10  &&numpart.substr(j) >= "2147483647") {
                return INT_MAX;
            }
        } else {
            if (neg) return INT_MIN;
            else return INT_MAX;
        }
        //  将数字根据它所在的位数累加起来
        int pos = 1;
        for (int k = numpart.size()-1; k >= j; k--) {
            ans += (numpart[k]-'0')*pos;
            pos *= 10;
        }
        if (neg) {
            ans *= -1;
        }
        return ans;
    }


Divide Two Integers

Divide two integers without using multiplication, division and mod operator.

If it is overflow, return MAX_INT.

基本思路:由于不给用乘、除、取模运算符,所以只能不断通过从被除数中减去除数直到被除数小于除数,并且记录减去了多少个除数来获得结果。比较简单的方法是每次都减去一个除数,而加快这个过程的方法是每次减去多个除数,怎么减呢?利用位移运算符将除数尽可能多地左移k次(左移后的结果不能超过被除数),相当于是divisor*pow(2,k), 可以看成被除数中包括了pow(2,k)个divisor,那么在这次中我们将可以减去pow(2,k)个除数。再利用同样的方法,在剩余的被除数中不断减去2的某个次幂的除数直到被除数无法被除数整除。

在做题之前有几个问题是要考虑的:
1.结果正负的判断
2.divisor为0,返回INT_MAX
3.divisor或dividend为INT_MIN时,取绝对值会溢出
4.divisor不断左移的话会溢出

以下是AC的代码
int divide(int dividend, int divisor) {
        // overflow?
        if (dividend == INT_MIN && divisor == -1) return INT_MAX;
        if (divisor == 0) return INT_MAX;
        if (divisor == INT_MIN) {
            return dividend == INT_MIN ? 1 : 0;
        }
        // positive?
        bool Negative = (dividend ^ divisor) >> 31;
        long divident = labs(dividend);
        divisor = abs(divisor);
        int times = 0;
        int t = 0;
        while (divident >= divisor) {
            while (divident >= (divisor << times)) {
                int d = (divisor << (times+1));
                if (d/2 == (divisor << times) && divident >= d) times++;
                else break;
            }
                    
            t += 1<

1.除数和被除数的最高位是否相同,异或结果为1表示不同,即结果为负
2.当dividend是INT_MIN,除数是-1时,结果与最小值取绝对值的效果相同会溢出
3.除0不解释了
4.divisor为INT_MIN时的答案不是0就是1
5.dividend为INT_MIN时的答案有很多,所以必须转成其绝对值时,要用long类型来保证不会溢出
6.右移不能超过30位,否则会溢出


参考资料:http://www.cnblogs.com/grandyang/p/4125588.html

                  https://leetcode.com/problems/string-to-integer-atoi/discuss/

                  https://discuss.leetcode.com/topic/15568/detailed-explained-8ms-c-solution

你可能感兴趣的:(leetcode002-数值处理Reverse Integer,Pow(x,n), atoi, Divide Two Integers)