04.07_区间dp

区间 DP

问题描述

区间动态规划是指在区间上进行状态转移的问题。
常见的有石子合并、回文串判定等。
通常需要定义状态表示区间[i,j]上的最优解。

石子合并

算法思想

  1. 定义dp[i][j]表示将区间[i,j]内的石子合并的最小代价
  2. 枚举区间长度和起点,然后枚举分割点
  3. 状态转移方程:dp[i][j] = min(dp[i][k] + dp[k+1][j] + sum[i,j])
  4. 最终答案为dp[1][n]

代码实现

int mergeStones(vector<int>& stones) {
    int n = stones.size();
    vector<int> sum(n + 1);
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, INT_MAX));
    
    // 前缀和
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i-1] + stones[i-1];
    }
    
    // 初始化长度为1的区间
    for (int i = 1; i <= n; i++) {
        dp[i][i] = 0;
    }
    
    // 枚举区间长度
    for (int len = 2; len <= n; len++) {
        // 枚举左端点
        for (int i = 1; i + len - 1 <= n; i++) {
            int j = i + len - 1;
            // 枚举分割点
            for (int k = i; k < j; k++) {
                dp[i][j] = min(dp[i][j], 
                             dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);
            }
        }
    }
    
    return dp[1][n];
}
public int mergeStones(int[] stones) {
    int n = stones.length;
    int[] sum = new int[n + 1];
    int[][] dp = new int[n + 1][n + 1];
    
    // 前缀和
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i-1] + stones[i-1];
    }
    
    // 初始化
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            dp[i][j] = Integer.MAX_VALUE;
        }
        dp[i][i] = 0;
    }
    
    // 枚举区间长度
    for (int len = 2; len <= n; len++) {
        for (int i = 1; i + len - 1 <= n; i++) {
            int j = i + len - 1;
            for (int k = i; k < j; k++) {
                dp[i][j] = Math.min(dp[i][j], 
                                  dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);
            }
        }
    }
    
    return dp[1][n];
}
def mergeStones(stones: List[int]) -> int:
    n = len(stones)
    sum = [0] * (n + 1)
    dp = [[float('inf')] * (n + 1) for _ in range(n + 1)]
    
    # 前缀和
    for i in range(1, n + 1):
        sum[i] = sum[i-1] + stones[i-1]
    
    # 初始化长度为1的区间
    for i in range(1, n + 1):
        dp[i][i] = 0
    
    # 枚举区间长度
    for length in range(2, n + 1):
        for i in range(1, n - length + 2):
            j = i + length - 1
            for k in range(i, j):
                dp[i][j] = min(dp[i][j], 
                             dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1])
    
    return dp[1][n]

回文串判定

算法思想

  1. 定义dp[i][j]表示区间[i,j]是否为回文串
  2. 如果s[i] == s[j],则看[i+1,j-1]是否为回文串
  3. 状态转移方程:dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1]
  4. 最终可以判断任意区间是否为回文串

代码实现

vector<vector<bool>> palindrome(string s) {
    int n = s.length();
    vector<vector<bool>> dp(n, vector<bool>(n, false));
    
    // 长度为1的区间
    for (int i = 0; i < n; i++) {
        dp[i][i] = true;
    }
    
    // 枚举区间长度
    for (int len = 2; len <= n; len++) {
        for (int i = 0; i + len - 1 < n; i++) {
            int j = i + len - 1;
            if (len == 2) {
                dp[i][j] = (s[i] == s[j]);
            } else {
                dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1];
            }
        }
    }
    
    return dp;
}
public boolean[][] palindrome(String s) {
    int n = s.length();
    boolean[][] dp = new boolean[n][n];
    
    // 长度为1的区间
    for (int i = 0; i < n; i++) {
        dp[i][i] = true;
    }
    
    // 枚举区间长度
    for (int len = 2; len <= n; len++) {
        for (int i = 0; i + len - 1 < n; i++) {
            int j = i + len - 1;
            if (len == 2) {
                dp[i][j] = (s.charAt(i) == s.charAt(j));
            } else {
                dp[i][j] = (s.charAt(i) == s.charAt(j)) && dp[i+1][j-1];
            }
        }
    }
    
    return dp;
}
def palindrome(s: str) -> List[List[bool]]:
    n = len(s)
    dp = [[False] * n for _ in range(n)]
    
    # 长度为1的区间
    for i in range(n):
        dp[i][i] = True
    
    # 枚举区间长度
    for length in range(2, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            if length == 2:
                dp[i][j] = (s[i] == s[j])
            else:
                dp[i][j] = (s[i] == s[j] and dp[i+1][j-1])
    
    return dp

时间复杂度分析

算法 时间复杂度 空间复杂度
石子合并
回文串判定

应用场景

  1. 石子合并问题
  2. 回文串判定
  3. 矩阵链乘法
  4. 括号匹配
  5. 最优三角剖分

注意事项

  1. 区间遍历顺序
  2. 边界条件处理
  3. 状态转移正确性
  4. 区间大小限制
  5. 空间优化可能

经典例题

  1. 【模板】区间dp
  2. 合并回文子串
  3. 涂色PAINT
  4. 括号区间匹配
  5. 能量项链
  6. 取数游戏
  7. 矩阵取数游戏
  8. 加分二叉树
全部评论

相关推荐

25春招笔试完就开始面试了,提前给大家分享个去年的面经1.自我介绍2.可以说一下IOC和AOP吗?3.IOC有什么好处?3.Spring&nbsp;aop有几种代理模式?4.第二个项目是个人项目吗?5.Java的面向对象有几大特性?并说说你对这几个特性的理解6.关于Redis的了解,你在这个项目中用Redis做了什么?7.aof和rdb是什么技术?区别是什么?8.redis集群技术你了解吗?9.redis的一个key过来会分配到哪个机器上,算法是怎么样的呢?它有一套自己的算法,做一个映射10.hashmap和hashtable的区别?11.concurrent&nbsp;hashmap的阈值是多少?12.hash冲突的话有几种解决方式线性探测,平方探测,拉链法13.介绍一下hashmap的扩容因子,初始扩容因子是多少,初始数组容量是多少14.在你的项目中,kafka是用来做什么的?15.说一下redis的缓存雪崩,缓存穿透怎么解决的?16.缓存穿透怎么解决的?答:布隆过滤器&nbsp;追问:有其他的解决方案吗?17.redis热点key过期了,怎么处理?大量用户同时访问一个key,热点失效了,动态调整失效时间18.项目中的es是做什么的?问了论文,专利19.Java用了多久了?平时遇到过OOM的状况吗?介绍了一次full&nbsp;gc20.介绍一下JVM的内存模型21.CMS垃圾回收和G1垃圾回收的区别22.关于Zookeeper?23.介绍一下Spring,&nbsp;Spring&nbsp;MVC,&nbsp;Spring&nbsp;Boot,&nbsp;Spring&nbsp;Cloud?24.微服务之间的通信方式?RPC25.数据库用的是什么?MySQL&nbsp;哪个版本?26.讲一下数据库的事务?ACID特性27.MySQL事务的隔离级别:读未提交,读已提交,可重复读(默认隔离级别),串行化四个隔离级别分别解决了什么问题28.介绍一下七层网络架构29.介绍一下ARP协议,这是哪一层协议30.关于传输层协议了解哪些?TCP和UDP,介绍一下应用场景31.说一下Https和Http的区别32.Https的加密方式?&nbsp;对称加密+非对称加密33.说一下Http请求建立时候的错误代码34.手撕一下二叉树的中序遍历?先写递归,再写非递归小米公司校招内推码:&nbsp;BAD31ZQ&nbsp;投递链接:&nbsp;https://xiaomi.jobs.f.mioffice.cn/referral/campus/position/?token=NTsxNzQxNjU5NDI4MzU5OzcyNTI2MjA3NTAxMzI5MDQwNDQ7NzQyNzMxNTUyNTI5NjI5MTk0OA小米公司社招内推码:&nbsp;BAD31ZQ&nbsp;投递链接:&nbsp;https://xiaomi.jobs.f.mioffice.cn/referral/position/?token=NTsxNzQxNjU5NDgzMTM1OzcyNTI2MjA3NTAxMzI5MDQwNDQ7NzQyNzMyNzM3MjQyNzYyNDU1Ng#小米内推##小米##春招##面经##内推#
小米集团
|
校招
|
超多精选岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务