列表

详情


1278. 分割回文串 III

给你一个由小写字母组成的字符串 s,和一个整数 k

请你按下面的要求分割字符串:

请返回以这种方式分割字符串所需修改的最少字符数。

 

示例 1:

输入:s = "abc", k = 2
输出:1
解释:你可以把字符串分割成 "ab" 和 "c",并修改 "ab" 中的 1 个字符,将它变成回文串。

示例 2:

输入:s = "aabbc", k = 3
输出:0
解释:你可以把字符串分割成 "aa"、"bb" 和 "c",它们都是回文串。

示例 3:

输入:s = "leetcode", k = 8
输出:0

 

提示:

原站题解

去查看

上次编辑到这里,代码来自缓存 点击恢复默认模板
class Solution { public: int palindromePartition(string s, int k) { } };

golang 解法, 执行用时: 0 ms, 内存消耗: 2.4 MB, 提交时间: 2023-06-15 14:43:00

/*
check(l, r) 函数判断将s[l:r+1]变成回文串所需的操作数,memo记忆化
开dp并初始化 dp[i][j]表示将s 的前j个字符分割成i段回文串所需的最少操作数
状态转移方程为dp[i][j] = min(dp[i][j], check(k - 1, j - 1) + dp[i - 1][k - 1])
返回答案 dp[k][n]
*/
func palindromePartition(s string, k int) int {
    n := len(s)
    // check函数判断将s[l:r+1]变成回文串所需的操作数,memo记忆化
    memo := make([][]int, n)
    for i := range memo {
        memo[i] = make([]int, n)
        for j := range memo[i] {
            memo[i][j] = -1
        }
    }
    check := func(l, r int) (ans int) {
        if memo[l][r] != -1 {
            return memo[l][r]
        }
        i, j := l, r
        for i < j {
            if s[i] != s[j] {
                ans++
            }
            i, j = i + 1, j - 1
        }
        memo[l][r] = ans
        return
    }
    // 开dp并初始化
    dp := make([][]int, k + 1)
    for i := range dp {
        dp[i] = make([]int, n + 1)
        for j := range dp[i] {
            dp[i][j] = n
        }
    }
    for i := 1; i <= n; i++ {
        dp[1][i] = check(0, i - 1)
    }
    for i := 1; i <= k; i++ {
        dp[i][i] = 0
    }
    // 状态转移求解
    for i := 2; i <= k; i++ {
        for j := i + 1; j <= n; j++ {
            for k := i; k <= j; k++ {
                dp[i][j] = min(dp[i][j], check(k - 1, j - 1) + dp[i - 1][k - 1])
            }
        }
    }
    // 返回答案
    return dp[k][n]
}
func min(x, y int) int {if x < y {return x}; return y}

golang 解法, 执行用时: 4 ms, 内存消耗: 2.2 MB, 提交时间: 2023-06-15 14:39:37

func palindromePartition(s string, k int) int {
    //状态转移方程
    //f[i][j] 前[0-i]字符,分割成j个回文字符串,有多少种分法
    //f[i][j] = f[x0][j-1] + cost(s, x0, i-1)

    f := make([][]int, len(s) + 1)
    for i := 0; i < len(f); i++ {
        f[i] = make([]int, len(s) +1)
        for j := 0; j < len(f[i]); j++ {
            f[i][j] = math.MaxInt32
        }
    }
     f[0][0] = 0    //[0]字符分割0份,需要0次修改
     f[1][1] = 0    //[0-1]字符分割1次, 需要0次修改
     //[left,right]字符修改位回文需要多少次修改
    cost := func(left int, right int) int {
        ret := 0
        for left < right {
            if s[left] != s[right] {
                ret++
            }
            left++
            right--
        }
        return ret
    }
    //遍历s, 前[0-i]字符
    for i := 0; i < len(s); i++ {
        //遍历, 分割成j个字符串
        for j := 1; j <= min(i+1, k); j++ {
            //如果分割成1个字符串
            //f[i][j] = f[i][1] : [0-i]字符分割成1个字符串需要多少次修改
            //计算字符[0-i]需要多少次修改
            if j == 1 {
                f[i][j] = cost(0, i)
            } else {
                //如果分割成多个字符串
                //第j个字符串从哪个字符开始遍历?
                //第j个字符串前面有j-1个字符串,所以至少有j-1个字符
                //j-1个字符:[0-j-2]
                //m从j-2开始遍历,到i结束,且不包括i, 因为i是最后一个字符索引,必须在第j个字符串中
                for m := j-2; m < i; m++ {
                    //f[i][j] = f[m][j-1] + cost(m+1,i)
                    //[0-i]字符分个成j子字符串需要修改多少次 = 
                    //[0-m]字符分割成j-1个字符串需要修改次数 + [m+1,i]字符串需要修改次数
                    f[i][j] = min(f[i][j], f[m][j-1] + cost(m+1,i))
                }
            }
        }
        
    }
    return f[len(s) - 1][k]
}

func min(a, b int) int { if a < b { return a }; return b }

golang 解法, 执行用时: 0 ms, 内存消耗: 2.1 MB, 提交时间: 2023-06-15 14:38:36

var cache [][]int // cache记录 前i个字符组成的字符串 切割出j个 字符串所花费的最小代价
func palindromePartition(s string, k int) int {
	lens := len(s)
	if lens <= k {
		return 0
	}
	cache = make([][]int, lens)
	for i := 0; i < lens; i++ {
		cache[i] = make([]int, k)
		for j := 0; j < k; j++ {
			cache[i][j] = math.MinInt32
		}
	}
	cs := []byte(s)
	return 	dfs(cs, 0, k-1)

}

func dfs(cs []byte, start int, k int) int {

	// len - start代表当前要切割的字符串的长度
	// 如果这个长度小于等于要切割的次数意味着没办法继续切割了
	if len(cs)-start <= k {
		return -1
	}

	if cache[start][k] != math.MinInt32 {
		return cache[start][k]
	}

	// k <= 0 时 还有剩余的字符串  直接计算最小代价
	if k <= 0 {
		cache[start][k] = cost(cs, start, len(cs))
		return cache[start][k]
	}
	minCost := math.MaxInt32
	for i := 1; i <= len(cs); i++ {
		// 计算 start 到 start + i 的最小代价
		cost := cost(cs, start, start+i)
		// 接着计算后面字符串的最小代价
		sub := dfs(cs, start+i, k-1)
		if sub == -1 {
			break
		}
		minCost = min(minCost, cost+sub)
	}
	// 将最小代价记录下来
	cache[start][k] = minCost
	return cache[start][k]
}

func cost(cs []byte, start int, end int) int {
	res := 0
	left := start
	right := end - 1
	for left < right {
		if cs[left] != cs[right] {
			res++
		}
		left++
		right--
	}
	return res

}

func min(a int, b int) int {
	if a < b {
		return a
	}
	return b
}

python3 解法, 执行用时: 88 ms, 内存消耗: 16.3 MB, 提交时间: 2023-06-15 14:37:50

class Solution:
    def palindromePartition(self, s: str, k: int) -> int:
        n = len(s)
        # 计算所有的子串变为回文串需要多少操作次数
        dp = [[0]*n for _ in range(n)]
        for i in range(n-2, -1, -1):
            dp[i][i+1] = int(s[i] != s[i+1])
            for j in range(i+2, n):
                dp[i][j] = dp[i+1][j-1]+int(s[i] != s[j])

        # 计算将第i个字符分到第j个子字符串需要修改的个数
        ans = [[0]*k for _ in range(n)]
        for i in range(1, n):
            ans[i][0] = dp[0][i]
            for j in range(1, min(k, i+1)):
                ans[i][j] = min([ans[p][j-1] + dp[p+1][i] for p in range(j-1, i)])
        return ans[n-1][k-1]

cpp 解法, 执行用时: 8 ms, 内存消耗: 6.6 MB, 提交时间: 2023-06-15 14:37:17

class Solution {
public:
    int palindromePartition(string& s, int k) {
        int n = s.size();
        vector<vector<int>> cost(n, vector<int>(n));
        for (int span = 2; span <= n; ++span) {
            for (int i = 0; i <= n - span; ++i) {
                int j = i + span - 1;
                cost[i][j] = cost[i + 1][j - 1] + (s[i] == s[j] ? 0 : 1);
            }
        }

        vector<vector<int>> f(n + 1, vector<int>(k + 1, INT_MAX));
        f[0][0] = 0;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= min(k, i); ++j) {
                if (j == 1) {
                    f[i][j] = cost[0][i - 1];
                }
                else {
                    for (int i0 = j - 1; i0 < i; ++i0) {
                        f[i][j] = min(f[i][j], f[i0][j - 1] + cost[i0][i - 1]);
                    }
                }
            }
        }
        
        return f[n][k];
    }
};

python3 解法, 执行用时: 128 ms, 内存消耗: 16.3 MB, 提交时间: 2023-06-15 14:37:00

class Solution:
    def palindromePartition(self, s: str, k: int) -> int:
        n = len(s)
        cost = [[0] * n for _ in range(n)] # 优化cost(l, r)
        for span in range(2, n + 1):
            for i in range(n - span + 1):
                j = i + span - 1
                cost[i][j] = cost[i + 1][j - 1] + (0 if s[i] == s[j] else 1)

        f = [[10**9] * (k + 1) for _ in range(n + 1)]
        f[0][0] = 0
        for i in range(1, n + 1):
            for j in range(1, min(k, i) + 1):
                if j == 1:
                    f[i][j] = cost[0][i - 1]
                else:
                    for i0 in range(j - 1, i):
                        f[i][j] = min(f[i][j], f[i0][j - 1] + cost[i0][i - 1])
        
        return f[n][k]

cpp 解法, 执行用时: 24 ms, 内存消耗: 6.3 MB, 提交时间: 2023-06-15 14:35:42

class Solution {
public:
    int cost(string& s, int l, int r) {
        int ret = 0;
        for (int i = l, j = r; i < j; ++i, --j) {
            if (s[i] != s[j]) {
                ++ret;
            }
        }
        return ret;
    }

    int palindromePartition(string& s, int k) {
        int n = s.size();
        vector<vector<int>> f(n + 1, vector<int>(k + 1, INT_MAX));
        f[0][0] = 0;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= min(k, i); ++j) {
                if (j == 1) {
                    f[i][j] = cost(s, 0, i - 1);
                }
                else {
                    for (int i0 = j - 1; i0 < i; ++i0) {
                        f[i][j] = min(f[i][j], f[i0][j - 1] + cost(s, i0, i - 1));
                    }
                }
            }
        }
        
        return f[n][k];
    }
};

python3 解法, 执行用时: 1328 ms, 内存消耗: 16.2 MB, 提交时间: 2023-06-15 14:35:27

'''
动态规划
f[i][j] 表示对于字符串 s 的前 i 个字符,将它分割成 j 个非空且不相交的回文串,最少需要修改的字符数。
在进行状态转移时,我们可以枚举第 j 个回文串的起始位置 i0,那么就有如下的状态转移方程:
f[i][j] = min(f[i0][j - 1] + cost(s, i0 + 1, i))
其中 cost(s, l, r) 表示将 s 的第 l 个到第 r 个字符组成的子串变成回文串,最少需要修改的字符数。
'''
class Solution:
    def palindromePartition(self, s: str, k: int) -> int:
        def cost(l, r):
            ret, i, j = 0, l, r
            while i < j:
                ret += (0 if s[i] == s[j] else 1)
                i += 1
                j -= 1
            return ret
        
        n = len(s)
        f = [[10**9] * (k + 1) for _ in range(n + 1)]
        f[0][0] = 0
        for i in range(1, n + 1):
            for j in range(1, min(k, i) + 1):
                if j == 1:
                    f[i][j] = cost(0, i - 1)
                else:
                    for i0 in range(j - 1, i):
                        f[i][j] = min(f[i][j], f[i0][j - 1] + cost(i0, i - 1))
        
        return f[n][k]

上一题