畅游面试中的动态规划套路-回文子序列系列

4.回文子序列

最长回文子序列

在这里插入图片描述

题目链接 :516. Longest Palindromic Subsequence

题解链接:畅游面试中的动态规划套路-回文子序列系列之最长回文子序列

方法1:暴力递归
  • helper(String s, int start, int end) 函数表示,从start索引到end索引,所能找到的当前s的最长回文子序列的长度
  • base case:
    • start == end 当前单词只有一个字符,长度为1
    • start > end 不合法
public int longestPalindromeSubseq(String s) {
    return helper(s, 0, s.length() - 1);
}


private int helper(String s, int start, int end) {
    if (start == end) return 1;
    if (start > end) return 0;
    int ans = 0;
    if (s.charAt(start) == s.charAt(end)) {
        ans = helper(s, start + 1, end - 1) + 2;
    } else {
        ans = Math.max(helper(s, start + 1, end),
                       helper(s, start, end - 1));
    }
    return ans;
}
方法2:自顶向下记忆化递归(Top-down)

对方法1进行记忆化修改后可以得到方法2

  • 准备一个 h e l p e r ( S t r i n g s , i n t i , i n t j ) helper(String s, int i, int j) helper(Strings,inti,intj)函数,其中s是字符串本身,ij是起始位置,memo记录的是字符的最长子序列长度, m e m o [ 0 ] [ n − 1 ] memo[0][n-1] memo[0][n1]为所求
  • 记忆化:当memo[i][j]不为null的时候,说明不是初始化的值,被求解过,直接返回
  • 出口条件:
    • i>j时,返回0,因为没有意义,我们要求的起始位置i要小于j
    • i==j时,返回1,只有一个字符,可以形成回文,即是其本身,长度为1
  • 递归逻辑:
    • s[i]==s[j]时,说明需要在 h e l p e r ( i + 1 , j − 1 ) helper(i+1,j-1) helper(i+1,j1)基础上+2
    • s[i]!=s[j]时,要取 m a x [ h e l p e r ( i + 1 , j ) , h e l p e r ( i , j − 1 ) ] max[helper(i+1,j),helper(i,j-1)] max[helper(i+1,j),helper(i,j1)]
Integer[][] memo;

public int longestPalindromeSubseq(String s) {
    memo = new Integer[s.length()][s.length()];
    return helper(s, 0, s.length() - 1);
}


private int helper(String s, int start, int end) {
    if(memo[start][end]!=null) return memo[start][end];
    if (start == end) return 1;
    if (start > end) return 0;
    int ans = 0;
    if (s.charAt(start) == s.charAt(end)) {
        ans = helper(s, start + 1, end - 1) + 2;
    } else {
        ans = Math.max(helper(s, start + 1, end),
                       helper(s, start, end - 1));
    }
    return  memo[start][end] = ans ;
}
方法3:自底向上填表DP(Bottom-up)
  • 定义dp[i][j]:表示s[i...j]之间的最长子序列的长度,注意是子序列,不是子串,子序列是可以跳跃的,子串不可以
    • s[i]==s[j]时,说明ij位置的字符可以形成一个回文,这个回文的长度为2,根据dp的思想,其结果应该是依赖前面的结果,也就是s[i+1 .... j-1]这个范围的字符回文个数,也就是dp[i+1][j-1],即 d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] + 2 dp[i][j]=dp[i+1][j-1]+2 dp[i][j]=dp[i+1][j1]+2
    • s[i]!=s[j]时,说明ij位置的字符不能形成一个回文,这个时候要看 s [ i + 1... j ] s[i+1...j] s[i+1...j] s [ i . . . j − 1 ] s[i...j-1] s[i...j1]这两段,因为 s [ i + 1 ] s[i+1] s[i+1]可能与 s [ i + 2... j ] s[i+2...j] s[i+2...j]范围内的某个字符相同,拼凑出回文,因为 s [ i ] ! = s [ j ] s[i]!=s[j] s[i]!=s[j],同理可得 s [ i . . . j − 1 ] s[i...j-1] s[i...j1]这段,故此, d p [ i ] [ j ] = m a x [ d p [ i ] [ j − 1 ] , d p [ i + 1 ] [ j ] ] dp[i][j]=max[dp[i][j-1],dp[i+1][j]] dp[i][j]=max[dp[i][j1],dp[i+1][j]]
  • base case:
    • 很容想到的是i==j时,说明s[i...j]只有一个字符,此时其自身可以形成一个回文,长度为1
    • i>j时,此时是不存在的,因为我们规定了s[i...j]起始位置i要小于结束位置j的,此时初始化为0
  • 有两种遍历方式
    • 斜着遍历
    • 倒着遍历
  • 返回结果 d p [ 0 ] [ n − 1 ] dp[0][n-1] dp[0][n1]其实就是 s [ 0... n − 1 ] s[0...n-1] s[0...n1]的最长回文子序列的长度
 public int longestPalindromeSubseq1st(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];
        for (int i = n - 1; i >= 0; i--) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][n - 1];
    }

最长回文子串

题目链接 :5. Longest Palindromic Substring

题解链接:畅游面试中的动态规划套路-回文子序列系列之最长回文子串

方法1:暴力递归
  • helper(String s, int start, int end)

    • 表示sstartend位置,是否有回文子串
  • base case:

    • start == end : 相等时,说明只有一个字符了,返回T
    • start +1== end :两个字符的时候,比较两个字符是否相等

        public String longestPalindrome(String s) {
            String ans = "";
            for (int i = 0; i < s.length(); i++) {
                for (int j = i; j < s.length(); j++) {
                    if (helper(s, i, j) && j - i + 1 > ans.length()) {
                        ans = s.substring(i, j + 1);
                    }
                }
            }
            return ans;
        }


        private boolean helper(String s, int start, int end) {
            if (start == end) return true;
            if (start + 1 == end) return s.charAt(start) == s.charAt(end);
            boolean ans = false;
            if (s.charAt(start) == s.charAt(end)) {
                ans = helper(s, start + 1, end - 1);
            }
            return ans;
        }
方法2:自顶向下记忆化递归(Top-down)

脱胎与方法1,添加记忆化

  Boolean[][] memo;

public String longestPalindrome(String s) {
    memo = new Boolean[s.length()][s.length()];
    String ans = "";
    for (int i = 0; i < s.length(); i++) {
        for (int j = i; j < s.length(); j++) {
            if (helper(s, i, j) && j - i + 1 > ans.length()) {
                ans = s.substring(i, j + 1);
            }
        }
    }
    return ans;
}


private boolean helper(String s, int start, int end) {
    if (start == end) return true;
    if (start + 1 == end) return s.charAt(start) == s.charAt(end);
    if (memo[start][end] != null) return memo[start][end];
    boolean ans = false;
    if (s.charAt(start) == s.charAt(end)) {
        ans = helper(s, start + 1, end - 1);
    }
    return memo[start][end] = ans;
}
方法3:自底向上填表递归(Bottom-up)
  • 其中f[i][j]表示s中,从ij是否有回文子串

  • k为遍历的字符长度,可以为n

    • 此时i=0,j=i+k-1=0+n-1=n-1
  • 条件为当前字符[i]==[j]的时候,要么只有两个字符,要么砍头去尾,有回文子串

        public String longestPalindrome(String s) {
            if (s == null || s.length() == 0) return "";
            int n = s.length();
            boolean[][] f = new boolean[n][n];
            for (int i = 0; i < n; ++i) f[i][i] = true;
            int maxLen = 1, start = 0;
            for (int k = 2; k <= n; k++) {
                // System.out.printf("k:%d\n", k);
                for (int i = 0; i < n - k + 1; i++) {
                    int j = i + k - 1;
                    // System.out.printf("i:%d,j:%d\n", i, j);
                    if (s.charAt(i) == s.charAt(j) && (k == 2 || f[i + 1][j - 1])) {
                        f[i][j] = true;
                        if (maxLen < k) {
                            maxLen = k;
                            start = i;
                        }
                    }
                }
            }
            return s.substring(start, start + maxLen);
        }

另外一种写法

  • k为遍历的字符长度,可以为n 即当i=0的时候
        public String longestPalindrome(String s) {
            if (s == null || s.length() <= 0) return s;
            int n = s.length();
            boolean[][] f = new boolean[n][n];
            for (int i = 0; i < n; i++) f[i][i] = true;
            int maxLen = 1, start = 0;
            for (int i = n - 1; i >= 0; i--) {
                for (int k = 1; k < n - i; k++) {
                    int j = k + i;
                    if (s.charAt(i) == s.charAt(j)) {
                        f[i][j] = (k == 1) || f[i + 1][j - 1];
                    }
                    if (f[i][j] && j - i + 1 > maxLen) {
                        maxLen = j - i + 1;
                        start = i;
                    }
                }
            }
            return s.substring(start, start + maxLen);
        }
方法4:中心扩展法
public String longestPalindrome(String s) {
    if (s == null || s.length() == 0) {
        return "";
    }
    int n = s.length();
    int start = 0, end = 0;
    for (int i = 0; i < n; i++) {
        //获取到当前点i 的奇回文和偶回文的最大长度
        int len1 = expandBySeed(s, i, i);
        int len2 = expandBySeed(s, i, i + 1);
        //取最大长度,然后扩展
        int len = Math.max(len1, len2);
        if (len > (end - start)) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}


/**
         * 由中心往两边扩散,返回满足最大回文的长度
         *
         * @param s
         * @param start
         * @param end
         * @return
         */
private int expandBySeed(String s, int start, int end) {
    int n = s.length();
    while (start >= 0 && end < n && s.charAt(start) == s.charAt(end)) {
        start--;
        end++;
    }
    return end - start - 1;
}
方法5:Manacher算法

本动态规划的文章着重讲动态规划,涉及马拉车算法的内容不详细展开,下面的代码取自weiwei大佬的题解

public class Solution {

    public String longestPalindrome(String s) {
        // 特判
        int len = s.length();
        if (len < 2) {
            return s;
        }

        // 得到预处理字符串
        String str = addBoundaries(s, '#');
        // 新字符串的长度
        int sLen = 2 * len + 1;

        // 数组 p 记录了扫描过的回文子串的信息
        int[] p = new int[sLen];

        // 双指针,它们是一一对应的,须同时更新
        int maxRight = 0;
        int center = 0;

        // 当前遍历的中心最大扩散步数,其值等于原始字符串的最长回文子串的长度
        int maxLen = 1;
        // 原始字符串的最长回文子串的起始位置,与 maxLen 必须同时更新        
        int start = 0;

        for (int i = 0; i < sLen; i++) {
            if (i < maxRight) {
                int mirror = 2 * center - i;
                // 这一行代码是 Manacher 算法的关键所在,要结合图形来理解
                p[i] = Math.min(maxRight - i, p[mirror]);
            }

            // 下一次尝试扩散的左右起点,能扩散的步数直接加到 p[i] 中
            int left = i - (1 + p[i]);
            int right = i + (1 + p[i]);

            // left >= 0 && right < sLen 保证不越界
            // str.charAt(left) == str.charAt(right) 表示可以扩散 1 次
            while (left >= 0 && right < sLen && str.charAt(left) == str.charAt(right)) {
                p[i]++;
                left--;
                right++;

            }
            // 根据 maxRight 的定义,它是遍历过的 i 的 i + p[i] 的最大者
            // 如果 maxRight 的值越大,进入上面 i < maxRight 的判断的可能性就越大,这样就可以重复利用之前判断过的回文信息了
            if (i + p[i] > maxRight) {
                // maxRight 和 center 需要同时更新
                maxRight = i + p[i];
                center = i;
            }
            if (p[i] > maxLen) {
                // 记录最长回文子串的长度和相应它在原始字符串中的起点
                maxLen = p[i];
                start = (i - maxLen) / 2;
            }
        }
        return s.substring(start, start + maxLen);
    }


    /**
     * 创建预处理字符串
     *
     * @param s      原始字符串
     * @param divide 分隔字符
     * @return 使用分隔字符处理以后得到的字符串
     */
    private String addBoundaries(String s, char divide) {
        int len = s.length();
        if (len == 0) {
            return "";
        }
        if (s.indexOf(divide) != -1) {
            throw new IllegalArgumentException("参数错误,您传递的分割字符,在输入字符串中存在!");
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < len; i++) {
            stringBuilder.append(divide);
            stringBuilder.append(s.charAt(i));
        }
        stringBuilder.append(divide);
        return stringBuilder.toString();
    }
}

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-dong-tai-gui-hua-by-liweiwei1419/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

回文子串的个数

题目链接 :647. Palindromic Substrings

题解链接:畅游面试中的动态规划套路-回文子序列系列之回文子串的个数

方法1:暴力递归
public int countSubstrings(String s) {
    int ans = 0, n = s.length();
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            ans += helper(s, i, j);
        }
    }
    return ans;
}

private int helper(String s, int start, int end) {
    if (start >= end) return 1;
    return s.charAt(start) == s.charAt(end) ? helper(s, start + 1, end - 1) : 0;
}
方法2:自顶向下记忆化递归(Top-down)
Integer[][] memo;

public int countSubstrings(String s) {
    int ans = 0, n = s.length();
    memo = new Integer[n][n];
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            ans += helper(s, i, j);
        }
    }
    return ans;
}

private int helper(String s, int start, int end) {
    if (start >= end) return 1;
    if (memo[start][end] != null) return memo[start][end];
    return memo[start][end] = s.charAt(start) == s.charAt(end) ? helper(s, start + 1, end - 1) : 0;
}
方法3:自底向上填表DP(Bottom-up)
  • f[i][j]表示ij上的回文子串的个数,分三种情况讨论
    • 长度为1:自己和自己组成回文,回文子串个数为1
    • 长度为2:是否相等,相等则有一个回文子串,不相等则没有
    • 长度为3:一般情况,看是否相等,不相等,则为0,相等,则砍头去尾,看f[i + 1][j - 1]
public int countSubstrings(String s) {
    int n = s.length();
    int[][] f = new int[n][n];
    char[] chas = s.toCharArray();
    int ans = 0;
    for (int i = n - 1; i >= 0; i--) {
        for (int j = i; j < n; j++) {
            if (i == j) f[i][j] = 1;
            else if (i + 1 == j) f[i][j] = chas[i] == chas[j] ? 1 : 0;
            else f[i][j] = chas[i] == chas[j] ? f[i + 1][j - 1] : 0;
            ans += f[i][j];
        }
    }
    return ans;
}
方法4:中心扩展法
/**
 * @param s
 * @return
 */
public int countSubstrings2nd(String s) {
    int result = 0;
    for (int i = 0; i < s.length(); i++) {
        //以当前点i位置,向两边扩展,以i i+1位置向两边扩展
        result += countSegment(s, i, i);
        result += countSegment(s, i, i + 1);
    }
    return result;
}


public int countSegment(String s, int start, int end) {
    int count = 0;
    //start往左边跑,end往右边跑,注意边界
    while (start >= 0 && end < s.length() && s.charAt(start--) == s.charAt(end++)) {
        count++;
    }
    return count;
}

让字符串成为回文串的最少插入次数

题目链接:1312. Minimum Insertion Steps to Make a String Palindrome

老题解链接:动态规划解最长子序列子串等一类问题之让字符串成为回文及其Follow Up[Sika Deer]

新题解链接:畅游面试中的动态规划套路-回文子序列系列之让字符串成为回文串的最少插入次数

方法1:暴力递归
  • helper(String s, int start, int end)表示sstartend想要形成回文,最少的插入次数
  • base case:
    • start >= end时,一个字符的时候,本身就是回文,不需要添加,大于的时候,越界,无意义
  • [start]=[end]的时候,砍头去尾向前看
  • [start]!=[end]的时候,前后各添加一个,进入递归找最小的
public int minInsertions(String s) {
    return helper(s, 0, s.length() - 1);
}

private int helper(String s, int start, int end) {
    if (start >= end) return 0;
    int ans;
    if (s.charAt(start) == s.charAt(end)) {
        ans = helper(s, start + 1, end - 1);
    } else {
        ans = Math.min(helper(s, start, end - 1), helper(s, start + 1, end)) + 1;
    }
    return ans;
}
方法2:自顶向下记忆化递归(Top-down)
Integer[][] memo;
public int minInsertions(String s) {
    int n = s.length();
    memo = new Integer[n][n];
    return helper(s, 0, n - 1);
}

private int helper(String s, int start, int end) {
    if (memo[start][end] != null) return memo[start][end];
    if (start >= end) return 0;
    int ans;
    if (s.charAt(start) == s.charAt(end)) {
        ans = helper(s, start + 1, end - 1);
    } else {
        ans = Math.min(helper(s, start, end - 1), helper(s, start + 1, end)) + 1;
    }
    return memo[start][end] = ans;

}
方法3:自底向上填表DP(Bottom-up)
public int minInsertions(String s) {
    // $dp[i][j]$表示子串$str[i...j]$范围内的最少添加多少个字符后,可以形成回文子串
    char[] chas = s.toCharArray();
    int n = chas.length;
    int[][] dp = new int[n][n];
    for (int j = 1; j < n; j++) {
        dp[j - 1][j] = (chas[j - 1] == chas[j]) ? 0 : 1;
        for (int i = j - 2; i >= 0; i--) {
            if (chas[i] == chas[j]) dp[i][j] = dp[i + 1][j - 1];
            else dp[i][j] = Math.min(dp[i + 1][j], dp[i][j - 1]) + 1;
        }
    }
    return dp[0][s.length() - 1];
}

另外一种解法

使用畅游面试中的动态规划套路-回文子序列系列之最长回文子序列递减即可

public int minDelBuildPalindrome(String s) {
    int n = s.length();
    int[][] f = new int[n][n];
    for (int i = n - 1; i >= 0; i--) {//i的顺序从高到低
        f[i][i] = 1;
        for (int j = i + 1; j < n; j++) {
            if (s.charAt(i) == s.charAt(j)) f[i][j] = f[i + 1][j - 1] + 2;
            else f[i][j] = Math.max(f[i + 1][j], f[i][j - 1]);
        }
    }

    return n - f[0][n - 1];
}

怎么删掉最少字符构成回文

题目链接:Minimum Deletions in a String to make it a Palindrome

题解链接:畅游面试中的动态规划套路-回文子序列系列之怎么删掉最少字符构成回文

这题和畅游面试中的动态规划套路-回文子序列系列之最长回文子序列一样,相当于求最长回文子序列,相减可得 s.length()-maxLPSLen

方法1:暴力递归
public int minDelBuildPalindrome(String s) {
    return s.length() - helper(s, 0, s.length() - 1);
}

private int helper(String s, int start, int end) {
    if (start == end) return 1;
    if (start > end) return 0;
    int ans;
    if (s.charAt(start) == s.charAt(end)) {
        ans = helper(s, start + 1, end - 1) + 2;
    } else {
        ans = Math.max(helper(s, start, end - 1), helper(s, start + 1, end));
    }
  //  System.out.println(ans);
    return ans;
}
方法2:自顶向下记忆化递归(Top-down)
Integer[][] memo ;

public int minDelBuildPalindrome(String s) {
    memo = new Integer[s.length()][s.length()];
    return s.length() - helper(s, 0, s.length() - 1);
}

private int helper(String s, int start, int end) {
    if(memo[start][end]!=null) return memo[start][end];
    if (start == end) return 1;
    if (start > end) return 0;
    int ans;
    if (s.charAt(start) == s.charAt(end)) {
        ans = helper(s, start + 1, end - 1) + 2;
    } else {
        ans = Math.max(helper(s, start, end - 1), helper(s, start + 1, end));
    }
   // System.out.println(ans);
    return memo[start][end]=ans;
}
方法3:自底向上填表DP(Bottom-up)
public int minDelBuildPalindrome(String s) {
    int n = s.length();
    int[][] f = new int[n][n];
    for (int i = n - 1; i >= 0; i--) {//i的顺序从高到低
        f[i][i] = 1;
        for (int j = i + 1; j < n; j++) {
            if (s.charAt(i) == s.charAt(j)) f[i][j] = f[i + 1][j - 1] + 2;
            else f[i][j] = Math.max(f[i + 1][j], f[i][j - 1]);
        }
    }

    return n - f[0][n - 1];
}

分割回文串

题目链接 :131. Palindrome Partitioning

题解链接:畅游面试中的动态规划套路-回文子序列系列之分割回文串

方法1:暴力递归
  • dfs(String s) 返回当前字符s所能分割的回文串的组合
  • 先判断左部分是不是回文,不是回文跳过,是回文,再递归去拿右部分的回文串组合
  • 出口条件是当前的字符串为空的时候,也就是分割结束
public List<List<String>> partition(String s) {
    return  dfs(s);
}

private List<List<String>> dfs(String s) {
    List<List<String>> res = new ArrayList<>();
    if (s == null || s.length() == 0) res.add(new ArrayList<>());
    for (int i = 0; i < s.length(); i++) {
        if (isPalindrome(s, 0, i)) {
            String left = s.substring(0, i + 1);
            for (List<String> rightList : dfs(s.substring(i + 1))) {
                List<String> sub = new ArrayList<>();
                sub.add(left);
                sub.addAll(rightList);
                res.add(sub);
            }
        }
    }
    return res;
}


private boolean isPalindrome(String s, int l, int r) {
    while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
    return true;
}
  • 另外一种写法
List<List<String>> res = new ArrayList<>();

public List<List<String>> partition(String s) {
    dfs(s, 0, s.length(), new ArrayList<>());
    return res;
}


private void dfs(String s, int curr, int total, ArrayList<String> sub) {
    if (curr == total) {
        res.add(new ArrayList<>(sub));
        return;
    }
    for (int i = curr; i < total; i++) {
        if (isPalindrome(s, curr, i)) {
            sub.add(s.substring(curr, i + 1));
            dfs(s, i + 1, total, sub);
            sub.remove(sub.size() - 1);
        }
    }
}

private boolean isPalindrome(String s, int l, int r) {
    while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
    return true;
}
方法2:自顶向下记忆化递归(Top-down)

脱胎于方法1

Map<String, List<List<String>>> memo = new HashMap<>();

public List<List<String>> partition(String s) {
    List<List<String>> dfs = dfs(s);
    return dfs;
}

private List<List<String>> dfs(String s) {
    if (memo.containsKey(s)) return memo.get(s);
    List<List<String>> res = new ArrayList<>();
    if (s == null || s.length() == 0) res.add(new ArrayList<>());
    for (int i = 0; i < s.length(); i++) {
        if (isPalindrome(s, 0, i)) {
            String left = s.substring(0, i + 1);
            //右部分从i+1开始
            for (List<String> rightList : dfs(s.substring(i + 1))) {
                List<String> sub = new ArrayList<>();
                sub.add(left);
                sub.addAll(rightList);
                res.add(sub);
            }
        }
    }
    memo.put(s, res);
    return res;
}


private boolean isPalindrome(String s, int l, int r) {
    while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
    return true;
}
方法3:自底向上填表DP(Bottom-up)
  • dp[i][j]表示[i...j]范围内子串能否形成回文,先初始化该dp
List<List<String>> res = new ArrayList<>();

public List<List<String>> partition(String s) {
    int n = s.length();
    boolean[][] dp = new boolean[n][n];
    for (int j = 0; j < n; j++) {
        for (int i = 0; i <= j; i++) {
            if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1])) dp[i][j] = true;
        }
    }
    dfs(s, 0, n, new ArrayList<>(), dp);
    return res;
}

private void dfs(String s, int i, int n, List<String> sub, boolean[][] dp) {
    if (i == n) {
        res.add(new ArrayList<>(sub));
        return;
    }
    for (int j = i; j < n; j++) {
        if (dp[i][j]) {
            sub.add(s.substring(i, j + 1));
            dfs(s, j + 1, n, sub, dp);
            sub.remove(sub.size() - 1);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值