1. 程式人生 > >leetcode | 132. Palindrome Partitioning II

leetcode | 132. Palindrome Partitioning II

題目

Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

Example:

Input: "aab"
Output: 1
Explanation: The palindrome partitioning ["aa","b"] could be produced using 1 cut.

思路與解法

此題目要求我們計算將字串s

切分為迴文子串的最小切割次數。
對於題目中的樣例字串"aab",我們想要得到該字串的最小切分次數,首先需要判斷字串是否為迴文序列,如果該字串為迴文序列,那麼萬事大吉。然而,大多數情況下輸入字串是不可能為迴文序列的,所以我們需要進行切分操作,即我們需要找到該字串的連續迴文子串的最少個數,但是最開始我們需要得到一個字串是否滿足迴文。
我們可以採用動態規劃的思想來解決該問題,首先定義以下dp陣列:
dp[i][j]表示從s[i:j+1](左閉右開)是否為迴文序列,如果是迴文序列,則dp[i][j]=1;否則dp[i][j]=0
同時,可以推出狀態轉移方程為:

// 情況1:i+1 == j (長度等於2的子串)
if s[i] == s[j] { dp[i][j] = 1 } // 情況1:i+1 < j (長度大於2的子串) if dp[i+1][j-1] == 1 && s[i] == s[j] { dp[i][j] = 1 }

此時,我們得到了字串s的所有子串是否滿足迴文的dp陣列:時間複雜度為 O ( N 2

) O(N^2) 。相比較於迴圈遍歷字串並首尾判斷迴文的方法要快上許多,後者複雜度為 O ( N 3 ) O(N^3)
之後,我們需要計算將該字串切分所需要的最少切分數,定義如下陣列,minCuts[i]表示字串s[:i](前i個字元,minCuts下標從1~len(s),與dp陣列下標範圍不同)切分為迴文子串的最小切分數,則狀態轉移方程為:

// 下屬if條件句中,j<i
// dp[j-1][i-1]為1,表示s[j-1:i-1]為迴文序列
if dp[j-1][i-1] == 1 && minCuts[i] > minCuts[j-1] + 1 {
	minCuts[i] = minCuts[j-1] + 1
}

程式碼實現(Go)

const INT_MAX = int(^uint(0) >> 1) 
func minCut(s string) int {
    lenS := len(s)
    dp := make([][]int, lenS)
    minCuts := make([]int, lenS+10)
    // 初始化dp陣列,dp[i][i]=1
    // 初始化minCuts[2~lenS] = INT_MAX
    for i:=0; i<lenS; i++ {
        dp[i] = make([]int, lenS)
        dp[i][i] = 1
        minCuts[i+1] = INT_MAX
    }
    // 長度為0的字串最小切分數設定為-1;長度為1的字串最小切分數設定為0
    minCuts[0] = -1
    minCuts[1] = 0
    // 注意迴圈的順序,j在外層
    // 簡單講是因為dp[0][lenS]應該在已知dp[1][lenS-1]後在計算獲得
    for j:=0; j<lenS; j++ {
        for i:=0; i<lenS; i++ {
            if j > i {
                if dp[i+1][j-1]==1 && s[i] == s[j] {
                    dp[i][j] = 1
                } else if i+1==j && s[i] == s[j] {
                    dp[i][j] = 1
                }
            }
        }
    }
	// 獲得minCuts,為方便處理邊界,我將minCuts的下標定義為1~lenS
    for i:=1; i<=lenS; i++ {
        for j:=1; j<=i; j++ {
            if dp[j-1][i-1] == 1 && minCuts[i] > minCuts[j-1] + 1 {
                minCuts[i] = minCuts[j-1] + 1
            }
        }
    }

    return minCuts[lenS]
}

執行結果

總體時間複雜度為 O ( N 2 ) O(N^2) :
在這裡插入圖片描述

遞迴解法

最初我採用遞迴來實現(dp陣列計算方法同上),遞迴更加容易理解,我們在計算s的最小切分數時,求得s[:k]s[k:](其中1<=k<lenS)最小切分數之和得最小值,即為s得最小切分數。

for k:=i+1; k<=j; k++ {
    cuts1 := getMinCuts(i, k-1, s)
    cuts2 := getMinCuts(k, j, s)
    if cuts1 + cuts2 + 1 < minCuts {
        minCuts = cuts1 + cuts2 + 1
    }
}

但是,上述方法中做了許多無用的計算(並不能通過後三組資料),不滿足dp[i][k-1]==1得計算時多餘的,所以我進行了改進:

const INT_MAX = int(^uint(0) >> 1) 
var cutsMap map[string]int
var dp [][]int

func getMinCuts(i, j int, s string) int{
    if cutsMap[s[i:j+1]]!=0 {
        return cutsMap[s[i:j+1]]
    }
    if dp[i][j] == 1 {
        return 0
    }
    minCuts := INT_MAX
    for k:=i+1; k<=j; k++ {
        if dp[i][k-1] == 1 {
            cuts := getMinCuts(k, j, s)
            if cuts + 1 < minCuts {
                minCuts = cuts + 1
            }
        }
    }
    cutsMap[s[i:j+1]] = minCuts
    return minCuts
}

func minCut(s string) int {
    cutsMap = make(map[string]int)
    lenS := len(s)
    dp = make([][]int, lenS)
    for i:=0; i<lenS; i++ {
        dp[i] = make([]int, lenS)
        dp[i][i] = 1
    }
    for j:=0; j<lenS; j++ {
        for i:=0; i<lenS; i++ {
            if j > i {
                if dp[i+1][j-1]==1 && s[i] == s[j] {
                    dp[i][j] = 1
                } else if i+1==j && s[i] == s[j] {
                    dp[i][j] = 1
                }
            }
        }
    }
    return getMinCuts(0, lenS-1, s)
}

在這裡插入圖片描述