动态规划

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式 。

最大子串和

给定一整型数列{a1,a2…,an},找出连续非空子串{ax,ax+1,…,ay},使得该子序列的和最大.

也可以用分治,将串分成两半,最大子串要么在左边,要么在右边,要么穿过中点横跨左右.

暴力解法:直接求出所以的子串和再找出最大.
O(n^3)

int a[105],n,maxSum=0;
void solve(){
	for(int i=0;i<n;i++){
		for(int j=i;j<n;j++){
			int T=0;
			for(int k=i;k<=j;k++)
				T+=a[k];
			if(T>maxSum)
				maxSum=T;
		}
	}

}

改进的穷举:
O(n^2)

int a[105],n,maxSum=0;
void solve(){
	for(int i=0;i<n;i++){
		int T=0;
		for(int j=i;j<n;j++){
			T+=a[j];
			if(T>maxSum)
				maxSum=T;
		}
	}

}

动态规划:
O(n)
使用数组b[j]保存0-j的最大子串和
例如:对于a[]={-2,11,-4,13,-5,-2}
b[]={-2,11,7,20,15,13}

int a[105],n,maxSum=0;
void solve(){
	int b[105];
	b[0]=a[0];
	for(int i=1;i<n;i++){
		if(b[i-1]>0)
			b[i]=a[i]+b[i-1];
		else
			b[i]=a[i];
		if(b[i]>maxSum)
			maxSum=b[i];
	}

}

最长递增子序列

给定一个长度为N的数组,找出一个最长的单调递增子序列,子序列不一定连续,但初始顺序不能乱。

用d[i]表示0-i数组的最长子序列长度,则有递推方程:
在求d[i]时,找到i前面的所有j使得a[j]<a[i],如果j存在,那么d[i]=max{d[j]}+1;如果不存在,那么a[i]自己构成长度为1的递增子序列.

int list(int[] p,n){
	if(n==1)
		return 1;
	int d[]=new int[n];
	Arrays.fill(d,0);
	d[0]=1;
	for(int i=1;i<n;i++){
		d[i]=1;
		for(int j=0;j<i;j++){
			if(p[j]<p[i]&&d[j]+1>d[i])
				d[i]=d[j]+1;
		}
	}
	int max=1;
	for(int i=0;i<n;i++){
		if(d[i]>max)
			max=d[i];
	}
	return max;
}

0-1背包问题(acwing 2)

有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

设d[i][j]表示将前i个物品放入剩余容量为j的背包可获得的最大价值.则:
d[i+1][j]=max{d[i][j],d[i][j-w[i+1]]+v[i+1]}
d[i][j-w[i+1]]+v[i+1]其实是查表操作,查一下前i个物品在剩余容量j-w[i+1]时的最大价值.
因为对于第i+1个物品,如果重量小于背包容量则可以选择放入或不放入,我们取最大值.

对于第0个物品(w[0]),我们可以计算出背包容量为0~C时的最大价值,如果w[0]>剩余容量,则没法放入,如果w[0]<=剩余容量,则必须放入.
对于第1,2,3…个物品,可以利用上面递推公式求出.

 public int knapSack(int[] w, int[] v, int C) {//w:重量,v价值,C背包容量
        int size = w.length;
        if (size == 0) {
            return 0;
        }

        int[][] dp = new int[size][C + 1];
        //初始化第一行
        //仅考虑容量为0-C的背包放第0个物品的情况
        for (int i = 0; i <= C; i++) {
            dp[0][i] = w[0] <= i ? v[0] : 0;
        }
		//填充其他行和列
        for (int i = 1; i < size; i++) {
            for (int j = 0; j <= C; j++) {
                dp[i][j] = dp[i - 1][j];
                if (w[i] <= j) {
                    dp[i][j] = Math.max(dp[i][j], v[i] + dp[i - 1][j - w[i]]);
                }
            }
        }
        return dp[size - 1][C];
    }

完全背包问题(acwing 3)

有 N 种物品和一个容量是C 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

数据范围
0<N,C≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

这里增加了每种物品有无限个的条件.

还是设dp[i][j]表示将前i个物品放入剩余容量为j的背包可获得的最大价值
设第i+1个物品放入k(0,1,2,3…)个
如果剩余容量j>=kw[i]
则dp[i+1][j]=max{ dp[i][j-k
w[i]]+k *v[i]}

dp数组含义:dp[i][j]dp[i][j]=从编号为1~i的物品中挑选物品放入容量为j的背包中能得到的最大价值。注意:n种物品编号范围为1-n,0做作递推的起点。
初始条件:dp[0][0~W]=0;设第0个物品重量为0,价值为0;
递推公式:
dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}
结果:结果在dp[n][W]中

创建设置时默认初始化为0,所以不用手动设置dp[0][0~W]=0.
由于引入了重量为0价值为0的物品,所以dp第一维长度为P.length + 1,由于最大容量为T,需访问dp[i][T],所以第二维长度为T+1.

public static class Knapsack {
    private static int[] P={5,8};
    private static int[] V={5,7};
    private static int T = 10;

    private int[][] dp = new int[P.length + 1][T + 1];

    @Test
    public void solve3() {
        for (int i = 0; i < P.length; i++){
            for (int j = 0; j <= T; j++){
                for (int k = 0; k * V[i] <= j; k++){
                    dp[i+1][j] = Math.max(dp[i+1][j], dp[i][j-k * V[i]] + k * P[i]);
                }
            }
        }
        System.out.println("最大价值为:" + dp[P.length][T]);
    }

找零钱(LeetCode 322)

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:

输入: coins = [2], amount = 3
输出: -1

说明:
你可以认为每种硬币的数量是无限的。

分析:这里硬币面值不一定能构成倍数关系,所以不能用贪心法.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值