
2025-08-17:统计可以被最后一个数位整除的子字符串数目。用go语言,给定一个只含数字字符的字符串 s。统计所有满足下列两点的连续非空子串的数量:其末位不是 0,并且把该子串作为十进制整数后,该整数能被子串的最后一位数字整除。子串可以包含前导零。返回满足条件的子串个数。
1 <= s.length <= 100000。
s 只包含数字。
输入:s = "12936"。
输出:11。
解释:
子字符串 "29" ,"129" ,"293" 和 "2936" 不能被它们的最后一位整除,总共有 15 个子字符串,所以答案是 15 - 4 = 11 。
题目来自力扣3448。
为了高效地解决这个问题,我们可以利用动态规划的方法来跟踪子串的模数信息。具体来说,对于每个字符(即数字)在字符串中的位置,我们维护一个数组 f,其中 f[m][rem] 表示以当前字符结尾的子串中,模 m 余数为 rem 的子串数量。这里的 m 是可能的模数(即数字 1 到 9),rem 是模 m 的余数(0 到 m-1)。
f,大小为 [10][9]。f[m][rem] 表示以当前字符结尾的子串中,模 m 余数为 rem 的子串数量。这里 m 的取值范围是 1 到 9(因为模数只能是 1 到 9,对应数字的最后一位),rem 的取值范围是 0 到 m-1。ans 为 0,用于累计满足条件的子串数量。s 中的每一个字符 c(即数字 d = int(c - '0')),我们进行以下操作:d 是 0,则跳过(因为子串的最后一个字符不能是 0)。m(从 1 到 9):nf(大小为 9),用于存储更新后的模数信息。d 单独作为一个子串,其模 m 的余数为 d % m,因此 nf[d % m] += 1。rem(即 f[m][rem] 的所有可能值),计算新的余数 (rem * 10 + d) % m,并将 f[m][rem] 的值累加到 nf 的对应位置。这相当于将当前数字 d 追加到所有以之前字符结尾的子串后面,形成新的子串。nf 的值赋给 f[m],完成滚动更新。d(即子串的最后一位),我们需要统计所有以 d 结尾的子串中,模 d 余数为 0 的数量(即 f[d][0]),并将这个数量累加到 ans 中。ans 中存储的就是所有满足条件的子串数量。以 s = "12936" 为例:
f 全为 0,ans = 0。d = 1,非 0。m = 1,nf[0] = 1(因为 1 % 1 = 0)。f[1][0] = 1,ans += 1(子串 "1")。d = 2,非 0。m = 1,nf[0] = 1 + f[1][0] = 2(子串 "2" 和 "12")。m = 2,nf[0] = 1(子串 "2"),nf[1] = f[2][0]("12" 的模 2 余数为 0,(0*10 + 2) % 2 = 0,所以 nf[0] += 1)。f[2][0] = 2,ans += 2(子串 "2" 和 "12")。f 和 ans。ans = 11。s,长度为 n。m(1 到 9),共 9 次。m,需要遍历余数 rem(最多 9 个)。O(n * 9 * 9) = O(n)(因为常数可以忽略)。f,大小为 10 * 9。O(1)(常数空间)。通过动态规划和滚动数组的技巧,我们高效地统计了满足条件的子串数量。算法的时间复杂度是线性的,空间复杂度是常数。
.
package main
import (
"fmt"
)
func countSubstrings(s string) (ans int64) {
f := [10][9]int{}
for _, c := range s {
d := int(c - '0')
for m := 1; m < 10; m++ { // 枚举模数 m
// 滚动数组计算 f
nf := [9]int{}
nf[d%m] = 1
for rem, fv := range f[m][:m] { // 枚举模 m 的余数 rem
nf[(rem*10+d)%m] += fv // 刷表法
}
f[m] = nf
}
// 以 s[i] 结尾的,模 s[i] 余数为 0 的子串个数
ans += int64(f[d][0])
}
return
}
func main() {
s := "12936"
result := countSubstrings(s)
fmt.Println(result)
}

.
# -*-coding:utf-8-*-
def count_substrings(s: str) -> int:
# f[m] 保存当前以遍历到的位置结尾的、按模 m 的不同余数分组的子串数量
# 我们用长度可变的列表存储,每个 f[m] 的长度为 m(m=0 未使用,但保留占位)
f = [[0] * 9for _ in range(10)]
ans = 0
for c in s:
d = int(c)
for m in range(1, 10):
nf = [0] * m
nf[d % m] = 1 # 只有当前数字本身作为子串
for rem, fv in enumerate(f[m][:m]):
if fv:
nf[(rem * 10 + d) % m] += fv
f[m] = nf
# 以当前字符结尾且末位为 d 的子串中,模 d 为 0 的个数(d==0 时 f[0][0] 为 0)
ans += f[d][0]
return ans
if __name__ == "__main__":
s = "12936"
print(count_substrings(s))
·
我们相信Go语言和算法为普通开发者提供了困境的“面试利器”,并致力于分享全面的编程知识。在这里,您可以找到最新的Go语言教程、算法解析、提升面试竞争力的秘籍以及行业动态。 欢迎关注“福大规模架构师每日一题”,让 Go 语言和算法助力您的职业发展