动态规划——划分型

动态规划——划分型


目录

  1. 划分型动态规划概述
  2. 最少将n分成几个完全平方数之和
  3. 求字符串划分回文串的最少次数
  4. 求最短时间抄写完所有的书

1. 划分型动态规划概述

给定长度为N的序列或字符串,要求划分成若干段

  1. 段数不限,或指定K段
  2. 每一段满足一定的性质

2. 最少将n分成几个完全平方数之和

1. 题目描述:给定一个正整数n,问最少可以将n分成几个完全平方数之和

例子:
输入:n=13
输出:2(13=4+9)

2. 思路
  1. 确定状态

    1. 最优策略中最后一个完全平方数j^2,那么n-j^2也一定能被划分成最少的完成平方数之和。
  2. 转移方程

    1. 设 f[i] 表示 i 最少被分成几个完全平方数之和
      f[i] = min(1<=jj<=i){f[i-jj] + 1}
  3. 初始条件和边界情况
    初始条件:f[0] = 0

  4. 计算顺序
    初始化 f[0]
    计算f[1]…f[N]
    答案是 f[N]

3. 代码实现
public static int numSquares(int n) {
        int[] f = new int[n];
        f[0] = 0;
        for (int i = 1; i <= n; i++) {
            f[i] = Integer.MAX_VALUE;
            for (int j = 1; j * j <= i; j++) {
                f[i] = Math.min(f[i - j * j] + 1, f[i]);
            }
        }
        return f[n];
    }

3. 求字符串划分回文串的最少次数

1. 题目描述

给定一个字符串S[0…N-1],将这个字符串划分成若干段,每一段都是一个回文串,求最少划分次数

例如:
输入:“aab”
输出:1(划分1次->“aa”,“b”)

2. 思路
  1. 确定状态
    关注最优策略中最后一段回文串,设为S[j…N-1],那么则需要知道S前 j 个字符[0…j-1]最少可以划分成几个回文串

  2. 转移方程
    设 f[i] 为前 i 个字符 S[0…i-1] 最少可以划分成几个回文串
    f[i] = min(j=0,…i-1){f[j]+1|S[j…i+1]是回文串}

  3. 初始条件和边界情况
    初始条件:f[0] = 0

  4. 计算顺序
    f[0],f[1],…f[N]
    答案是 f[N]-1

3. 代码实现
private boolean[][] calcPalin(char[] s) {
        int n = s.length;
        boolean[][] f = new boolean[n][n];
        int i, j, c;
        for (i = 0; i < n; i++) {
            for (j = i; j < n; j++) {
                f[i][j] = false;
            }
        }

        //奇数
        for (c = 0; c < n; c++) {
            i = j = c;
            while (i >= 0 && j < n && s[i] == s[j]) {
                f[i][j] = true;
                i--;
                j++;
            }
        }

        //偶数
        for (c = 0; c < n; c++) {
            i = c;
            j = c + 1;
            while (i >= 0 && j < n && s[i] == s[j]) {
                f[i][j] = true;
                i--;
                j++;
            }
        }
        return f;

    }

    public int minCut(String ss) {
        char[] s = ss.toCharArray();
        int n = s.length;
        if (n == 0) {
            return 0;
        }
        boolean[][] isPalin = calcPalin(s);
        int[] f = new int[n + 1];
        f[0] = 0;
        for (int i = 1; i <= n; i++) {
            f[i] = Integer.MAX_VALUE;
            for (int j = 0; j < i; j++) {
                if (isPalin[j][i - 1]) {
                    f[i] = Math.min(f[i], f[j] + 1);
                }
            }
        }
        return f[n] - 1;
    }

4. 求最短时间抄写完所有的书

1. 题目描述

有N本书需要被抄写,第i本书有A[i]页,i=0,1,…N-1。
有K个抄写员,每个抄写员可以抄写连续的若干本书(例如:第3~5本书,或者第10本书),每个抄写员的抄写速度都一样:一分钟一页。
最少需要多少时间抄写完所有的书

例子:
输入:A=[3,2,4],K=2
输出:5(第一个抄写员抄写第1本和第2本,第二个抄写员抄写第3本书)

2. 思路
  1. 确定状态
    最后一步:最优策略中最后一个抄写员Bob(设他为第K个)抄写的部分为一段连续的书,包含最后一本,如果Bob抄写第j本到n-1本书,则Bob需要时间A[j]+…+A[N-1]
    则需要知道前面K-1个人最少需要多少时间抄完前j本书(第0~i-1本书)
    状态:设f[k][i]为前k个抄写员最少需要多少时间抄完前i本书
  2. 转移方程
    设f[k][i]为前k个抄写员最少需要多少时间抄完前i本书
    f[k][i] = min(j=0,…i){max{f[k-1][j], A[j]+…+A[N-1]}}
  3. 初始条件和边界情况
    0个抄写员只能抄0本书:f[0][0] = 0,f[0][1] = f[0][2] = … = f[0][N] = 正无穷
    k个抄写员(k>0)需要0时间抄0本书:f[k][0] = 0 (k>0)
    如果K>N,可以赋值K=N
  4. 计算顺序
    计算 f[0][0],f[0][1],…f[0][N]
    计算 f[1][0],f[1][1],…f[1][N]

    计算 f[K][0],f[K][1],…f[K][N]
    时间复杂度O(N^2K),空间复杂度O(NK)
3. 代码实现
public int copyBook(int K, int[] A) {
        int n = A.length;
        if (n == 0) {
            return 0;
        }
        int[][] f = new int[K + 1][n + 1];
        f[0][0] = 0;
        for (int i = 1; i <= n; i++) {
            f[0][i] = Integer.MAX_VALUE;
        }
        for (int k = 1; k <= K; k++) {
            f[k][0] = 0;
            for (int i = 1; i <= n; i++) {
                f[k][i] = Integer.MAX_VALUE;
                int sum = 0;
                for (int j = i; j >= 0; j--) {
                    f[k][i] = Math.min(f[k][i], Math.max(f[k - 1][j], sum));
                    if (j > 0) {
                        sum += A[j - 1];
                    }
                }
            }
        }
        return f[K][n];
    }

5. 解密方式

1. 题目描述
  1. 有一段由A-Z组成的字母串信息被加密成数字串,加密方式为:A->1, B->2,…, Z->26,
  2. 给定加密后的数字串S[0…N-1],问有多少种方式解密成字母串
    例子:
    输入:12
    输出:2(AB或者L)
2. 思路
  1. 确定状态
    1. 解密数字串即划分成若干段数字,每段数字对应一个字母
    2. 最后一步:对应一个字母A, B,…,Z,这个字母加密时变成1, 2, …26
    在这里插入图片描述
    一共100+50=150种解密方式
    3. 要求数字串前N个字符的解密方式数,需要知道数字串前N-1和N-2个字符的解密方式数
  2. 转移方程
    1. 状态:设数字串S前i个数字解密成字母串有f[i]种方式
    在这里插入图片描述
  3. 初始条件和边界情况
    初始条件:f[0]=1,即空串有1种方式解密
    边界情况:如果i=1,只看最后一个数字
  4. 计算顺序
    f[0], f[1],…,f[N]
    答案是f[N]
3. 代码实现
public int numDecodings(String ss) {
        if (ss.length() == 0) {
            return 1;
        }
        char[] s = ss.toCharArray();
        int n = s.length;
        int[] f = new int[n + 1];
        f[0] = 1;

        for (int i = 1; i <= n; i++) {
            f[i] = 0;
            if (s[i - 1] >= '1' && s[i - 1] <= '9') {
                f[i] += f[i - 1];
            }
            if (i > 1) {
                int num = 10 * (s[i - 2] - '0') + (s[i - 1] - '0');
                if (num >= 10 || num <= 26) {
                    f[i] += f[i - 2];
                }
            }

        }
        return f[n];
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值