LeetCode[5]-最長の回文サブストリング&&動的計画法&&再帰&&ブルートフォース列挙を検索

 タイトル-  [LeetCode-5から](中程度)

文字列sが与えられた場合、sの最大長が1000を超えないと仮定して、sで最長の回文部分文字列を見つけます。

ヒント:回文文字列:順方向と逆方向の両方の読み取りは同じです

示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 同样是符合题意的答案。

示例 2:
输入: "cbbd"
输出: "bb"

文章

        この古典的な質問は、LeetCodeで中程度の難易度としてマークされています。質問の意味を理解できる限り、作成するのは難しくありませんが、最適なアルゴリズムを作成するのは簡単ではありません。私自身の問題解決のアイデアによると、3種類をまとめました:

方法1:ブルートフォースの列挙

        私の先生はかつて、「2つのforループで解決できないアルゴリズムは世界にありません」と言いました。バブルソートの概念に基づいて、私が考えた最初で最も暴力的な方法は、すべての部分文字列を列挙し、最後に決定することでした。回文の最長の文字列。

    public String longestPalindrome(String s){
        if (s.isEmpty()) return null;
        if (s.length() == 1) return s;

        String ret =  s.substring(0,1);
        for (int i = 0; i < s.length(); i++) {
            for (int j = i+1; j < s.length(); j++) {
                String temp = s.substring(i,j+1);
                String rev = new StringBuilder(temp).reverse().toString();
                // 如果是回文串,且长度大于ret,替换
                if (temp.equals(rev) && temp.length() > ret.length()) {
                    ret = temp;
                }
            }
        }
        return ret;
    }

実行しました...LeetCodeプログラムに「制限時間を超えました」と表示され、暴力的すぎる解決策は許可されていません。

IDEAで実行すると、メソッドは問題ありません。

 方法2:動的計画法

        分割統治法、欲張り法、バックトラッキング法、動的計画法の4つの一般的に使用されるアルゴリズム。その中で、動的計画法を最初に習得する必要があります。このアルゴリズムの問​​題を通して、一緒に学びましょう。

        動的計画法プロセス:各決定は現在の状態に依存し、それが次に状態遷移を引き起こします。意思決定シーケンスは変化する状態で生成されるため、この多段階の最適化意思決定プロセスは動的計画法と呼ばれます。

        基本的な考え方は分割統治法に似ていますが、解決する問題をいくつかのサブ問題(ステージ)に分解し、サブステージを順番に解決します。前者のサブ問題の解決は有用です。後者のサブ問題の解決のための情報。

        この質問では、文字列strに対して、新しい2桁の配列dp [] [] arrをメモとして作成し、サブ問題の結果を記録します。ここで、dp [i] [j]は、i番目の問題かどうかを示します。 jへの文字列は回文です。trueははいを意味し、falseはいいえを意味します。dp [i、j] = trueとすると、dp [i + 1、j-1]=trueである必要があります。このようにして、最長の回文サブストリングを一連のサブ問題に分解できます。これは、動的計画法によって解決できます。これは動的計画法の本質でもありますが、このアプローチのアルゴリズムの複雑さもO(n ^ 2)です。

        まず、状態遷移方程式を作成します。

 上記の状態遷移方程式は、str [i] = str [j]の場合、次のようになります。

  • str [i + 1 ... j-1]が回文である場合、str [i...j]も回文です。
  • str [i + 1 ... j-1]が回文ではない場合、str [i...j]も回文ではありません。 
    public String longestPalindrome(String s) {
        if (s.isEmpty()) return s;
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        int left = 0, right = 0;
        // 倒序向前遍历
        for (int i = len - 2; i >= 0; i--) {
            for (int j = i + 1; j < len; j++) {
                // 小于3一定是回文,aa或aba
                dp[i][j] = s.charAt(i) == s.charAt(j) && ( j-i < 3 || dp[i+1][j-1]);
                // 更新左右边界
                if(dp[i][j] && right-left < j-i){
                    left = i;
                    right = j;
                }
            }
        }
        return s.substring(left,right+1);
    }

それを実行しました...そして結果は悪くありません。

 注意深い学生は、上記のアルゴリズムが逆の順序で便利であり、正の順序の原理が次のように同じであることに気付くでしょう。

    public String longestPalindrome_1(String s){
        if (s.isEmpty()) return null;
        if (s.length() == 1) return s;
        int len = s.length();
        boolean[][] arr = new boolean[len][len];
        int left = 0, right = 0;
        // 正序向后遍历,注意i和j的位置
        for (int i = 1; i < len; i++) {
            for (int j = i - 1; j >= 0; j--) {
                arr[i][j] = s.charAt(i) == s.charAt(j) && ( i-j < 3 || arr[i-1][j+1]);
                if (arr[i][j] && (i - j > right - left)) {
                    right = i;
                    left = j;
                }
            }
        }
        return s.substring(left, right+1);
    }

方法3:再帰

オンラインのOOM事故を目撃したので、問題を解決するために再帰を使用することはめったにありません。簡単に思えますが、時間とメモリを消費します。ただし、次の方法では、再帰のアイデアを使用して最初の方法を最適化しました。開始点は、「不要なクエリと判断を減らして操作時間を短縮する」ことです。

基本的な考え方:外側のループは1回トラバースして、ターゲット位置iの対称位置にある要素が等しいかどうかを判断します。等しい場合、whileループは両側の境界を取り、左右の境界値を保存します。等しくない場合は、トラバースを続行して不要な検索値の操作を減らします。

class Solution {
        
    private int stt = 0;
    private int end = 0;

    public String longestPalindrome_3(String s) {
        char[] str = s.toCharArray();
        find(str, 0);
        return s.substring(stt, end);
    }

    private void find(char[] str, int i) {
        if(i >= str.length) return;
        int left = i - 1;
        int right = i + 1;
        // 处理右侧连续的相同字符
        while(right < str.length && str[i] == str[right]) right++;
        // 重新定义下次循环的起点
        i = right;
        // 查找左右边缘
        while(left >= 0 && right < str.length && str[left] == str[right]) {
            left--;
            right++;
        }
        // 左边恢复到回文字符串端点
        left++;
        // 刷新记录
        if(right - left > end - stt) {
            stt = left;
            end = right;
        }
        find(str, i);
    }
}

しばらくの間それを実行しました...hohohoho、私はネチズンの100%を打ち負かすとは思っていませんでした。この書き込み方法は最短時間であり、メモリ消費量は最適化されていません。


おすすめ

転載: blog.csdn.net/weixin_44259720/article/details/122988981