动态规划解决算法问题(一)

什么是动态规划:

  动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。                   ------维基百科

   以上定义来自维基百科,看定义感觉还是有点抽象。简单来说,动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法。

  一般这些子问题很相似,可以通过函数关系式递推出来。然后呢,动态规划就致力于解决每个子问题一次,减少重复计算,比如斐波那契数列就可以看做入门级的经典动态规划问题。

动态规划的三个步骤就是:

1.定义状态,

2.列出递推公式,

3.找出边界条件。

下面,我们来看两道题。

问题一:

给定一个字符串s和一个字符串t,计算在s的子序列中t出现的个数。

分析:

  字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置 所组成的新字符串。
(例如,"BCD"是"ABCDE"的一个子序列,而"BDC"不是)
  这题说的是s的子序列中出现t的个数,翻译一下就是字符串s的所有子序列中,和字符串t完全一样的有多少个。我们定义dp[i][j]表示t的前i个字符可以由s的前j个字符组成的个数
(也可以说是字符串s的前j个字符组成的子序列中,和字符串t的前i个字符组成的字符串一样的有多少个)。
   那么最终我们只需要求出dp[tLength][ sLength]即可(其中tLength和sLength分 别表示字符串t和s的长度)
  如果字符串t的第i个字符和字符串s的第j个字符不一样,也就是说字符串s的第j个字符 不能匹配字符串t的第i个字符。那么我们只能计算字符串s的前j-1个字符构成的子序列中包含字符串t的前i个字符组成的字符串的个数。
动态规划的三个步骤就是:
1.定义状态,
2.列出递推公式,
3.找出边界条件。
前面两步我们都完成了,我们来看最后一个。
因为空字符串" "是所有字符串的子集,所以当字符串t为空的时候,dp[0][j]=1;

来看一下代码:


import java.util.Scanner;

public class Demo01 {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String s=sc.next();
        System.out.println("请再输入一个字符串:");
        String t=sc.next();
        int index=numDistinct(s,t);
        System.out.println(index);

    }

    public static int numDistinct(String s, String t) {
        //sLength和tLength分别是两个字符串的长度
        int sLength = s.length();
        int tLength = t.length();
        int[][] dp = new int[tLength + 1][sLength + 1];
        //base case 边界条件
        for (int j = 0; j <= sLength; j++) {
            dp[0][j] = 1;
        }
        for (int i = 1; i <= tLength; i++) {
            for (int j = 1; j <= sLength; j++) {
                //下面是递推公式
                if (t.charAt(i - 1) == s.charAt(j - 1)) {
                    //如果字符串t的第i个字符和s的第j个字符一样,
                    // 那么有两种选择
                    dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
                } else {
                    //如果字符串t的第i个字符和s的第j个字符不一样,我们只能用字符串s的前j-1个字符来计算他包含的数量
                    dp[i][j] = dp[i][j - 1];
                }
            }
        }
        return dp[tLength][sLength];
    }
}

运行结果如图:

问题二:

给你一个只包含正整数的非空数组nums。请你判断是否可以将这个数组分割成两 个子集,使得两个子集的元素和相等。

分析:
这题判断把数组分成两份,这两份的元素和是否相等。
  首先我们需要计算数组中所 有元素的和sum,然后判断sum是否是偶数:
  如果不是偶数,说明不可能分割成完全相等的两份,直接返回false。
  如果是偶数,我们只需要判断是否存在一些元素的和等于sum/2,如果等于sum/2,那么剩下的肯定也等于sum/2,说明我们可以把数组分为元素和相等的两部分。

看一下代码 :


import java.util.Arrays;

public class Demo02 {
    public static void main(String[] args) {
//        int[]  nums={1,2,3,4,5,5};
        int[]  nums={1,2,3,4,5,6,7,8,9};
        boolean b=canPartition(nums);
        System.out.println("原数组是:"+Arrays.toString(nums));
        if(b){
            System.out.println("原数组可以分割成两个元素和相同的子集");
        }else {
            System.out.println("原数组不可以分割成两个元素和相同的子集");
        }
        

    }
    public static boolean canPartition(int[] nums) {
//计算数组中所有数字的和
        int sum = 0;
        for (int n : nums)
            sum += n;
//如果sum是奇数,直接返回false
        if ((sum & 1) == 1)
            return false;
        int len = sum >> 1;
//这里bits的长度是len+1,因为我们只需要计算
//低位就行了,没必要计算所有的
        byte[] bits = new byte[len + 1];
        bits[0] = 1;
        for (int i = 0; i < nums.length; i++) {
            int num = nums[i];
            int size = len - num;
            for (int j = size; j >= 0; j--) {
                bits[j + num] |= bits[j];
            }
//判断中位数如果是1,说明可以分成两种相等的
//子集,直接返回true,不需要再计算了
            if ((bits[len] & 1) != 0)
                return true; }
        return false; }
}

在主方法里我定义了两个数组,是为了测试能否成功判断,所以,以下分别为是/不是的两个运行结果。

{1,2,3,4,5,5}可以分成两个元素和相同的子集

{1,2,3,4,5,6,7,8,9}则不能

运行结果如下:

数组 {1,2,3,4,5,5}运行结果:

{1,2,3,4,5,6,7,8,9}运行结果:

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值