Java算法实战:揭秘回文字符串查找和计数

1. 回文字符串基础知识

回文字符串是一系列字符,正着读和反着读都是一样的,如“madam”或“racecar”。在编程中,我们时常需要检测或者处理回文字符串,例如数据验证、DNA序列分析等。

1.1 简述回文字符串的定义

回文是指一个字符串忽略标点、大小写和空格,正向和反向都一样。回文字符串的定义很简单,举例来说:“level”, “deified”, “civic” 和 “radar” 等都是回文字符串。

1.2 回文字符串的性质和应用场景

  • 性质:回文字符串的主要性质是它们是对称的,其第一个字符和最后一个字符相同,第二个字符和倒数第二个字符相同,以此类推。
  • 应用场景:回文字符串的概念在许多领域都有应用,例如文本编辑器中查找回文单词,生物学中查找特定的DNA序列,甚至在某些加密算法中也会使用到回文结构。

2. 查找回文子字符串的方法

2.1. 暴力方法

暴力方法是通过检查所有的子字符串,来确定它们是否为回文。以下是使用Java语言的代码示例:

public class BruteForcePalindrome {

    public static boolean isPalindrome(String s) {
        for (int i = 0, j = s.length() - 1; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }

    public static void findPalindromes(String input) {
        int n = input.length();
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j <= n; j++) {
                String substr = input.substring(i, j);
                if (isPalindrome(substr)) {
                    System.out.println(substr);
                }
            }
        }
    }

    public static void main(String[] args) {
        String s = "ababa";
        findPalindromes(s);
    }
}

2.2. 中心扩展算法

中心扩展算法从每个可能的中心开始检查可能的最长回文。以下是Java代码示例:

public class CenterExpansionPalindrome {

    public static void findPalindromes(String input) {
        for (int center = 0; center < 2 * input.length() - 1; center++) {
            int left = center / 2;
            int right = left + center % 2;
            while (left >= 0 && right < input.length() && input.charAt(left) == input.charAt(right)) {
                System.out.println(input.substring(left, right + 1));
                left--;
                right++;
            }
        }
    }

    public static void main(String[] args) {
        String s = "racecar";
        findPalindromes(s);
    }
}

2.3. Manacher’s Algorithm(马拉车算法)

马拉车算法是一种高效的查找最长回文子字符串的方法。这里是一个Java实现:

public class ManacherAlgorithm {

    private static String preProcess(String s) {
        StringBuilder sb = new StringBuilder("$#");
        for (int i = 0; i < s.length(); i++) {
            sb.append(s.charAt(i));
            sb.append('#');
        }
        sb.append('@');
        return sb.toString();
    }

    public static void findPalindromes(String input) {
        String T = preProcess(input);
        int n = T.length();
        int[] P = new int[n];
        int C = 0, R = 0;
        for (int i = 1; i < n - 1; i++) {
            int i_mirror = 2 * C - i;
            P[i] = (R > i) ? Math.min(R - i, P[i_mirror]) : 0;
            while (T.charAt(i + 1 + P[i]) == T.charAt(i - 1 - P[i])) {
                P[i]++;
            }
            if (i + P[i] > R) {
                C = i;
                R = i + P[i];
            }
        }
        for (int i = 1; i < n - 1; i++) {
            if(P[i] > 0) {
                System.out.println(input.substring((i - 1 - P[i]) / 2, (i - 1 + P[i]) / 2));
            }
        }
    }

    public static void main(String[] args) {
        String s = "abababa";
        findPalindromes(s);
    }
}

在上述实现中,先对原始字符串进行预处理,在每个字符间插入特殊符号(比如#),这样可以统一处理偶数长度和奇数长度的回文。然后使用ManacherAlgorithm中的findPalindromes方法寻找回文子字符串。

3. 统计回文子字符串出现次数

3.1. 哈希表的使用

在找到所有回文子字符串后,我们需要统计它们的出现次数。要有效地做到这一点,我们可以使用哈希表(在Java中通常是HashMap)来存储每个子字符串及其对应的计数。下面的Java代码示例展示了如何使用哈希表来统计回文子字符串的次数。

import java.util.HashMap;
import java.util.Map;

public class PalindromeFrequency {

    public static Map<String, Integer> countPalindromes(String[] palindromes) {
        Map<String, Integer> palindromeCount = new HashMap<>();
        for (String palindrome : palindromes) {
            palindromeCount.put(palindrome, palindromeCount.getOrDefault(palindrome, 0) + 1);
        }
        return palindromeCount;
    }

    // 假设我们已经有了一个回文子字符串数组
    public static void main(String[] args) {
        String[] palindromes = {"aba", "level", "ana", "level"};
        Map<String, Integer> count = countPalindromes(palindromes);
        for (String key : count.keySet()) {
            System.out.println("Palindrome: " + key + ", Count: " + count.get(key));
        }
    }
}

3.2. 结果去重和统计实现

为了去除重复的回文子字符串,我们可以在将字符串加入哈希表之前,先检查它是否已经存在。此外,我们也可能需要对回文字符串按照一定的规则(如字母表顺序)进行排序,以便更好地组织和查询。
以下代码示例展示了如果同时考虑去重和统计:

import java.util.TreeMap;

public class PalindromeFrequencyAndDeduplication {

    public static TreeMap<String, Integer> countPalindromes(String input) {
        TreeMap<String, Integer> palindromeCount = new TreeMap<>();
        // 此处省略查找回文子字符串的代码... (可以使用上面讨论的任何算法)
        // 假设我们已经有了一个包含所有回文子字符串的列表
        String[] foundPalindromes = {"aba", "level", "ana", "level"};
        for (String palindrome : foundPalindromes) {
            palindromeCount.put(palindrome, palindromeCount.getOrDefault(palindrome, 0) + 1);
        }
        return palindromeCount;
    }

    public static void main(String[] args) {
        String s = "abacdclevelanalevel";
        TreeMap<String, Integer> count = countPalindromes(s);
        for (Map.Entry<String, Integer> entry : count.entrySet()) {
            System.out.println("Palindrome: " + entry.getKey() + ", Count: " + entry.getValue());
        }
    }
}

使用TreeMap而不是HashMap是因为TreeMap会根据键自然排序,使得输出更易于阅读和检查。

4. 具体实现

file

4.1. 纯Java实现代码示例

现在来实现一个完整的Java类,该类可以一次性查找所有的回文子字符串,并统计每个回文子字符串的出现次数。

import java.util.HashMap;
import java.util.Map;

public class PalindromeFinder {

    private static boolean isPalindrome(String s) {
        for (int i = 0, j = s.length() - 1; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }

    public static Map<String, Integer> findAllPalindromes(String s) {
        Map<String, Integer> palindromeOccurrences = new HashMap<>();
        for (int i = 0; i < s.length(); i++) {
            for (int j = i + 1; j <= s.length(); j++) {
                String subStr = s.substring(i, j);
                if (isPalindrome(subStr)) {
                    palindromeOccurrences.put(subStr, palindromeOccurrences.getOrDefault(subStr, 0) + 1);
                }
            }
        }
        return palindromeOccurrences;
    }

    public static void main(String[] args) {
        String s = "abaxabaxabb";
        Map<String, Integer> palindromes = findAllPalindromes(s);
        for (Map.Entry<String, Integer> entry : palindromes.entrySet()) {
            System.out.println("Palindrome: " + entry.getKey() + ", Count: " + entry.getValue());
        }
    }
}

4.2. 优化方案和性能分析

尽管上述实现可以工作,但在大型字符串上可能会变得非常慢。优化可以从两个方面进行:

  1. 使用更高效的算法,比如Manacher’s Algorithm,来找回文子串。
  2. 对记录和统计过程进行优化,比如使用TreeMap以保持排序,或者其它结构来优化查询和更新操作。

性能方面,我们应该考虑算法的时间复杂度和空间复杂度。原始的暴力搜索算法的时间复杂度是O(n^3),并且我们还需要额外的空间来存储所有的子字符串和计数。优化后的算法,如Manacher’s算法,将时间复杂度提高到了O(n),这对于处理长字符串数据来说,提升是非常明显的。

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逆流的小鱼168

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值