Hard

题目描述

给你两个整数 lr

如果一个整数的各位数字形成严格单调序列,即数字严格递增或严格递减,则称该整数为好数。所有单位数都被认为是好数。

如果一个整数是好数,或者它的各位数字之和是好数,则称该整数为花哨数

返回一个整数,表示区间 [l, r](包含端点)内花哨数的个数。

严格递增序列是指每个元素都严格大于前一个元素(如果存在)。

严格递减序列是指每个元素都严格小于前一个元素(如果存在)。

示例 1:

输入:l = 8, r = 10
输出:3
解释:
8 和 9 是单位数,所以它们是好数,因此是花哨数。
10 的数字是 [1, 0],形成严格递减序列,所以 10 是好数,因此是花哨数。
因此,答案是 3。

示例 2:

输入:l = 12340, r = 12341
输出:1
解释:
12340
  12340 不是好数,因为 [1, 2, 3, 4, 0] 不是严格单调的。
  数字和是 1 + 2 + 3 + 4 + 0 = 10。
  10 是好数,因为它的数字 [1, 0] 是严格递减的。因此,12340 是花哨数。

12341
  12341 不是好数,因为 [1, 2, 3, 4, 1] 不是严格单调的。
  数字和是 1 + 2 + 3 + 4 + 1 = 11。
  11 不是好数,因为它的数字 [1, 1] 不是严格单调的。因此,12341 不是花哨数。

因此,答案是 1。

提示:

  • 1 <= l <= r <= 10^15

解题思路

这道题需要统计区间内的"花哨数",花哨数定义为:数字本身是好数,或者各位数字之和是好数。

解题思路

核心思想: 使用数位DP + 预处理的方法求解。

  1. 预处理好数集合:

    • 生成所有由数字1-9组成的严格递增数
    • 生成所有由数字0-9组成的严格递减数
    • 去重得到所有好数
  2. 预处理好的数字和:

    • 由于最大数是10^15,最大数字和为9×15=135
    • 找出[1,135]中所有是好数的数字和
  3. 数位DP统计:

    • 实现count(bound)函数,统计≤bound且数字和为好数的数的个数
    • 使用记忆化搜索,状态包括:当前位置、是否达到上界、当前数字和
  4. 容斥原理计算最终结果:

    答案 = count(r) - count(l-1) + 区间内好数个数 - 既是好数又数字和是好数的个数
    

关键在于数位DP的实现,需要维护当前数字和,并判断最终和是否在好数集合中。时间复杂度主要由数位DP决定,为O(log(r) × 数字和范围)。

代码实现

class Solution {
public:
    vector<long long> goods;
    vector<bool> good_sums;
    vector<vector<vector<int>>> memo;
    
    void generateGoods() {
        set<long long> good_set;
        
        // Generate strictly increasing numbers from digits 1-9
        for (int mask = 1; mask < (1 << 9); mask++) {
            long long num = 0;
            for (int i = 0; i < 9; i++) {
                if (mask & (1 << i)) {
                    num = num * 10 + (i + 1);
                }
            }
            good_set.insert(num);
        }
        
        // Generate strictly decreasing numbers from digits 0-9
        for (int mask = 1; mask < (1 << 10); mask++) {
            long long num = 0;
            for (int i = 9; i >= 0; i--) {
                if (mask & (1 << i)) {
                    num = num * 10 + i;
                }
            }
            if (num > 0) good_set.insert(num);
        }
        
        goods.assign(good_set.begin(), good_set.end());
    }
    
    bool isGood(int n) {
        if (n < 10) return true;
        string s = to_string(n);
        bool inc = true, dec = true;
        for (int i = 1; i < s.size(); i++) {
            if (s[i] <= s[i-1]) inc = false;
            if (s[i] >= s[i-1]) dec = false;
        }
        return inc || dec;
    }
    
    void generateGoodSums() {
        good_sums.resize(146, false);
        for (int i = 1; i <= 145; i++) {
            good_sums[i] = isGood(i);
        }
    }
    
    long long digitDP(const string& num, int pos, bool tight, int sum) {
        if (pos == num.size()) {
            return good_sums[sum] ? 1 : 0;
        }
        
        if (!tight && memo[pos][sum][0] != -1) {
            return memo[pos][sum][0];
        }
        
        int limit = tight ? (num[pos] - '0') : 9;
        long long result = 0;
        
        for (int digit = 0; digit <= limit; digit++) {
            if (pos == 0 && digit == 0 && num.size() > 1) {
                result += digitDP(num, pos + 1, false, sum);
            } else {
                result += digitDP(num, pos + 1, tight && (digit == limit), sum + digit);
            }
        }
        
        if (!tight) {
            memo[pos][sum][0] = result;
        }
        
        return result;
    }
    
    long long count(long long n) {
        if (n <= 0) return 0;
        string num = to_string(n);
        memo.assign(num.size(), vector<vector<int>>(146, vector<int>(1, -1)));
        return digitDP(num, 0, true, 0);
    }
    
    long long countFancy(long long l, long long r) {
        generateGoods();
        generateGoodSums();
        
        long long result = count(r) - count(l - 1);
        
        // Count goods in range [l, r]
        auto left = lower_bound(goods.begin(), goods.end(), l);
        auto right = upper_bound(goods.begin(), goods.end(), r);
        long long goodsInRange = right - left;
        
        // Count goods in range whose digit sum is also good
        long long overlap = 0;
        for (auto it = left; it != right; it++) {
            long long num = *it;
            int sum = 0;
            while (num > 0) {
                sum += num % 10;
                num /= 10;
            }
            if (good_sums[sum]) overlap++;
        }
        
        return result + goodsInRange - overlap;
    }
};
class Solution:
    def countFancy(self, l: int, r: int) -> int:
        # Generate all good numbers
        goods = set()
        
        # Strictly increasing from 1-9
        for mask in range(1, 1 << 9):
            num = 0
            for i in range(9):
                if mask & (1 << i):
                    num = num * 10 + (i + 1)
            goods.add(num)
        
        # Strictly decreasing from 0-9
        for mask in range(1, 1 << 10):
            num = 0
            for i in range(9, -1, -1):
                if mask & (1 << i):
                    num = num * 10 + i
            if num > 0:
                goods.add(num)
        
        goods = sorted(goods)
        
        # Check if a number is good
        def is_good(n):
            if n < 10:
                return True
            s = str(n)
            inc = all(s[i] > s[i-1] for i in range(1, len(s)))
            dec = all(s[i] < s[i-1] for i in range(1, len(s)))
            return inc or dec
        
        # Generate good sums
        good_sums = [is_good(i) for i in range(146)]
        
        # Digit DP
        def count(n):
            if n <= 0:
                return 0
            
            s = str(n)
            memo = {}
            
            def dp(pos, tight, digit_sum):
                if pos == len(s):
                    return 1 if good_sums[digit_sum] else 0
                
                if not tight and (pos, digit_sum) in memo:
                    return memo[(pos, digit_sum)]
                
                limit = int(s[pos]) if tight else 9
                result = 0
                
                for digit in range(limit + 1):
                    if pos == 0 and digit == 0 and len(s) > 1:
                        result += dp(pos + 1, False, digit_sum)
                    else:
                        result += dp(pos + 1, tight and digit == limit, digit_sum + digit)
                
                if not tight:
                    memo[(pos, digit_sum)] = result
                
                return result
            
            return dp(0, True, 0)
        
        result = count(r) - count(l - 1)
        
        # Count goods in range
        import bisect
        left_idx = bisect.bisect_left(goods, l)
        right_idx = bisect.bisect_right(goods, r)
        goods_in_range = right_idx - left_idx
        
        # Count overlap
        overlap = 0
        for i in range(left_idx, right_idx):
            num = goods[i]
            digit_sum = sum(int(d) for d in str(num))
            if good_sums[digit_sum]:
                overlap += 1
        
        return result + goods_in_range - overlap
public class Solution {
    private List<long> goods;
    private bool[] goodSums;
    private Dictionary<(int, int, bool), long> memo;
    
    public long CountFancy(long l, long r) {
        GenerateGoods();
        GenerateGoodSums();
        
        long result = Count(r) - Count(l - 1);
        
        // Count goods in range [l, r]
        int leftIdx = Array.BinarySearch(goods.ToArray(), l);
        if (leftIdx < 0) leftIdx = ~leftIdx;
        
        int rightIdx = Array.BinarySearch(goods.ToArray(), r);
        if (rightIdx < 0) rightIdx = ~rightIdx;
        else rightIdx++;
        
        long goodsInRange = rightIdx - leftIdx;
        
        // Count overlap
        long overlap = 0;
        for (int i = leftIdx; i < rightIdx; i++) {
            long num = goods[i];
            int digitSum = 0;
            long temp = num;
            while (temp > 0) {
                digitSum += (int)(temp % 10);
                temp /= 10;
            }
            if (goodSums[digitSum]) overlap++;
        }
        
        return result + goodsInRange - overlap;
    }
    
    private void GenerateGoods() {
        var goodSet = new HashSet<long>();
        
        // Strictly increasing from 1-9
        for (int mask = 1; mask < (1 << 9); mask++) {
            long num = 0;
            for (int i = 0; i < 9; i++) {
                if ((mask & (1 << i)) != 0) {
                    num = num * 10 + (i + 1);
                }
            }
            goodSet.Add(num);
        }
        
        // Strictly decreasing from 0-9
        for (int mask = 1; mask < (1 << 10); mask++) {
            long num = 0;
            for (int i = 9; i >= 0; i--) {
                if ((mask & (1 << i)) != 0) {
                    num = num * 10 + i;
                }
            }
            if (num > 0) goodSet.Add(num);
        }
        
        goods = goodSet.OrderBy(x => x).ToList();
    }
    
    private bool IsGood(int n) {
        if (n < 10) return true;
        string s = n.ToString();
        bool inc = true, dec = true;
        for (int i = 1; i < s.Length; i++) {
            if (s[i] <= s[i-1]) inc = false;
            if (s[i] >= s[i-1]) dec = false;
        }
        return inc || dec;
    }
    
    private void GenerateGoodSums() {
        goodSums = new bool[146];
        for (int i = 1; i <= 145; i++) {
            goodSums[i] = IsGood(i);
        }
    }
    
    private long Count(long n) {
        if (n <= 0) return 0;
        
        string num = n.ToString();
        memo = new Dictionary<(int, int, bool), long>();
        
        return DigitDP(num, 0, true, 0);
    }
    
    private long DigitDP(string num, int pos, bool tight, int sum) {
        if (pos == num.Length) {
            return goodSums[sum] ? 1 : 0;
        }
        
        var key = (pos, sum, tight);
        if (!tight && memo.ContainsKey(key)) {
            return memo[key];
        }
        
        int limit = tight ? (num[pos] - '0') : 9;
        long result = 0;
        
        for (int digit = 0; digit <= limit; digit++) {
            if (pos == 0 && digit == 0 && num.Length > 1) {
                result += DigitDP(num, pos + 1, false, sum);
            } else {
                result += DigitDP(num, pos + 1, tight && (digit == limit), sum + digit);
            }
        }
        
        if (!tight) {
            memo[key] = result;
        }
        
        return result;
    }
}
var countFancy = function(l, r) {
    function isGood(num) {
        const digits = num.toString().split('').map(Number);
        if (digits.length <= 1) return true;
        
        let increasing = true;
        let decreasing = true;
        
        for (let i = 1; i < digits.length; i++) {
            if (digits[i] <= digits[i-1]) increasing = false;
            if (digits[i] >= digits[i-1]) decreasing = false;
        }
        
        return increasing || decreasing;
    }
    
    function isFancy(num) {
        if (isGood(num)) return true;
        
        const digitSum = num.toString().split('').reduce((sum, digit) => sum + parseInt(digit), 0);
        return isGood(digitSum);
    }
    
    function countFancyUpTo(limit) {
        const s = limit.toString();
        const n = s.length;
        const memo = new Map();
        
        function dp(pos, tight, started, lastDigit, isIncreasing, isDecreasing, digitSum) {
            if (pos === n) {
                if (!started) return 0;
                
                const currentGood = isIncreasing || isDecreasing;
                if (currentGood) return 1;
                
                return isGood(digitSum) ? 1 : 0;
            }
            
            const key = `${pos},${tight},${started},${lastDigit},${isIncreasing},${isDecreasing},${digitSum}`;
            if (memo.has(key)) return memo.get(key);
            
            const maxDigit = tight ? parseInt(s[pos]) : 9;
            let result = 0;
            
            for (let digit = 0; digit <= maxDigit; digit++) {
                const newTight = tight && (digit === maxDigit);
                const newStarted = started || (digit > 0);
                
                if (!newStarted) {
                    result += dp(pos + 1, newTight, false, -1, true, true, 0);
                } else {
                    const newIsIncreasing = lastDigit === -1 ? true : (isIncreasing && digit > lastDigit);
                    const newIsDecreasing = lastDigit === -1 ? true : (isDecreasing && digit < lastDigit);
                    const newDigitSum = digitSum + digit;
                    
                    result += dp(pos + 1, newTight, true, digit, newIsIncreasing, newIsDecreasing, newDigitSum);
                }
            }
            
            memo.set(key, result);
            return result;
        }
        
        return dp(0, true, false, -1, true, true, 0);
    }
    
    return countFancyUpTo(r) - countFancyUpTo(l - 1);
};

复杂度分析

指标复杂度
时间-
空间-