赞
踩
区间DP,是经常会用到的、解决区间问题的一种方法,经常以动态规划(dfs/记忆化搜索)的形式展现,最核心的思想就是枚举区间(枚举端点),寻找切割点,处理因切割点而产生的结果,进行区间累加或者选取个别区间,从而解决整体的问题。
- class Solution {
- public int 区间dp(int[] v) {
- int n = v.length;
- int[][] dp = new int[n][n];
- //枚举区间长度
- for(int len = 满足题目要求的最小值; len <= n; len++){
- //枚举左端点
- for(int L = 0; L+len-1 < n; L++){
- //枚举右端点
- int R = L + len - 1;
- //设置动态规划数组的初始值
- dp[L][R] = maxvalue;
- or
- dp[L][R] = minvalue;
- //枚举区间内的可能的切割点
- for(int k = L+1; k < R; k++){
- dp[L][R] = Math.min(dp[L][R], dp[L][k] ? dp[k][R] ? 因切割点而产生的特殊值);
- }
- }
- }
- //返回规定的区间范围
- return dp[0][n-1];
- or
- return dp[1][n]
- or
- others
- }
- }
枚举端点的方法和枚举区间本质上都一样,有些题目反而用枚举端点的方法更容易思考
- class Solution {
- public int 区间dp(int[] v) {
- int[][] dp = new int[v.length][v.length];
- //枚举左端点,左端点经常从大到小枚举,为什么请看下文
- for(int i = v.length-3; i >= 0; i--){
- //枚举右端点
- for(int j = i+2; j < v.length; j++){
- //dp数组初始值
- dp[i][j] = Integer.MAX_VALUE;
- or
- dp[i][j] = Integer.MIN_VALUE;
- //枚举切割点
- for(int k = i+1; k < j; k++){
- dp[i][j] = Math.min(dp[i][j],dp[i][k]?dp[k][j]?因切割点而产生的特殊值);
- }
- }
- }
- return dp[0][v.length-1];
- }
- }
给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。
「回文串」是正读和反读都相同的字符串。
- 输入:s = "mbadm"
- 输出:2
- 解释:字符串可变为 "mbdadbm" 或者 "mdbabdm" 。
-
- 输入:s = "zzazz"
- 输出:0
- 解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。
此问题和leetcode 516 最长回文子序列属于同类题目[Ref. 3]:
" 求的是将 s 变成回文串需要添加的最少字符数,所以我们只用求最长回文子序列长度即可,然后字符串 s 中除去最长回文子序列,剩下的字符就是不构成回文子序列的字符,添加与其同等数量的字符,将 s 构成回文串。"
**leetcode 516 最长回文子序列
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
- 输入:s = "bbbab"
- 输出:4
- 解释:一个可能的最长回文子序列为 "bbbb" 。
-
- 输入:s = "cbbd"
- 输出:2
- 解释:一个可能的最长回文子序列为 "bb" 。
- class Solution {
- public int longestPalindromeSubseq(String s) {
- int len = s.length();
- int[][] dp = new int[len][len];
- for(int i = len-1; i >= 0; i--){
- dp[i][i] = 1;
- for(int j = i+1; j < len; 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][len-1];
- }
- }
此题详解:
子串和子序列问题-动态规划向_子串 子序列_ForwardSummer的博客-CSDN博客
再看此题
- class Solution {
- public int minInsertions(String s) {
- int len = s.length();
- int[][] dp = new int[len+1][len+1];
- for(int i = len-1; i>= 0; i--){
- dp[i][i] = 1;
- for(int j = i+1; j < len; 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 len-dp[0][len-1];
- }
- }
本题小结:
(1)dp[i][j] 代表从i到j的区间(左闭右闭)内最长的回文子序列的长度
(2)那么,当s.charAt(i) == s.charAt(j),
(3)当s.charAt(i) != s.charAt(j),
(4) 字符串 s 中除去最长回文子序列,剩下的字符就是不构成回文子序列的字符
你有一个凸的 n 边形,其每个顶点都有一个整数值。给定一个整数数组 values ,其中 values[i] 是第 i 个顶点的值(即 顺时针顺序 )。
假设将多边形 剖分 为 n - 2 个三角形。对于每个三角形,该三角形的值是顶点标记的乘积,三角剖分的分数是进行三角剖分后所有 n - 2 个三角形的值之和。
返回 多边形进行三角剖分后可以得到的最低分 。
- 1.边长为n的多边形形产生的三角形为n-2个,n>=3
-
- 2.当边为5时,固定一个边当作底(比如4-5),选择一个顶点(比如2),
- 蓝色线表示的三角形将整个五边形分为三个部分。
-
- 3.当边长为5时,知道当前三角形面积(蓝色部分),知道左右的三角形面积,
- 即可得到所有的三角形面积,进而求其和。
-
- 4.当来到多边形,依然找到一个三角形(求出其面积),将三角形左右两边求出(S1+S2),
- 即可求所有的三角形构成的面积之和。
-
- 5.求出所有的可能性,选择其中一个最小的。
-
- class Solution {
- int[] v;
- public int minScoreTriangulation(int[] v) {
- this.v = v;
- return dfs(0,v.length-1);
- }
- public int dfs(int i, int j){
- if(i+1 == j) return 0;
- int res = Integer.MAX_VALUE;
- for(int k = i+1; k < j; k++){
- res = Math.min(res,dfs(i,k)+dfs(k,j)+v[i]*v[j]*v[k]);
- }
- return res;
- }
- }
本方法小结:
(1)dfs的意义代表从i端点到j端点构成的多边形的最小面积,相当于上图的{1~N+N~1}(顺时针)
(2)以i和j为底,k为顶点的三角形会把整个多边形分成更小的子问题
(3)左边的子问题为dfs(i,k),右边的子问题为dfs(k,j),已经解决的问题为当前这个三角形,即为v[i]*v[j]*v[k]
(4)当i+1 == j时,只有两个点,构不成三角形,返回0
DFS超时,因为算法复杂度是,数据很容易超时,接下来改成记忆化搜索。
- class Solution {
- int[][] memo;
- int[] v;
- public int minScoreTriangulation(int[] v) {
- this.v = v;
- memo = new int[v.length][v.length];
- for(int[] i : memo){
- Arrays.fill(i,-1);
- }
- return dfs(0,v.length-1);
- }
- public int dfs(int i, int j){
- if(i+1 == j) return 0;
- if(memo[i][j] != -1) return memo[i][j];
- int res = Integer.MAX_VALUE;
- for(int k = i+1; k < j; k++){
- res = Math.min(res,dfs(i,k)+dfs(k,j)+v[i]*v[j]*v[k]);
- }
- memo[i][j] = res;
- return res;
- }
- }
- class Solution {
- public int minScoreTriangulation(int[] v) {
- int[][] dp = new int[v.length][v.length];
- for(int i = v.length-3; i >= 0; i--){
- for(int j = i+2; j < v.length; j++){
- dp[i][j] = Integer.MAX_VALUE;
- for(int k = i+1; k < j; k++){
- dp[i][j] = Math.min(dp[i][j],dp[i][k]+dp[k][j]+v[i]*v[j]*v[k]);
- }
- }
- }
- return dp[0][v.length-1];
- }
- }
本方法小结:
(1)dp[i][j]代表从i到j区间内所构成的三角形面积的最小值
(2)i从length-3开始可以理解成 从i开始最少需要三条边构成三角形,j和k都比i要大,所以从i-3开始满足能成立一个三角形,j从i+2开始是因为中间需要留至少一个k的空间,最后k在i和j中取值,左虚右虚
(3)由于k比i大,而 dp[i][j]需要dp[i][k]作为基础,所以i的转移方向为从大到小,同理,j的方向为从小到大
- class Solution {
- public int minScoreTriangulation(int[] v) {
- int n = v.length;
- int[][] dp = new int[n][n];
- for(int len = 3; len <= n; len++){
- for(int L = 0; L+len-1 < n; L++){
- int R = L + len - 1;
- dp[L][R] = 0x3f3f3f3f;
- for(int k = L+1; k < R; k++){
- dp[L][R] = Math.min(dp[L][R], dp[L][k] + dp[k][R] + v[L]*v[R]*v[k]);
- }
- }
- }
- return dp[0][n-1];
- }
- }
本方法小结:
(1)以上的方法都是枚举左右端点,此方法为经典的枚举区间
(2)区间长度len最少从3开始,长度大于等于3才能构成三角形,枚举左端点,右端点为左端点加上区间长度,然后枚举中间的k点(相当于三角形的顶点)
(3)一般的区间DP都是枚举区间,然后枚举左右端点,这题可以直接枚举左右端点,然后枚举中间的k点
[1] leetcode 灵茶山艾府 【视频讲解】教你一步步思考动态规划!(Python/Java/C++/Go)
[2] leetcode Chisato 【视频讲解】教你一步步思考动态规划!(Python/Java/C++/Go)
[3] leetcode return up; C++:动态规划(新瓶装旧酒)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。