Count Good Numbers

Jul 4, 2021

https://leetcode.com/problems/count-good-numbers/

Basic Idea

这道题其实并不复杂,只需要计算5^numEven * 4^numOdd 即可,但问题是给定的位数非常大,需要一种可以快速计算乘方的方法。discussion中很多解法都涉及到了binary exponentiation算法,但事实上只需要简单的分治法即可。我们只需要从1次方开始,然后2,4,8,16 ... 每次计算翻倍次数的乘方,最终时间复杂度为 logN.

有递归和iterative两种做法,递归稍简单一些。

Java Code:

// recursion
class Solution {
    long MOD = (long) 1e9 + 7;
    
    public int countGoodNumbers(long n) {
        if (n == 1) return 5;
        long numEven = (n + 1) / 2;
        long numOdd = n / 2;
        return (int) (pow(5, numEven) * pow(4, numOdd) % MOD);
    }
    
    // 利用公式 a^b = (a^0.5b)^2 递归,时间复杂度为 logN
    private long pow(long a, long b) {    
        if (b == 1) return a;
        long half = pow(a, b / 2);
        long res = half * half % MOD;
        if (b % 2 == 1) {
            return res * a % MOD;
        } else {
            return res;
        }
    }
}

// iterative
class Solution {
    long MOD = (long) 1e9 + 7;
    
    public int countGoodNumbers(long n) {
        if (n == 1) return 5;
        long numEven = (n + 1) / 2;
        long numOdd = n / 2;
        return (int) (pow(5, numEven) * pow(4, numOdd) % MOD);
    }
    
    // Iterative 的方法
    // 每次以两倍扩张,直到剩下部分不足翻倍,然后重新从1开始两倍扩张
    // 例如当b=100,先两倍扩张到64,然后剩下36,再从1开始两倍扩张到32,剩下4,
    // 然后两倍扩张到4,结束
    private long pow(long a, long b) {    
        long res = 1; // 最终结果
        long curr = 1; // 当前的乘方次数
        long p = a; // 当前翻倍乘方过程中的结果
        while (true) {
            if (curr == b) {
                return res * p % MOD;
            }
            if (curr * 2 > b) {
                res = res * p % MOD;
                p = a;
                b = b - curr; // b 为剩下的乘方次数
                curr = 1;
            }
            if (curr * 2 <= b) {
                p = p * p % MOD;
                curr *= 2;
            }
        }
    }
}

Last updated