统计各位数字之和为偶数的整数个数

标签: 数学 模拟

难度: Easy

给你一个正整数 num ,请你统计并返回 小于或等于 num 且各位数字之和为 偶数 的正整数的数目。

正整数的 各位数字之和 是其所有位上的对应数字相加的结果。

示例 1:

输入:num = 4
输出:2
解释:
只有 2 和 4 满足小于等于 4 且各位数字之和为偶数。    

示例 2:

输入:num = 30
输出:14
解释:
只有 14 个整数满足小于等于 30 且各位数字之和为偶数,分别是: 
2、4、6、8、11、13、15、17、19、20、22、24、26 和 28 。

提示:

  • 1 <= num <= 1000

Submission

运行时间: 28 ms

内存: 16.5 MB

class Solution:
    def countEven(self, num: int) -> int:
        ans = num // 10 * 5 - 1
        x, s = num // 10, 0
        while x:
            s += x % 10
            x //= 10
        ans += (num % 10 + 2 - (s & 1)) >> 1
        return ans
    

Explain

本题解采用数学和模拟的方法来解决问题。首先,计算出小于当前十位数(num//10)的整数中,满足各位数字之和为偶数的数字数量。对于每个十位数区间,例如0-9, 10-19等,其中有一半的数字的各位数字之和是偶数。因此,可以直接计算出直到最近的十位数的满足条件的数字总数。接着处理最后一个不完整的十位区间的数字,即从最近的十位数到num的数字,逐个检查这些数字,统计其中满足各位数字之和为偶数的数量。为此,计算从0开始到最近的十位数的数字之和是否为偶数,然后根据最后一位数字的奇偶性调整计数结果。

时间复杂度: O(log(num))

空间复杂度: O(1)

class Solution:
    def countEven(self, num: int) -> int:
        # 初始化计数器,ans为小于当前十位数的满足条件的数字总数
        ans = num // 10 * 5 - 1
        # x为数num的十位数部分,s为十位数部分的数字之和
        x, s = num // 10, 0
        # 计算x的各位数字之和
        while x:
            s += x % 10
            x //= 10
        # 根据最后一位数字的奇偶性调整计数结果
        ans += (num % 10 + 2 - (s & 1)) >> 1
        return ans
    

Explore

在任何一个连续的十个整数(例如0-9, 10-19等)中,每个整数的最后一位(个位)依次从0到9变化。由于个位数字的改变直接影响整个数的各位数字之和的奇偶性,而0到9这十个数字中恰好有五个数字的个位是偶数(0, 2, 4, 6, 8),五个数字的个位是奇数(1, 3, 5, 7, 9),这导致整个数的各位数字之和也将在偶数和奇数之间交替变化。因此,在每个连续的十个数中,恰好有五个数的各位数字之和为偶数。

计数器初始设置为`num // 10 * 5 - 1`是因为我们要考虑从1到num的最近的整十数(例如,如果num是25,那么最近的整十数是20)。每个整十数区间(如0-9, 10-19)中有五个数字的各位数字之和为偶数,但我们需要从1开始计数,所以整十数(如10, 20)本身的处理需要稍作调整。由于整十数本身被计算了两次(一次在其所属的区间如0-9,一次在如10-19),因此我们需要减去1以矫正这个重复计算的影响。

`(s & 1)`检查整数s(代表从0开始到num的最近十位数之前所有位的数字之和)的奇偶性。如果s是奇数,`(s & 1)`结果为1;如果s是偶数,结果为0。表达式`num % 10 + 2 - (s & 1)`通过考虑s的奇偶性来调整范围内的计数。如果s是偶数,则`(s & 1)`为0,此时如果num的最后一位是偶数,则应该包括这个数,因此`num % 10 + 2`确保了这一点。右移操作`>> 1`是整除2的快捷方式,用于从可能的十个数字中选出其中的偶数和。