/**
* leetcode1371 每个元音字母包含偶数次的最长子字符串
* 方法四 ,前缀和+状态压缩
* @param s
* @return
*/
public static int findTheLongestSubstring(String s){
int n = s.length();
int[] pos = new int[1<<5]; // 5个字符,00000~11111,总共有32种状态, 00001表示a出现次数为奇数次,其他字符出现次数为偶数次
Arrays.fill(pos,-1);
int ans = 0,status = 0;
pos[0] = 0;
for(int i=0;i<n;i++){
char ch = s.charAt(i);
if(ch == 'a'){
status ^= (1<<0); //00001 ,^是异或运算符,<<是左移运算符 初始如果从来没出现过是这样的 00000<<00001=00001 ,但是如果e出现过那么 00010 << 00001 就变成 00011
}else if(ch == 'e'){
status ^= (1<<1); //00010
}else if(ch == 'i'){
status ^= (1<<2); //00100
}else if(ch == 'o'){
status ^= (1<<3); //01000
}else if(ch == 'u'){
status ^= (1<<4); //10000
}
if(pos[status] >= 0){
if(ans < i+1-pos[status]){
System.out.println(String.format("目前为止符合条件的最长子字符串为%s",s.substring(pos[status],i+1)));
}
ans = Math.max(ans,i+1-pos[status]);
}else {
//如果该奇偶状态位置从未记录过,记录第一次出现该奇偶状态所在的位置
pos[status]=i+1;
}
}
return ans;
}
/**
* 遍历子字符串
* @param s
* @return
*/
static List<String> test003(String s){
List<String> list = new ArrayList<>();
for (int i = 0; i <s.length() ; i++) {
for (int j = i+1; j <=s.length() ; j++) {
list.add(s.substring(i,j));
System.out.println(s.substring(i,j));
}
}
return list;
}
@Data
static class Result1 {
private int maxSubLength;
private String maxSubString;
private int start;
private int end;
List<Result1> subStringList;
}
/**
* 求1和2都出现偶数次的最长子字符串长度
* 输入示例 2231145
* 输出 7
* 说明:最长子字符串是 2231145, 它刚好包含两个1和两个2
* 方法一 暴力穷举,遍历字符串的所有子串
* @param s
* @return
*/
static Result1 findTheLongestSubstring1(String s){
Result1 result1 = new Result1();
char[] chars = s.toCharArray();
int len = s.length();
int maxSubLength = 0;
int start=0;
int end = 0;
List<Result1> subStringList = new ArrayList<>();
String maxSubString = "";
for (int i = 0; i <len ; i++) {
for (int j = i+1; j <=len ; j++) {
int cnt1 = 0;
int cnt2 = 0;
for (int k = i; k <j ; k++) {
if(chars[k]=='1'){
cnt1++;
}else if(chars[k]=='2') {
cnt2++;
}
}
if(cnt1%2==0 && cnt2%2==0){
String substring = String.valueOf(chars,i,j-i);
System.out.println(substring);
if( (j-i) > maxSubLength){
maxSubLength = j-i;
maxSubString = String.valueOf(chars,i,j-i);
start = i;
end = j;
}
Result1 result11 = new Result1();
result11.setMaxSubLength(j-i);
result11.setMaxSubString(substring);
result11.setStart(i);
result11.setEnd(j);
subStringList.add(result11);
}
}
}
System.out.println(maxSubLength);
System.out.println(maxSubString);
result1.setMaxSubLength(maxSubLength);
result1.setMaxSubString(maxSubString);
result1.setStart(start);
result1.setEnd(end);
result1.setSubStringList(subStringList);
return result1;
}
/**
* 方法二 利用前缀和数组
* 定义pre[i][k]表示在字符串前i个字符中,第k个字符一共出现的次数,
* 假设我们需要求出[l,r]这个区间的子串某个字符出现的次数,就可以用pre[r][k]-pre[l-1][k]
* 利用前缀和优化了统计子串的时间复杂度,然而枚举所有的子串复杂度仍需要O(n^2)
*/
static void findTheLongestSubstring2(){
String s = "122231145";
int[][] pre = new int[s.length()+1][2];
char[] chars = s.toCharArray();
int len = s.length();
int maxSubLength = 0;
String maxSubString = "";
int t1=0;
int t2=0;
for (int i = 1; i <=len ; i++) {
if(chars[i-1]=='1'){
pre[i][0] = ++t1;
}else {
pre[i][0] = t1;
}
if(chars[i-1]=='2'){
pre[i][1] = ++t2;
}else {
pre[i][1] = t2;
}
}
for (int i = 1; i <=len ; i++) {
for (int j = i; j <=len ; j++) {
int cnt1 = pre[j][0] - pre[i-1][0];
int cnt2 = pre[j][1] - pre[i-1][1];
System.out.println(String.format("区间%d~%d,1出现次数%d",i,j,cnt1));
System.out.println(String.format("区间%d~%d,2出现次数%d",i,j,cnt2));
if(cnt1%2==0 && cnt2%2==0){
if( j-(i-1) > maxSubLength){
maxSubLength = j-(i-1);
maxSubString = String.valueOf(chars,i-1,j-(i-1));
}
}
}
}
System.out.println(maxSubLength);
System.out.println(maxSubString);
}
/**
* 方法三,在获取前缀和的同时就去看遍历到当前字符位置i,从第1个位置找到i,看满足条件的区间j~i
* 利用前缀和优化了统计子串的时间复杂度,然而枚举所有的子串复杂度仍需要O(n^2),
* 可以这样优化,枚举字符串每个位置i,计算以它结尾的满足条件的最长字符串的长度。找到最小的j满足
* pre[i][k]-pre[j][k]最小的j,长度就是i-j
* @param
*/
static void findTheLongestSubstring3(){
String s = "122231145";
int[][] pre = new int[s.length()+1][2];
char[] chars = s.toCharArray();
int len = s.length();
int maxSubLength = 0;
String maxSubString = "";
int t1=0;
int t2=0;
for (int i = 1; i <=len ; i++) {
if(chars[i-1]=='1'){
pre[i][0] = ++t1;
}else {
pre[i][0] = t1;
}
if(chars[i-1]=='2'){
pre[i][1] = ++t2;
}else {
pre[i][1] = t2;
}
for (int j = 1; j <=i ; j++) {
int cnt1 = pre[i][0] - pre[j-1][0];
int cnt2 = pre[i][1] - pre[j-1][1];
System.out.println(String.format("区间%d~%d,1出现次数%d",j,i,cnt1));
System.out.println(String.format("区间%d~%d,2出现次数%d",j,i,cnt2));
if(cnt1%2==0 && cnt2%2==0){
if( i-(j-1) > maxSubLength){
maxSubLength = i-(j-1);
maxSubString = String.valueOf(chars,j-1,i-(j-1));
}
}
}
}
System.out.println(maxSubLength);
System.out.println(maxSubString);
}
/**
* 方法四,利用奇偶性
* 偶数这个条件其实告诉了我们,对于满足条件的子串而言,两个前缀和 pre[i][k]\textit{pre}[i][k]pre[i][k] 和 pre[j][k]\textit{pre}[j][k]pre[j][k] 的奇偶性一定是相同的
* 此时我们就可以利用哈希表存储每一种奇偶性(即考虑所有的元音字母)对应最早出现的位置,边遍历边更新答案。
*/
static void findTheLongestSubstring4(){
String s = "122231145";
char[] chars = s.toCharArray();
int len = s.length();
int maxSubLength = 0;
int status00 = -1;
int status01 = -1;
int status10 = -1;
int status11 = -1;
int status1 = 0;
int status2 = 0;
for (int i = 0; i <len ; i++) {
if(chars[i]=='1'){
status1 ^= 1;
}else if(chars[i]=='2'){
status2 ^= 1;
}
if(status1==0 && status2==0){
if(status00==-1){
status00 = i;
}
maxSubLength = Math.max(maxSubLength,i-status00);
}else if(status1==0 && status2==1){
if(status01==-1){
status01 = i;
}
maxSubLength = Math.max(maxSubLength,i-status01);
}else if(status1==1 && status2==0){
if(status10==-1){
status10 = i;
}
maxSubLength = Math.max(maxSubLength,i-status10);
}else if(status1==1 && status2==1){
if(status11==-1){
status11 = i;
}
maxSubLength = Math.max(maxSubLength,i-status11);
}
}
System.out.println(maxSubLength);
}
static int findTheLongestSubstring6(String s){
int[][] pre = new int[s.length()+1][5];
char[] chars = s.toCharArray();
int len = s.length();
int maxSubLength = 0;
String maxSubString = "";
int t1=0;
int t2=0;
int t3=0;
int t4=0;
int t5=0;
for (int i = 1; i <=len ; i++) {
if(chars[i-1]=='a'){
pre[i][0] = ++t1;
}else {
pre[i][0] = t1;
}
if(chars[i-1]=='e'){
pre[i][1] = ++t2;
}else {
pre[i][1] = t2;
}
if(chars[i-1]=='i'){
pre[i][2] = ++t3;
}else {
pre[i][2] = t3;
}
if(chars[i-1]=='o'){
pre[i][3] = ++t4;
}else {
pre[i][3] = t4;
}
if(chars[i-1]=='u'){
pre[i][4] = ++t5;
}else {
pre[i][4] = t5;
}
for (int j = 1; j <=i ; j++) {
int cnt1 = pre[i][0] - pre[j-1][0];
int cnt2 = pre[i][1] - pre[j-1][1];
int cnt3 = pre[i][2] - pre[j-1][2];
int cnt4 = pre[i][3] - pre[j-1][3];
int cnt5 = pre[i][4] - pre[j-1][4];
if(cnt1%2==0 && cnt2%2==0 && cnt3%2==0 && cnt4%2==0 && cnt5%2==0){
if( i-(j-1) > maxSubLength){
maxSubLength = i-(j-1);
maxSubString = String.valueOf(chars,j-1,i-(j-1));
}
}
}
}
return maxSubLength;
}