LeetCode第五题:最长回文

题目

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思考过程

  1. 首先需要查找最长的回文,必须要有一个变量max记录当前存在的最长回文长度。
  2. 第二步,我们遍历给定string中的每个字符,查找以这个字符为中心的最长回文长度,如果当前回文长度大于max,更新max
  3. 循环进行第二步,只到考察完string中的每一个字符

名词

考察:表示判断某个字符处的最大回文长度

更多思考

  1. 相同字符的思考
=================
abcdeedc
01234567
=================
当我们考察到第一个字符`e`的时候,我们不能去比较`3``5`的位置,因为`5`的位置字符也是`e`,这也是一种回文,所以我们应该直接比较`3``6`的位置。并且我们也不需要再考察下一个`e`(`5`位的`e`),因为已经包含在了上一次考察中(`4`位置的`e`
  1. 有没有不需要考察的情况
  • 如果我们已经找到一个比较长的回文,并且当前考察的字符很接近字符串的结尾或者开始,那么我们可以判断,当前字符到开始/结尾处的所有字符
// 这里是判断,是否查询出的maxStr已经是最大
if ((i + 1) * 2 - 1 < temp_length) {
    ......
}
  • 优先判断最远处的字符

实现

从左往右依次查找

public class Solution {

    public String longestPalindrome(String s) {
        // 构建最长字符串实例,默认长度为0
        int[] max = new int[2];
        char[] charArray = s.toCharArray();
        int length = charArray.length;
        // 处理空字符串的情况
        if (length == 0) {
            return "";
        }
        for (int i = 0; i < length; i++) {
            int temp_length = max[1] - max[0] + 1;
            // 这里是判断,是否查询出的maxStr已经是最大
            if (((length - i) * 2 - 1) < temp_length) {
                break;
            }
            // 这里先判断,是否有可能比已经存在的最长的回文更长
            if (mayMax(charArray, i, temp_length)) {
                // 0 存储开始的index, 1 存储结束的index
                int[] temp_max = new int[2];
                // 向左查找和当前下标相同的index
                int left_index = leftRepeatStrMaxLong(charArray, i);
                // 向右查找和当前下标相同的index
                int right_index = rightRepeatStrMaxLong(charArray, i);
                temp_max[0] = left_index;
                temp_max[1] = right_index;
                // 指定两个index,向两边依次查找
                getMaxStr(charArray, temp_max);
                // 如果找到更长的回文就更新max
                if (temp_max[1] - temp_max[0] + 1 > temp_length) {
                    max = temp_max;
                }
                // 根据right_index设置需要考察的下一个下标
                i = right_index;
            }
        }
    
        return s.substring(max[0], max[1] + 1);
    }
    
    public int rightRepeatStrMaxLong(char[] source, int i) {
        int j;
        for (j = i + 1; j < source.length; j++) {
            if (source[j] != source[i]) {
                break;
            }
        }
        return j - 1;
    }
    
    public int leftRepeatStrMaxLong(char[] source, int i) {
        int j;
        for (j = i - 1; j >= 0; j--) {
            if (source[j] != source[i]) {
                break;
            }
        }
        return j + 1;
    }
    
    public Boolean mayMax(char[] source, int i, int length) {
        // 如果长度小于2,回文肯定有机会更长
        if (length >= 2) {
            // 当前最长回文长度的一半
            int half_length = length / 2;
            // 判断是否超出长度,如果超出长度,说明不可能是最大
            // i + half_length 表示 和当前最长回文长度一样长的回文的right index
            // tips:可以思考这里为什么 right_index 不能设置为 i + half_length + 1
            int right_index = i + half_length;
            // i - half_length 表示 和当前最长回文长度一样长的回文的left index
            int left_index = i - half_length;
            if (right_index >= source.length - 1 || left_index <= 0) {
                return false;
            }
            // 如果数组不会越界,就尝试着测试最远处的两对元素是否相同
            // 第一部分情况是常见的情况
            // 第二部分的判断校验的是偶数长度回文的方式
            // ==================================
            // abadeed
            // 0123456
            // ==================================
            // 假如 i == 4,此时 half_length = 1, left_index = 3, right_index = 5, 此时第一部分的判断是不成立的
            // 第二部分的判断成立,并且结果页可能是一段回文
            return ((source[left_index] ^ source[right_index]) + (source[left_index + 1] ^ source[right_index - 1])) == 0
                    || ((source[left_index] ^ source[right_index + 1]) + (source[left_index + 1] ^ source[right_index])) == 0;
        } else {
            return true;
        }
    }
    
    public void getMaxStr(char[] source, int[] currentMaxStr) {
        // 设置新的
        int left = currentMaxStr[0] - 1;
        int right = currentMaxStr[1] + 1;
        // 当对source的访问不越界 并且 source[left] == source[right],就进行下一对的元素比较
        while (!(left < 0 || right > source.length - 1) && source[left] == source[right]) {
            left--;
            right++;
        }
        // 这里需要特殊处理一下left和right,因为当前对source进行了越界访问或者source[left] == source[right]
        currentMaxStr[0] = left + 1;
        currentMaxStr[1] = right - 1;
    }
}

优化思路

  • 从中间开始查找是不是能更快的找出比较长的回文,这样就有利于mayMax()方法的判断
  • 简化方法,合理声明变量,传值

优化后的版本:

public class Solution {

    /**
     * 向左考察的索引
     */
    private int left_index_rest = 0;
    /**
     * 向右考察的索引
     */
    private int right_index_rest = 0;
    /**
     * length - 1
     */
    private int total_index;
    /**
     * 每次考察字符时,使用的临时数组
     */
    private int[] temp_max = new int[2];
    private char[] charArray;
    /**
     * 当前出现的最大回文
     */
    private int[] max = new int[2];
    /**
     * 当前查看的下标
     */
    private int i;
    /**
     * temp_max[1] - temp_max[0] + 1
     */
    private int current_length;
    /**
     * max[1] - max[0] + 1
     */
    private int temp_length;


    private void choiceIndex() {
        if (left_index_rest >= 0) {
            i = left_index_rest--;
        } else {
            i = right_index_rest++;
        }
    }
    
    public String longestPalindrome(String s) {
        // 构建最长字符串实例,默认长度为0
        charArray = s.toCharArray();
        int length = charArray.length;
        // 处理空字符串的情况
        if (length == 0) {
            return "";
        }
        int choice_index = length / 2 - 1;
        left_index_rest = choice_index;
        right_index_rest = choice_index + 1;
        total_index = length - 1;
        i = left_index_rest;
        temp_length = max[1] - max[0] + 1;
        while (i < length && i >= 0) {
            // 这里是判断,是否查询出的maxStr已经是最大
            if ((i + 1) * 2 - 1 < temp_length) {
                left_index_rest = -1;
                choiceIndex();
                continue;
            }
            if (((length - i) * 2 - 1) < temp_length) {
                break;
            }
            // 这里先判断,是否有可能比已经存在的最长的回文更长
            if (mayMax(i, temp_length)) {
                // 0 存储开始的index, 1 存储结束的index
                // 向左查找和当前下标相同的index
                leftRepeatStrMaxLong(i);
                // 向右查找和当前下标相同的index
                rightRepeatStrMaxLong(i);
                // 指定两个index,向两边依次查找
                // 根据right_index设置需要考察的下一个下标
                if (temp_max[0] < left_index_rest) {
                    left_index_rest = temp_max[0] - 1;
                }
    
                if (temp_max[1] > right_index_rest) {
                    right_index_rest = temp_max[1] + 1;
                }
    
                getMaxStr();
                current_length = temp_max[1] - temp_max[0];
                // 如果找到更长的回文就更新max
                if (temp_max[1] - temp_max[0] >= temp_length) {
                    max[0] = temp_max[0];
                    max[1] = temp_max[1];
                    temp_length = max[1] - max[0] + 1;
                }
            }
            choiceIndex();
        }
    
        return s.substring(max[0], max[1] + 1);
    }
    
    public void rightRepeatStrMaxLong(int i) {
        int c = charArray[i];
        int j = i + 1;
        while (j < charArray.length && c == charArray[j]) {
            j ++;
        }
        temp_max[1] = j - 1;
    }
    
    public void leftRepeatStrMaxLong(int i) {
        int c = charArray[i];
        int j = i - 1;
        while (j >= 0 && c == charArray[j]) {
            j--;
        }
        temp_max[0] = j + 1;
    }
    
    public Boolean mayMax(int i, int length) {
        // 如果长度小于2,回文肯定有机会更长
        if (length >= 2) {
            // 当前最长回文长度的一半
            int half_length = length / 2;
            // 判断是否超出长度,如果超出长度,说明不可能是最大
            // i + half_length 表示 和当前最长回文长度一样长的回文的right index
            // tips:可以思考这里为什么 right_index 不能设置为 i + half_length + 1
            int right_index = i + half_length;
            // i - half_length 表示 和当前最长回文长度一样长的回文的left index
            int left_index = i - half_length;
            if (right_index >= charArray.length - 1 || left_index <= 0) {
                return false;
            }
            // 如果数组不会越界,就尝试着测试最远处的两对元素是否相同
            // 第一部分情况是常见的情况
            // 第二部分的判断校验的是偶数长度回文的方式
            // ==================================
            // abadeed
            // 0123456
            // ==================================
            // 假如 i == 4,此时 half_length = 1, left_index = 3, right_index = 5, 此时第一部分的判断是不成立的
            // 第二部分的判断成立,并且结果页可能是一段回文
            return ((charArray[left_index] == charArray[right_index]) && (charArray[left_index + 1]== charArray[right_index - 1]))
                    || ((charArray[left_index] == charArray[right_index + 1]) && (charArray[left_index + 1] == charArray[right_index]));
        } else {
            return true;
        }
    }
    
    public void getMaxStr() {
        // 设置新的
        int left = temp_max[0] - 1;
        int right = temp_max[1] + 1;
        // 当对source的访问不越界 并且 source[left] == source[right],就进行下一对的元素比较
        while (!(left < 0 || right > charArray.length - 1) && charArray[left] == charArray[right]) {
            left--;
            right++;
        }
        // 这里需要特殊处理一下left和right,因为当前对source进行了越界访问或者source[left] == source[right]
        temp_max[0] = left + 1;
        temp_max[1] = right - 1;
    }
}

运行 4ms - 5ms,打败99%

如何考察更少的字符?

应该还有可能进一步优化,考察的数量

我的个人博客有空来坐坐
https://www.wangyanan.online

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值