A Count Task(求选取子串的方案数(这子串是连续的),子串中只有一种元素)

探讨ACountTask问题,即在长字符串中计算只包含单一类型小写字母的子串数量。通过算法分析和代码实现,展示如何高效解决此类计数任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A Count Task

时间限制: 1 Sec  内存限制: 128 MB

题目描述

Count is one of WNJXYK’s favorite tasks. Recently, he had a very long string and he wondered that how many substrings which contains exactly one kind of lowercase in this long string. But this string is so long that he had kept counting for several days. His friend Kayaking wants to help him, so he turns to you for help. 

 

输入

The input starts with one line contains exactly one positive integer T which is the number of test cases.
Each test case contains one line with a string which you need to do a counting task on.

 

输出

For each test case, output one line containing “y” where y is the number of target substrings.

 

 

样例输入

复制样例数据

3
qwertyuiop
qqwweerrttyyuuiioopp
aaaaaaaaaa

样例输出

10
30
55

 

提示

1≤T≤20,1≤len(String)≤10^5,1≤∑len(string)≤10^6
Strings only contain lowercase English letters.

比如aaaa4个a,那么选一个有4种,选两个有3种,选。。。(连续的!!)和为4+3+2+1,就这样遍历一遍就ok了。

/**/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <stack>
#include <queue>

typedef long long LL;
using namespace std;

int n;
char s[100005];

int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);

	scanf("%d", &n);
	while(n--){
		scanf("%s", s);
		int len = strlen(s);
		LL ans = 0;
		for (int i = 0; i < len; i++){
			int num = 1;
			char ch = s[i];
			for (i++; i < len; i++){
				if(ch == s[i]) num++;
				else break;
			}
			ans += (LL)(1 + num) * num / 2;//记得long long,不然错的。
			i--;
		}
		printf("%lld\n", ans);
	}

	return 0;
}
/**/

 

<think>嗯,用户问的是如何找到一个数字的连续子串,并判断这些子串是否是三的倍数,用Java解决。我需要先理解问题,然后思考解决步骤。 首先,题目应该是给定一个由数字组成的字符串,比如“1234”,找出所有连续子串,然后判断每个子串对应的数字是否是3的倍数。例如,子串“12”对应的数字12,是否是3的倍数。 那用户的需可能是在处理大数的情况下,因为如果数字很大,直接转换成整数可能会溢出,所以需要考虑其他方法。比如,利用数学性质来判断是否是3的倍数,而不需要实际计算整个数。 我记得判断一个数是否是3的倍数的方法是,各位数字之和是否能被3整除。这可能是一个关键点。所以,如果能计算出子串的数字之和,就可以快速判断是否是3的倍数。 那问题就转化为:找出所有连续子串,计算每个子串的数字之和,判断是否能被3整除。统计这样的子串的数量。 但是,直接遍历所有子串的话,时间复杂度会是O(),当n很大的时候可能效率不高。比如字符串长度是1e5的话,这样的方法会超时。不过,用户可能只是需要解决LeetCode类似的问题,可能n的范围允许O()的时间复杂度? 或者有没有更高效的方法?例如,利用前缀和和数学性质来优化。 比如,前缀和数组sum,其中sum[i]表示前i位的数字之和。那么子串s[j...i]的和就是sum[i] - sum[j]。如果这个差能被3整除,那么对应的数字就能被3整除。这时候,问题转化为寻找所有i和j,使得(sum[i] - sum[j]) mod 3 == 0。这等价于sum[i] mod 3 == sum[j] mod 3。 这样的话,我们可以维护一个数组或者哈希表,记录前缀和mod3出现的次数。这样可以在O(n)的时间复杂度内解决问题。 比如,遍历字符串时,计算当前前缀和的mod3的值,然后查看之前有多少次相同的mod值,累计到结果中。同时,需要处理前缀和本身是否被3整除的情况,即当mod为0时,自身也是一个有效子串。 但是这里需要注意,子串连续的数字组成的数,比如“123”的子串是“1”,“2”,“3”,“12”,“23”,“123”。每个子串对应的数字之和是否是3的倍数。例如,“123”的和是1+2+3=6,是3的倍数,所以对应的数字123也是3的倍数。所以,判断子串的数字之和是否是3的倍数,就可以知道该数字是否是3的倍数。因此,正确的做法应该是计算每个子串的数字之和,并判断其是否能被3整除。 那这个时候,利用前缀和mod3的方法是否可行? 举个例子,比如字符串"123": 前缀和数组sum[0]=0,sum[1]=1,sum[2]=3,sum[3]=6. 然后对于子串s[j..i-1],对应的sum[i] - sum[j]。比如i=3(对应sum[3]=6),j=0,sum[i]-sum[j]=6,mod3为0。所以对应的子串是“123”,其和是6,能被3整除。因此该子串对应的数字是3的倍数。 因此,只要找到所有i>j,满足sum[i] ≡ sum[j] mod3,这样的子串的和能被3整除,从而该子串对应的数字能被3整除。因此,可以维护一个数组,记录各个余数出现的次数,这样每次计算当前余数,然后加上之前相同余数的出现次数,就能得到符合条件的子串数量。 但这样是否准确?例如,假设当前前缀和mod3的余数是1,而之前出现过k次余数1的情况,那么这k次对应的每个位置j到当前位置i的子串的和的余数是0,因此这些子串的和能被3整除,对应的数字也能被3整除。所以这样的方法是正确的。 但是这样的话,每个子串对应的数字是否等于其各位数字之和?或者说,这个方法是否正确? 哦,这里可能存在一个误区。因为一个数的各位数字之和能被3整除,才能说明这个数本身能被3整除。所以,判断子串各位之和是否能被3整除是正确的,和这个数是否是3的倍数是等价的。因此,我们的方法正确。 所以,问题可以转化为:统计所有连续子串的各位数字之和能被3整除的数量。而这个问题可以通过前缀和mod3,然后统计余数出现的次数来高效解决。 这样,时间复杂度是O(n),空间复杂度是O(1)(因为余数只有0、1、2三种情况)。 所以,具体的步骤可以是: 1. 初始化一个长度为3的数组count,用来记录前缀和mod3的余数出现的次数。初始时,count[0] = 1,因为前缀和为0时(即没有元素的时候),余数为0,出现了一次。 2. 遍历字符串中的每个字符,将其转换为数字,累加到当前的前缀和sum中。 3. 计算当前sum的余数mod = sum %3。 4. 当前余数出现的次数之前有多少次,就将这个次数加到结果中。例如,如果当前余数是0,那么之前余数0出现的次数有多少次,就有多少个子串的和能被3整除。 5. 更新count数组中的对应余数的次数。 例如,例子"123": 初始化count = [1,0,0], result=0. i=0,字符是'1',sum=1,mod=1。count[mod]是count[1]=0,所以result +=0。然后count[1]++ → count=[1,1,0]. i=1,字符是'2',sum=1+2=3,mod=0。此时count[0]是1,所以result +=1 → result=1。然后count[0]++ → count=[2,1,0]. i=2,字符是'3',sum=3+3=6,mod=0。此时count[0]是2,所以result +=2 → result=3。然后count[0]++ → count=[3,1,0]. 总共有3个子串满足条件。但实际例子中的每个子串: "1" → 和1,不行。 "12" → 3 → 可以。 "123" →6 → 可以。 "2" →2→不行。 "23"→5→不行。 "3"→3→可以。 总共有3个符合条件的子串:12,123,3。所以结果应该是3,而上述方法得到的结果是1+2=3,正确。 这说明这种方法是对的。那这个时候,用户的问题可以这样解决,使用O(n)时间复杂度的算法,而不是暴力枚举所有子串。 那现在需要将这个思路转化为Java代码。同时,需要注意处理大数的情况,比如字符串非常长的时候,sum可能会溢出。例如,如果字符串长度是1e5,每个数字都是9,sum会达到9e5,这可能会超过Java的int的范围(最大到2^31-1,即约2e9)。所以,如果n是1e5的话,sum可能达到9e5,这在int范围内是没问题的。但如果是更大的n,可能需要使用long类型。但是题目中的字符串长度可能不会超过导致sum溢出的情况,或者题目中的约束允许,所以这里可能需要处理一下。 或者,由于sum的mod3的特性,是否可以在计算sum时直接mod3,从而避免sum过大? 是的,因为sum mod3的结果只和当前数字的mod3有关。因此,在每次计算sum时,可以保持sum mod3的值,而不需要保留sum的完整数值。例如: sum += digit; mod = sum %3; 但这样是否正确?假设原来的sum是s,加上digit之后sum变成s+digit。mod3等于 (s + digit) mod3。而如果我们保持sum mod3的值,那么每次只需计算当前sum mod3的值,这样sum可以一直保持mod3后的结果,从而避免溢出。 例如,sum当前是mod3后的余数r。然后加上digit后的sum mod3等于 (r + digit) mod3。这样,sum可以保持为一个较小的数,不会溢出。 所以,可以修改算法,在每一步sum都保存为sum mod3的值。这样,sum的取值范围是0、1、2,不会溢出。这样无论字符串多长,都可以处理。 那这个思路的正确性如何? 举个例子,假设之前的sum mod3是r,现在加上digit的值d。新的sum的mod3应该是 (r +d) mod3。所以,只要每次sum保存的是当前的前缀和mod3的值,就可以正确计算后续的mod3。 这样,整个算法可以优化为: 初始化sum_mod =0,count数组初始为count[0]=1. 然后遍历每个字符d: sum_mod = (sum_mod + d) %3; result += count[sum_mod]; count[sum_mod]++; 这样是否正确? 或者,可能需要考虑余数的正确累加方式。例如,假设sum_mod是当前的前缀和mod3的值,当加上新的digit后,新的sum_mod应该是 (sum_mod + digit) %3。这样,每次前缀和的mod3的值是正确的。这似乎是对的。 比如,之前的例子: 字符串"123": 初始化sum_mod=0,count=[1,0,0], result=0. 第一个字符'1',digit=1: sum_mod = (0 +1) %3 =1. result += count[1] → count[1]是0,result还是0. 然后count[1]++ → count=[1,1,0]. 第二个字符'2',digit=2: sum_mod = (1+2) %3 =3%3=0. result +=count[0] → count[0]是1,所以result=1. count[0]++ → count=[2,1,0]. 第三个字符'3',digit=3: sum_mod = (0+3) %3=3%3=0. result +=count[0] →2 → result=3. count[0]++ →count=[3,1,0]. 最终结果是3,正确。 所以,这说明,我们可以将sum_mod保持在mod3的状态,避免sum变量溢出。这样,无论字符串多长,都不需要担心数值溢出问题,因为sum_mod的值只有0、1、2。 因此,这个方法不仅高效,而且能处理大数情况。 所以,现在可以将这个思路转化为Java代码。 Java代码的大致步骤: - 初始化一个数组count,长度为3,count[0]=1,其他为0. - 遍历字符串中的每个字符,转换为数字,并计算当前sum_mod. - 累加count[sum_mod]到结果中. - 更新count[sum_mod]++. 这样时间复杂度是O(n),空间复杂度O(1). 但需要验证这个方法是否正确。例如,考虑另一个例子,比如输入"333": 各个子串是: 3 →3 →是. 3 →3 →是. 3 →3 →是. 33 →6 →是. 33 →6 →是. 333 →9 →是. 总共有6个符合条件的子串。根据算法: 初始化count=[1,0,0], result=0. 第一个字符3: sum_mod = (0 +3) %3=0. result += count[0] →1 →result=1. count[0] becomes 2. 第二个字符3: sum_mod = (0 +3) %3=0. result +=count[0] →2 →result=3. count[0] becomes3. 第三个字符3: sum_mod=0+3=3%3=0. result +=3 →3+3=6. count[0] becomes4. 总结果是6,正确。所以算法正确。 这说明这种方法是正确的。 那么,用户的问题是要解所有连续子串中是3的倍数的数量,可以用这种方法高效解决。 现在,需要将这个思路用Java代码实现。 所以,Java代码大致如下: public int countSubstringsDivisibleByThree(String numStr) { int[] count = new int[3]; count[0] = 1; int sumMod = 0; int result =0; for (char c : numStr.toCharArray()) { int digit = c - '0'; sumMod = (sumMod + digit) %3; result += count[sumMod]; count[sumMod]++; } return result; } 但这里有一个问题:当sumMod为0时,说明当前前缀和本身可以被3整除,因此从开始到当前位置的子串是有效的。例如,在例子"123"中,当处理到第三个字符时,sumMod是0,此时count[sumMod]是3,所以每次result加的是count[sumMod]的值,也就是前面已经有多少次同样的余数。 比如,当处理到第二个字符时,sumMod是0,此时count[0]是1(初始值被算进去),所以result加1,此时结果变为1,这对应的是前缀和到第二个字符时的子串,即"12"。而第三次sumMod是0时,count[0]已经增加到2,所以result +=2,得到总共有1+2=3。这对应的是当第三次sumMod为0时,有两次之前的前缀余数也为0,所以这两个位置到当前位置的子串的和都能被3整除,加上当前前缀和本身余数0的情况。哦,这里可能有一个问题:初始的count[0] =1,对应的是前缀和为0的时候,也就是没有元素的时候。例如,当sumMod第一次变为0时,count[sumMod]是1(初始值),所以result +=1。这对应的是从开始到当前位置的子串(比如前两位的和是3,余数0),这个时候,子串是0..i,即整个前两位。所以这样是否正确? 是的,因为当sumMod变为0时,当前的前缀和对应的子串是0到i,而初始的count[0]=1是代表前缀和为0的情况(没有元素),所以当第一次出现sumMod为0时,count[sumMod]是初始的1,这时候result加上1,对应的是从起始位置到当前i的子串。例如,在例子"12"中,sum_mod是0,count[0]是1(初始状态),所以此时result +=1,这对应的是子串"12",正确。所以这个逻辑是对的。 因此,这个算法是正确的,时间复杂度O(n),空间O(1)。 那用户的问题的解决方法是这个。现在需要将这个思路整理成答案,并解释每一步,同时给出Java代码。 另外,用户可能需要注意,题目中的子串连续的,所以这个算法统计的是所有可能的连续子串的数字之和能否被3整除,从而对应的数字是否被3整除。这完全正确。 可能用户的问题可能还包含如何将子串转换为数字,并判断是否是3的倍数,但因为数字可能很大,所以直接转换不可行。而这个方法避免了转换,直接利用数学性质,所以是高效的。 综上,回答应该包括: - 问题分析:连续子串对应的数是否是3的倍数,不能用直接转换的方法,因为大数问题。 - 数学原理:数能被3整除的条件是各位数字之和能被3整除。 - 高效算法:前缀和mod3,利用哈希表统计余数出现次数,O(n)时间。 - Java代码实现。 - 测试用例验证。 需要注意用户提到的“连续子串”,所以所有连续的子字符串都需要被考虑。 另外,是否有其他解法?比如暴力法,枚举所有子串,计算各位和,判断是否是3的倍数。时间复杂度O(),当n较大时不可行。但可能对于小n来说是可以的。不过,用户可能希望得到更高效的解法。 所以,在回答中,应该比较不同的解法,并推荐最优的O(n)方法。 综上,现在整理回答。</think>$\boxed{问题分析}$ 给定数字字符串,统计其$\text{连续子串}$对应的数值为3的倍数的数量。例如:输入"123" → 输出3(子串"12","123","3") $\boxed{数学原理}$ 一个数能被3整除 $\Leftrightarrow$ 其各位数字之和能被3整除。 $\triangleright$ 核心思路:统计所有连续子串的各位数字之和能被3整除的数量。 $\boxed{最优解法(前缀和+模运算)}$ 1. **前缀和模3**:维护前缀和$sum$的模3余数 2. **余数匹配**:若两位置余数相同,则中间子串和必为3的倍数 3. **哈希统计**:用数组记录余数出现次数,快速累加结果 **时间复杂度**:$O(n)$,空间复杂度:$O(1)$ ```java public int countSubstringsDivisibleByThree(String numStr) { int[] count = new int[3]; // 记录余数出现次数 count[0] = 1; // 初始前缀和为0(空子串) int sumMod = 0, result = 0; for (char c : numStr.toCharArray()) { int digit = c - '0'; sumMod = (sumMod + digit) % 3; // 更新当前前缀和模3余数 result += count[sumMod]; // 累加相同余数的历史出现次数 count[sumMod]++; // 记录当前余数 } return result; } ``` $\boxed{算法流程示例}$ 以输入$s="123"$为例: 1. 初始状态:`count = [1,0,0]`, `sumMod=0`, `result=0` 2. 处理字符'1' → `sumMod=1` → `result += count[1]=0` → 更新`count[1]=1` 3. 处理字符'2' → `sumMod=(1+2)%3=0` → `result += count[0]=1` → 更新`count[0]=2` 4. 处理字符'3' → `sumMod=(0+3)%3=0` → `result += count[0]=2` → 最终结果3 $\boxed{暴力解法对比}$ 遍历所有子串计算和: ```java // 时间复杂度O(),仅适用于小规模数据 public int bruteForce(String s) { int n = s.length(), cnt = 0; for (int i=0; i<n; i++) { int sum = 0; for (int j=i; j<n; j++) { sum += s.charAt(j) - '0'; if (sum % 3 == 0) cnt++; } } return cnt; } ``` $\boxed{关键证明}$ **余数传递性**: 若前缀和$sum[i] \equiv sum[j] \pmod{3}$,则子串$s[j+1..i]$的和$\equiv 0 \pmod{3}$,对应数值必为3的倍数。 $\boxed{易错点}$ 1. 未初始化`count[0]=1`(漏算从起点开始的子串) 2. 负数取模处理错误(Java中`%`可能返回负数,需确保余数为正) $\boxed{扩展应用}$ 1. 判断任意除数$k$的倍数:调整模数即可(需处理$k=0$的特殊情况) 2. 最长和为$k$倍数的子串:记录余数最早出现位置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值