目录
题目描述
对于一个字符串 S,我们定义 S 的分值 f(S)为 S 中出现的不同的字符个数。例如 f(“aba”) = 2,f(“abc”) = 3, f(“aaa”) = 1,f(“aba”)=2,f(“abc”)=3,f(“aaa”)=1。
现在给定一个字符串 S [0...n − 1](长度为 n),请你计算对于所有 S 的非空子串 S [i...j](0 ≤ i ≤ j < n),f(S[i...j]) 的和是多少。
输入描述
输入一行包含一个由小写字母组成的字符串 S。
其中,1≤ n ≤。
输出描述
输出一个整数表示答案。
输入输出样例
示例 1
输入
ababc
输出
28
运行限制
- 最大运行时间:1s
- 最大运行内存: 128M
题目来源:子串分值和
1、题目分析
- 用dp [ i ] 表示以第 i 个数组元素结尾的非空子串内不同元素的和
- 推理: 以题目给出的输入为例 ababc
- dp[ 0 ] = 1
- dp[ 1 ] = 2 + 1 = 3
- dp[ 2 ] = 2 + 2 + 1 = 5
- dp[ 3 ] = 2 + 2 + 2 + 1 = 7
- dp[ 4 ] = 3 + 3 + 3 + 2 + 1 = 12
- dp = 1 + 3 + 5 +7 + 12 = 28
- 所以我们求出以每个位置为 结尾的非空子串的不同元素的和,然后把他们相加。
- 怎么去求每个dp[ i ] 呢?
- 首先以每个c[ i ] 为尾部,计算前面出现过的字符的种类和个数
- 然后从前到后,没经过一个数组元素,便在记录数组里面减去它的类别的个数
2、代码展示
import java.util.Scanner;
public class _子串分值和 {
//计算以s[predix]为尾部的所有非空子串的不同字符的个数
//s是输入的字符序列,predix是尾部下标,length是s数组长度
static void maxSubSum(char[] s,int predix,int length,int dp[]) {
//存储出现的每个数组元素的个数
int character[] = new int[27];
for(int i=0;i<=predix;i++) {
character[s[i]-'a'] ++;
}
//从第一个元素开始,计算以s[predix]为尾部的所有子串
for(int i=0;i<=predix;i++) {
int count=0;
if(i>0) character[s[i-1]-'a']--;
for(int j=0;j<26;j++) {
//计算s[i]-s[predix]这个非空子串的不同字符的个数
if(character[j]!=0)
count++;
}
//把结果记录到dp数组
dp[predix]+=count;
}
}
public static void main(String[] args) {
System.out.println("Please input data:");
Scanner scanner = new Scanner(System.in);
String str = scanner.next();
int length = str.length();
int NUM;
NUM = length+5;
int dp[] = new int[NUM];
char s[] = str.toCharArray();
int result = 0;
for(int i=0;i<length;i++) {
maxSubSum(s, i, length,dp);
result+=dp[i];
}
System.out.println(result);
}
}
但是这段代码只能通过50%,其它部分超时。所以需要优化。
3、优化
import java.util.Scanner;
public class _子串分值和 {
//计算以s[predix]为尾部的所有非空子串的不同字符的个数
//s是输入的字符序列,predix是尾部下标,length是s数组长度
static void maxSubSum(char[] s,int predix,int length,int dp[]) {
//存储出现的每个数组元素的个数
int character[] = new int[27];
int count=0;
for(int i=0;i<=predix;i++) {
character[s[i]-'a'] ++;
if(character[s[i]-'a'] == 1)
count++;
}
dp[predix] = count;
//从第一个元素开始,计算以s[predix]为尾部的所有子串
for(int i=1;i<=predix;i++) {
character[s[i-1]-'a']--;
if(character[s[i-1]-'a']==0)
count--;
//把结果记录到dp数组
dp[predix]+=count;
}
}
public static void main(String[] args) {
System.out.println("Please input data:");
Scanner scanner = new Scanner(System.in);
String str = scanner.next();
int length = str.length();
int NUM;
NUM = length+5;
int dp[] = new int[NUM];
char s[] = str.toCharArray();
int result = 0;
for(int i=0;i<length;i++) {
maxSubSum(s, i, length,dp);
result+=dp[i];
}
System.out.println(result);
}
}
但是还是只能通过60%,剩余40%属于超时。
3、继续优化
- dp[ i ] :表示以 c[ i ] 元素结尾的子串的非空不同字符的个数之和
- dp[ i ] 和 dp[ i-1 ] 的比较
- dp[ i ] 与 dp[ i-1 ] 相比,dp[ i ] 表示以 c[ i ] 元素结尾的子串的非空不同字符的个数之和,dp[ i-1 ] 表示以 c[ i-1 ] 元素结尾的子串的非空不同字符的个数之和。dp[ i ]多一个元素。多出来的这个元素,对于前面有c[ i ] 这个元素的子串而言没有什么作用,因为多个 c[ i ] 只会被计算1次。但是对于那些没有 c[ i ] 元素的子串而言,他们就少计算了一个字符。
- 最后一个 c[ i ] 的位置 减去 倒数第二个 c[ i ] 的位置就是只有一个c[ i ] 且在末尾的最长子串。
比如dp[ 3 ] = dp[ 2 ] + 3 -1
对于下标从 0 到 0 的元素来说( 以它们为起点 ),以 下标 2 结尾和下标 3 结尾并没有什么不同,因为 c[ 3 ] = b,而 c[ 0 ] = b ,前面已经有了,不会参与不同符号的计数中去。
对于下标从 1 到 3 的元素来说( 以它们为起点 ),与dp[ 2 ]相比,少了一个符号c[ 3 ],而这个符号在1 - 3 之间是没有出现过的,所以多一个子串,就要 +1, 为什么只 +1 呢?这是因为除c[ 3 ] 以外的不同单词的个数在 dp[ 2 ] 中已经计算过了。
状态转移方程:dp[ n ] = dp [ n-1 ] + n - pos[ c[ i-1 ] ]
代码:
import java.util.Scanner;
public class _子串分值和2 {
public static void main(String[] args) {
System.out.println("Please input data:");
Scanner inputScanner = new Scanner(System.in);
String string = inputScanner.next();
int dp[] = new int[string.length()+5];
int pos[] = new int[200];
//如果不是定义为long类型,那只能通过60%
long ans=0L;
char[] c = string.toCharArray();
for(int i=1;i<=string.length();i++) {
dp[i] = dp[i-1] + i - pos[c[i-1]];
pos[c[i-1]]=i;
ans+=dp[i];
}
System.out.println(ans);
}
}
如果不是定义为long类型,那只能通过60%
4、方法二
我们可以从当前这个字符在哪些子串中第一次出现(因为只有第一次出现才记录分数),子串的个数之和也就是这个位置的字符产生的贡献值。
那么dp[ i ] = (i - pos[ c[ i ] ])*(length - i)
public class _子串分值和3 {
public static void main(String[] args) {
//记录最后一次字符串出现的位置
int pos[] = new int[27];
Scanner inputScanner = new Scanner(System.in);
char [] c = inputScanner.next().toCharArray();
//这个地方如果不是long类型,只能通过80%
//String类型是length()方法,char数组是length属性
long length = c.length;
//这个地方也要用long类型
long res = 0;
for(int i=0;i<length;i++) {
res += (long)((i-pos[c[i]-'a'])*(length-i));
pos[c[i]-'a']=i;
}
System.out.println(res);
}
}
注意:
- length必须使用 long 类型,这是由数据集决定的。所以以后代码有少部分答案错误,代码没有明显的逻辑错误,那试试看是不是数据类型表示的范围不够。
- res 同理,也必须使用 long 类型, int 类型不足以表示答案。