LintCode数学题总结

LC上有一些标记为mathematics的题目,基本就是纯数学题或者数字题,也是需要掌握的。

517. Ugly Number

丑数,是一个正数,并且因子只包含2、3、5。此外,1也算丑数。0和负数都不算丑数。

    public boolean isUgly(int num) {
        int[] check = {2, 3, 5};
        if (num == 1) {
            return true;
        }
        if (num <= 0) {
            return false;
        }
        while (num > 1) {
            int divider = -1;
            for (int i = 0; i < 3; i++) {
                if (num % check[i] == 0) {
                    divider = check[i];
                    break;
                }
            }
            if (divider == -1) {
                return false;
            } else {
                num /= divider;
            }
        }
        return true;
    }


488. Happy Number

判断一个数是不是快乐数。循环把一个数字的各个位的数字平方相加,直到结果为1为止,这就是快乐数,如果不满足就不是。注意有个技巧,就是循环判断的时候,如果一个数字循环重复出现了那么这个数肯定就不是快乐数,就可以通过这个条件来终止循环,Java中可以用HashSet表示,把每次产生的数加入set,如果新的数字已经在set中出现了,那就返回false

    private ArrayList<Integer> toArray(int n) {
        ArrayList<Integer> res = new ArrayList();
        while (n >= 1) {
            res.add(n % 10);
            n /= 10;
        }
        return res;
    }
    public boolean isHappy(int n) {
        if (n <= 0) {
            return false;
        }
        HashSet<Integer> set = new HashSet<Integer>();
        set.add(n);
        while (n != 1) {
            ArrayList<Integer> arr = toArray(n);
            n = 0;
            for (Integer num : arr) {
                n += (num * num);
            }
            if (set.contains(n)) {
                return false;
            } else {
                set.add(n);
            }
        }
        return true;
    }


2. Trailing Zeros

判断一个阶乘的末尾有多少个0。这道题肯定不能来蛮的,不然会超时,肯定要找规律来做。我们来看下如何使得末尾能构成0呢,

比如一个数乘以10,那它的末尾就有一个0了。而10 = 2 * 5。所以只要因子里有一个10,末尾就有一个0。换句话说,只要因子里面有一个2和一个5,末尾就有1个0了。

如果末尾要有2个0,一个数乘以100就可以使得末尾有2个0了。而100 = 10 * 10 = 2 * 2 * 5 * 5。

以此类推,我们就可以发现一个规律,末尾要有一个0,就找因子里有没有一对(2,5),而根据归纳法可以证明,在一个数的阶乘的因子里,2的数量是远远多于5的。所以关键就在于因子里面有多少个5的存在。如果从1到n逐个遍历去统计5的因子的个数的话,那是会超时的。下面这个方法可以巧妙地判断:

n = 5: There is one 5 and 3 2s in prime factors of 5! (2 * 2 * 2 * 3 * 5). So count of trailing 0s is 1.

n = 11: There are two 5s and three 2s in prime factors of 11! (2 8 * 34 * 52 * 7). So count of trailing 0s is 2.

We can easily observe that the number of 2s in prime factors is always more than or equal to the number of 5s. So if we count 5s in prime factors, we are done. How to count total number of 5s in prime factors of n!? A simple way is to calculate floor(n/5). For example, 7! has one 5, 10! has two 5s. It is done yet, there is one more thing to consider. Numbers like 25, 125, etc have more than one 5. For example if we consider 28!, we get one extra 5 and number of 0s become 6. Handling this is simple, first divide n by 5 and remove all single 5s, then divide by 25 to remove extra 5s and so on. Following is the summarized formula for counting trailing 0s.

Trailing 0s in n! = Count of 5s in prime factors of n!
                  = floor(n/5) + floor(n/25) + floor(n/125) + ....

那么怎样计算n!的质因子中所有5的个数呢?一个简单的方法是计算floor(n/5)。例如,7!有一个5,10!有两个5。除此之外,还有一件事情要考虑。诸如25,125之类的数字有不止一个5。例如,如果我们考虑28!,我们得到一个额外的5,并且0的总数变成了6。处理这个问题也很简单,首先对n÷5,移除所有的单个5,然后÷25,移除额外的5,以此类推。下面是归纳出的计算后缀0的公式。

n!后缀0的个数 = n!质因子中5的个数
              = floor(n/5) + floor(n/25) + floor(n/125) + ....

    public long trailingZeros(long n) {
        long res = 0;
        long divider = 5;
        while (n >= divider) {
            res += (n / divider);
            divider *= 5;
        }
        return res;
    }

366. Fibonacci

斐波那契数列,老生常谈了,没啥好说的了。


141. Sqrt(x)

428. Pow(x, n)

这两题请参考 LintCode二分查找题总结


518. Super Ugly Number

这道题与 Ugly Number II 的思路基本上是一样的,这道题算是那道题的变种。基本思路是PriorityQueue+Hash来解决。

找到第N个丑数,我们可以用优先队列来构建丑数,

一开始的元素里有1,我把1拿出来,乘以2、7、13、19,得到2、7、13、19,加入到优先队列中。

然后把2拿出来,乘以2、7、13、19,得到4、14、26、38,加入到优先队列中,得到1、2、4、7、13、14、19、26、38.

再把4拿出来,乘以2、7、13、19,得到8、28、52、76,加入到优先队列中,得到1、2、4、7、8、13、14、19、26、28、38、52、76.

注意在这个过程中可能会出现重复元素,我们每次添加进优先队列之前,要用HashSet判断一下这个数出现过没,保证每次新加进队列的元素是唯一的。

然后每次都把最小的元素pop出来,这样经过N此操作,就可以得到第N个丑数。时间复杂度为O(nlogn),外层遍历为N遍,然后每次遍历的时候,优先队列的插入花销logN的时间。

    public int nthSuperUglyNumber(int n, int[] primes) {
        Queue<Long> q = new PriorityQueue<Long>();
        HashSet<Long> set = new HashSet<Long>();
        q.offer(Long.valueOf(1));
        
        long res = 1;
        for (int i = 1; i <= n; i++) {
            res = q.poll();
            for (int j = 0; j < primes.length; j++) {
                long tmp = res * primes[j];
                if (!set.contains(tmp)) {
                    set.add(tmp);
                    q.offer(tmp);
                }
            }
        }
        return (int)res;
    }


407. Plus One

给定一个数组,模拟加1的操作:

    public int[] plusOne(int[] digits) {
        ArrayList<Integer> res = new ArrayList();
        int c = 1;
        for (int i = digits.length - 1; i >= 0; i--) {
            int tmp = digits[i] + c;
            if (tmp < 10) {
                res.add(tmp);
                c = 0;
            } else {
                tmp -= 10;
                res.add(tmp);
                c = 1;
            }
        }
        if (c == 1) {
            res.add(1);
        }
        int[] result = new int[res.size()];
        for (int i = 0; i < res.size(); i++) {
            result[i] = res.get(res.size() - i - 1);
        }
        return result;
    }

167. Add Two Numbers

221. Add Two Numbers II

详情见 LintCode链表题总结


414. Divide Two Integers

不准用乘法、除法和取模运算,要求实现对2个整数做除法的运算。基本思路就是利用减法。比如12/4可以看做让12连续减去4,直到结果为0,中间运用了3次减法才让结果为0,所以答案是3(12包含3个4)。按照这个思路写出的初步代码如下所示,会超时:

    public int divide(int dividend, int divisor) {
        long res = 0;
        boolean minus = (dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0);
        long a = Math.abs((long)dividend), b = Math.abs((long)divisor);
        while (a >= b) {
            a -= b;
            res++;
        }
        
        if (res > Integer.MAX_VALUE || res < Integer.MIN_VALUE || divisor == 0) {
            return Integer.MAX_VALUE;
        }
        if (minus) {
            return (int)res * (-1);
        }
        return (int)res;
    }
我们可以使用2分法来加速这个过程。不断对除数*2,直到它比被除数还大为止。加倍的同时,也记录下cnt,将被除数减掉加倍后的值,并且结果+cnt。因为是2倍地加大,所以速度会很快,指数级的速度。

需要考虑一些边界条件:

最小值的越界问题。对最小的正数取abs,得到的还是它。。。 因为最小的正数的绝对值大于最大的正数(INT)
Input: -2147483648, -1 Expected: 2147483647

    public int divide(int dividend, int divisor) {
        long res = 0;
        boolean minus = (dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0);
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        long a = Math.abs((long)dividend), b = Math.abs((long)divisor);
        while (a >= b) {
            int shift = 0;
            while (a >= (b << shift)) {
                shift++;
            }
            a -= (b << (shift - 1));
            res += (1 << (shift - 1));
        }
        
        if (res > Integer.MAX_VALUE || res < Integer.MIN_VALUE || divisor == 0) {
            return Integer.MAX_VALUE;
        }
        return minus ? -(int)res : (int)res;
    }

186. Max Points on a Line

在一个平面上有很多点,求最多能有多少点能在同一条直线上。

选定一个点,分别计算其他点和它构成的直线的斜率,斜率相同的点肯定在同一条直线上。

计算斜率时,注意重合点和x值相同的两个点(数学上称斜率不存在,此时斜率用int的最大值表示)。

要用到2层循环来遍历,用HashMap来记录。

    public int maxPoints(Point[] p) {
        if (p == null || p.length == 0) {
            return 0;
        }
        int max = 0;
        HashMap<Double, Integer> map = new HashMap(); //保存同一个斜率的点的个数
        
        for (int i = 0; i < p.length; i++) {
            map.clear();
            int duplicate = 1;

            for (int j = i + 1; j < p.length; j++) {
                double slope = 0;
                // 如果点重合
                if (p[i].x == p[j].x && p[i].y == p[j].y) {
                    duplicate++;
                    continue;
                } else if (p[i].x == p[j].x) {
                    // 在同一条竖线上,斜率最大
                    slope = Integer.MAX_VALUE;
                } else {
                    slope = (double)(p[i].y - p[j].y) / (double)(p[i].x - p[j].x);
                }
                if (map.containsKey(slope)) {
                    map.put(slope, map.get(slope) + 1);
                } else {
                    map.put(slope, 1);
                }
            }
            // 处理全部都是重合的点的情况
            if (map.keySet().size() == 0) {
                max = (duplicate > max) ? duplicate : max;
            } else {
                for (int tmp : map.values()) {
                    max = (tmp + duplicate) > max ? (tmp + duplicate) : max;
                }
            }
        }
        return max;
    }

413. Reverse Integer

输入一个整数,要求对它进行逆转。看似很简单,我们一开始可能会写出这样子的代码:

    public int reverseInteger(int n) {
        int res = 0;
        while (n != 0) {
            res = res * 10 + n % 10;
            n /= 10;
        }
        return res;
    }
然而运行的时候发现没法通过输入测例:1534236469。溢出了。可是整数的最大值不是2147483647么?那怎么会溢出呢。我们仔细观察下代码,发现唯一有可能溢出的地方在于res * 10那里。可能本来res是10位数没有溢出的,但是乘以10之后就会溢出了。所以我们要提前判断一下是不是溢出:

    public int reverseInteger(int n) {
        int res = 0;
        while (n != 0) {
            long tmp = (long)res * 10;
            if (tmp > Integer.MAX_VALUE) {
                return 0;
            }
            res = res * 10 + n % 10;
            n /= 10;
        }
        return res;
    }


3. Digit Counts

给定一个数组,判断一个digit在那个数组出现了几次。比如:if n = 12, k = 1 in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] we have FIVE 1's (1, 10, 11, 12)

很简单,就逐个遍历统计好了,我用的方法是临时把int转换为string,然后判断包含几个digit,进行统计。

    public int digitCounts(int k, int n) {
        int res = 0;
        for (int i = 0; i <= n; i++) {
            String tmp = "" + i;
            String kString = "" + k;
            char target = kString.charAt(0);
            for (int j = 0; j < tmp.length(); j++) {
                if (tmp.charAt(j) == target) {
                    res++;
                }
            }
        }
        return res;
    }
还有种方法是用除法和取模运算,就不赘述了。


161. Rotate Image

要求把矩阵往右旋转90度。这篇博客讲了三种方法:http://www.cnblogs.com/grandyang/p/4389572.html

我用的是第三种方法,即 转置 + 求逆的方法:

    public void rotate(int[][] matrix) {
        int n = matrix.length;
        for (int i = 0; i < n; i++) {
            // 转置
            for (int j = i + 1; j < n; j++) {
                int tmp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = tmp;
            }
            // 逆转
            int left = 0, right = n - 1;
            while (left < right) {
                int tmp = matrix[i][left];
                matrix[i][left] = matrix[i][right];
                matrix[i][right] = tmp;
                left++;
                right--;
            }
        }
    }
建议把这道题在纸上画画,帮助理解。

162. Set Matrix Zeroes

一个数组,只要有一个数为0,就把那行跟那列的所有元素全部变为0.

这道题中说的空间复杂度为O(mn)的解法自不用多说,直接新建一个和matrix等大小的矩阵,然后一行一行的扫,只要有0,就将新建的矩阵的对应行全赋0,行扫完再扫列,然后把更新完的矩阵赋给matrix即可,这个算法的空间复杂度太高。

将其优化到O(m+n)的方法是,用一个长度为m的一维数组记录各行中是否有0,用一个长度为n的一维数组记录各列中是否有0,最后直接更新matrix数组即可。

这道题的要求是用O(1)的空间,那么我们就不能新建数组,我们考虑就用原数组的第一行第一列来记录各行各列是否有0。空间复杂度为O(m+n)的解法如下:

    public void setZeroes(int[][] matrix) {
        // 处理特殊情况
        int m = matrix.length;
        if (matrix == null || m == 0) {
            return;
        }
        int n = matrix[0].length;
        if (n == 0) {
            return;
        }
        
        int[] row = new int[m];
        int[] col = new int[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    row[i] = 1;
                    col[j] = 1;
                }
            }
        }
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (row[i] == 1 || col[j] == 1) {
                    matrix[i][j] = 0;
                }
            }
        }
    }

先扫描第1行第1列,如果有0,则将各自的flag设置为true 

然后扫描 没有第一行第一列的 整个数组。如果出现了0,则将对应的第一行和第一列的位置赋0 

再次遍历 没有第一行第一列的 整个数组,如果对应的第一行和第一列的位置有一个为0,则将当前值赋0 

最后根据第一行第一列的flag来更新第一行第一列,代码如下:

    public void setZeroes(int[][] matrix) {
        // 处理特殊情况
        int m = matrix.length;
        if (matrix == null || m == 0) {
            return;
        }
        int n = matrix[0].length;
        
        boolean empty_row0 = false;
        boolean empty_col0 = false;
        
        for (int i = 0; i < n; i++) {
            if (matrix[0][i] == 0) {
                empty_row0 = true;
                break;
            }
        }
        
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                empty_col0 = true;
                break;
            }
        }
        
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[0][j] = 0;
                    matrix[i][0] = 0;
                }
            }
        }
        
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[0][j] == 0 || matrix[i][0] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        
        if (empty_row0) {
            for (int i = 0; i < n; i++) {
                matrix[0][i] = 0;
            }
        }
        
        if (empty_col0) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;
            }
        }
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值