判断一个数字中是否包含两个相同的子串

本文介绍了一种算法,用于检测给定数字字符串中是否存在长度大于等于2的重复子串,并提供了一个具体的Java实现示例。

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

题目描述:

判断给定的一个数字,把它当成字符串以后,该字符串中是否包含相同的两个子串,子串的长度要求大于等于2。

比如:12123,该数字包含两个“12”子串;

又比如:1223122,该数字包含两个“122”子串。

输入:
待判断的正整型数字,最大长度为9。
输出:
0:不包含;
1:包含。
样例输入:
12123
样例输出:
1

代码实现:

import java.util.Scanner;

/**
 *判断给定的一个数字,把它当成字符串以后,该字符串中是否包含相同的两个子串,子串的长度要求大于等于2。
 *比如:12123,该数字包含两个“12”子串;又比如:1223122,该数字包含两个“122”子串。
 *
 */
public class SameSubString {

	public static void main(String[] args){
		Scanner cin=new Scanner(System.in);
		long num;
		while(cin.hasNext()){
			num=cin.nextLong();
			if(!valid(num))
				return;
			if(hasSameSubStr(num))
				System.out.println(1);
			else
				System.out.println(0);
		}
	}

	private static boolean hasSameSubStr(long num) {
		String str=String.valueOf(num);
		int length=str.length();
		int maxSubStrLength=length>>1;//最大字串长度
		int end=length-maxSubStrLength;//子串位置结束点
		for (int j = 0; j <= end; j++) {
		String sub=str.substring(j, j+maxSubStrLength);
		String nextStr=str.substring(j+1, length);//除去起始位置的字串
		if(nextStr.contains(sub))
			return true;
	}
		return false;
	}

	private static boolean valid(long num) {
		if(num<=0)
			return false;
		String str=String.valueOf(num);
		if(str.length()<4||str.length()>9)
			return false;
		return true;
	}
}


<think>嗯,用户问的是如何找到一个数字的连续子串,并判断这些子串是否是三的倍数,用Java解决。我需要先理解问题,然后思考解决步骤。 首先,题目应该是给定一个数字组成的字符串,比如“1234”,找出所有连续子串,然后判断每个子串对应的数字是否是3的倍数。例如,子串“12”对应的数字12,是否是3的倍数。 那用户的需求可能是在处理大数的情况下,因为如果数字很大,直接转换成整数可能会溢出,所以需要考虑其他方法。比如,利用数学性质来判断是否是3的倍数,而不需要实际计整个数。 我记得判断一个是否是3的倍数的方法是,各位数字之和是否能被3整除。这可能是一个关键点。所以,如果能计子串数字之和,就可以快速判断是否是3的倍数。 那问题就转化为:找出所有连续子串,计每个子串数字之和,判断是否能被3整除。统计这样的子串的数量。 但是,直接遍历所有子串的话,时间复杂度会是O(n²),当n很大的时候可能效率不高。比如字符串长度是1e5的话,这样的方法会超时。不过,用户可能只是需要解决LeetCode类似的问题,可能n的范围允许O(n²)的时间复杂度? 或者有没有更高效的方法?例如,利用前缀和和数学性质来优化。 比如,前缀和数组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较大时不可行。但可能对于小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(n²),仅适用于小规模数据 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、付费专栏及课程。

余额充值