剣とは、オファー43に1〜nの整数の1が現れる回数を指します。

剣とは、オファー43に1〜nの整数の1が現れる回数を指します。

整数nを入力し、1からnまでのn個の整数の10進表現で1の出現回数を見つけます。
たとえば、12と入力すると、1から12までの整数に1を含む数字は、1、10、11、および12であり、1は合計5回表示されます。

出典:LeetCode
リンク:https ://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof
著作権はLeetCodeが所有しています商用の再版については、公式の承認に連絡してください。非商用の再版については、出典を示してください。

LeetCodeの説明から転載

これは典型的なデジタル動的計画法の問題です。デジタル動的計画法のアイデアとテンプレートを習得した後、数秒でそれを殺すことができます。
まず、ブルートフォース列挙使用してすべての数値を分割するソリューションがタイムアウトしましたしたがって、列挙を改善するという観点からアルゴリズムを更新することができます。
例として12345を取り上げます。このような大きな数値を列挙するには、次のようにします。最上位ビットから開始して、最上位ビットが0の場合(つまり、最上位ビットが上限1に達しない場合)、次の桁は次のようになります。最上位桁が1の場合(つまり、最上位桁が上限1に達する場合)、次の桁は0から2までの数値しか取得できません(2がこの数値の上限であるため)。類推により、すべての数値を列挙できます。このようにして、数値を激しく列挙するというデジタル動的計画法の最初のステップを理解ます。明らかに、列挙プロセスでは、現在の数字の列挙範囲を決定するために、前の数字が上限に達したかどうかをマークする必要があります。
これには非常に重要な利点があります。つまり、本質的にビット単位の列挙であり、数値を分割する元のプロセスを節約します。つまり、特定のビットが1に列挙されている限り、合計に+1を追加します。
さらに、単純な再帰書き込み方式は、多くの計算が繰り返されるためタイムアウトになります。そのため、再帰プロセスでは、各時間の結果が記録され、必要に応じて直接使用されます。時間のためのスペース。したがって、中間結果を格納するには配列が必要です。配列は対応する再帰の結果を格納するものであり、再帰関数にはパラメーターがあるため、パラメーターはこの再帰の結果をマークするための配列の添え字として使用されることを理解することが重要です。
詳細なコードは次のとおりです。

class Solution {
    
    
    private int[][][] dp = null;
    private int[] upperBound = null;

    public int countDigitOne(int n) {
    
    
        String string = String.valueOf(n);
        char[] chars = string.toCharArray();//实现对n的从最高位到最低位数位拆分
        int length = chars.length;
        upperBound = new int[length];//upperBound数组记录从n的最低位到最高位,每一位数字的上限
        for(int i = 0; i < length; ++i) {
    
    
            upperBound[i] = chars[length - 1 - i] - '0';//初始化upperBound数组,注意下标的变换,因为记录的是从n的最低位到最高位的数字上限
        }       
        dp = new int[length][2][length];//int[pos][limit][sum],下标对应dfs的参数,标识、记录中间结果
        for(int i = 0; i < length; ++i) {
    
    
            for(int j = 0; j < 2; ++j) {
    
    
                Arrays.fill(dp[i][j], -1);//初始化dp数组,用-1表示对应的dfs有没有执行过
            }
        }
        return this.dfs(length - 1, 1, 0);
    }

    private int dfs(int pos, int limit, int sum) {
    
    //pos与upperBound的下标对应
        if(pos == -1) return sum;//递归结束,返回sum
        if(dp[pos][limit][sum] != -1) return dp[pos][limit][sum];//对应的dfs执行过了,就直接返回以前算过的值,空间换时间,节省时间开销
        int maxNum = (limit == 1) ? upperBound[pos] : 9;//当前第pos位的数有上界要求,取上界数字,否则取9
        int res = 0;
        for(int i = 0; i <= maxNum; ++i) {
    
    //对当前第pos位的数字进行枚举,范围从0到maxNum
            //考虑低一位的数字:当当前第pos位有上界且达到了上界,低一位数字才有上界;
            //当当前第pos位为1时,1出现的次数加1,因此sum需要加1
            res += dfs(pos - 1, ((limit == 1) && (i == maxNum)) ? 1 : 0, (i == 1) ? sum + 1 : sum);
        }
        return dp[pos][limit][sum] = res;//标识、记录res,并返回res
    }
}

おすすめ

転載: blog.csdn.net/m0_55570281/article/details/114156236