左神算法讲堂笔记 09 由递归到动态规划

最经典的内容在最底下,前面的是知识铺垫

一、

有n个人希望把黄金分隔成 {a1,a2,a3,a4}, 希望代价最小。 代价是这么算的,例如长度是 7 ,分割成 3 4, 那么代价是7 。

逆向思维、哈夫曼编码

一开始的想法是,排序,每次切下来最大的。 但是 2 2 2 2 3 3 3 3 这种情况就pass了。 最后的做法是类似哈夫曼编码, 每次找出两个最小的, 合并,合并之后的点再加进去集合

二、

有n个项目,对于第i个项目,需要花费ci,得到的纯利润是di。启动资金是m,最多能做k个项目,问最后资金最多是多少?

思路

贪心是必须的,每次在 m > ci 的集合中找出di最大的。 我个人是想到了二分,但是进行多次二分,并且处理已经做过的项目这点不好处理。这里用堆来做,开一个小根堆,按ci进行排序; 每次都从小根堆中取出 m > 堆顶 的元素,放到另一个大根堆,这个大根堆按di进行排序。 因此每次都从大根堆中取出一个项目去做。

Java中的笔记

把该题封装成类, 那么一种实现比较器的就是下面这样,当然也可以传递泛型。

 class Node implements Comparable{
        int c,d;
        public Node(int c, int d) {
            this.c = c;
            this.d = d;
        }
        @Override
        public int compareTo(Object o) {
            Node o2 = (Node)o;
            return c - o2.c;
        }
    }

这种做法是在类中实现了Comparable接口,那么就把compareTo写死了,固定按一种顺序进行排序。
在本题应该采用另一种做法,传入比较器。

class MyCompare1 implements Comparator<Node> {
        @Override
        public int compare(Node o1, Node o2) {
        	// c小的在前
            return o1.c - o2.c;
        }
    }

三、汉诺塔

状态转移大法

// from上有n个, 目标是把1-n都移动到to上
    public void processing(int n,String from,String to,String help){
       if(n == 1){
           System.out.println("移动 1 from:" + from + " 到 " + to);
           return;
       }
       // 把n-1个 从from 放到 help 可以借助 to
       processing(n-1,from,help,to);
       // 把第n个 从from 放到 to
       System.out.println("移动 "+ n + " from:" + from + " 到 " + to);
       // 此时n-1个在help上,1个在to上。 现在可以把help当成from(从这根杆出发) , 移到to , 借助from杆
       processing(n-1,help,to,from);
    }

四、搜索

目的是为了掌握尝试的思想

4.1 打印字符串的所有子序列

abc -> " " , a, ab, abc , b,bc, c

思路:

我自己思考了下,惯性思维中的dfs大概会这么写

dfs(int len, int id,stringBuffer sb, string t){
if(len == t.size()){
// 操作sb
return;
}
dfs(len+1,id+1,sb,t);
for(int i=id+1;i<t.size();i++){
sb.append(t.charAt(i));
dfs(len+1,i,sb,t);
sb.remove(sb.length()-1);
}

}

但是这个代码有很多重复计算,由于空串的存在,导致什么也不做也能推到下一种状态,因此这么写就行.

void dfs(int id, char[] t, String res){
        if(id == t.length){
            System.out.println(res);
            return;
        }
        dfs(id+1,t,res);
        dfs(id+1,t,res + String.valueOf(t[id]));
    }

    public static void main(String[] args) {
        Dongtai d = new Dongtai();
        d.dfs(0,new String("abc").toCharArray(),"");
    }

五、动态规划

今天的课真让我醍醐灌顶,解开了我多年来的疑惑,佩服左神。

解决动态规划问题是有套路的,按套路走是肯定能做出来的。

来看个例题:

在这里插入图片描述

解法

第一步:写出递归的“试”法,看看如何尝试可以解决问题。

num:表示前几步已选择的值的和, 变量
id:在下标为 0 — id-1中的数中进行选择,变量
target:程序输入的目标和,常量
arr[]: 程序输入的数组,常量

boolean isEquals(int num, int id,int target, int arr[]){
        if(id == arr.length){
            return num == target;
        }
        return isEquals(num + arr[id], id + 1, target, arr)
                || isEquals(num, id + 1, target, arr);
    }

main函数中:
System.out.println(d.isEquals(0, 0, 19, new int[]{3, 4,7,7, 9, 7}));

第二步:判断是否是无后效性问题, 什么叫后效性问题?上面这个例子,现在有一个进入isEquals(num:7,id:3)的方法,那么这个方法计算的结果,和它是通过何种方式进入的无关。比方说通过选择3和4 , 或者选择7, 都能进入这个状态,但是一旦进入这个状态那么得到的值和前几步的选择无关

无后效性中的变量, id num 就作为动态规划dp数组的变量。

第三步: 确定目标状态,id = 0 , num = 0 ,是目标状态。(这里其实还比较不好理解,这是根据我们递归程序的入口定义的)

第四步: 求出终止状态和基础条件。这里也要参照我们写的递归方法
当id == arr.length时,num == target(从前n个中选,选出来的总和 == target) ,这个状态是true; 当id == arr.length,num != target,那么这些状态是false。

在这里插入图片描述

最后一步:确定普遍位置由哪些精确位置得到。
递归函数中写了,DP[id][num] = DP[id+1][num] || DP[id+1][num + arr[id]],因此这就是状态转移方程。现在进行填表即可。

从n-1行开始,DP[id][num] 需要查看DP[id+1][num] 和 DP[i+1][num + arr[id]] 这两个位置的值。 然后一步步把表填满,完成!

在这里插入图片描述
DP的代码不过就是把刷表的过程模拟了一遍而已。

boolean isEqualsDP(int target, int arr[]) {
        int n  = arr.length;
        int sum = 0;
        for(int i=0;i<n;i++)
            sum += arr[i];
        if(target > sum)return false;
        boolean [][]dp = new boolean [n+1][sum+1];
        for (int j = 0; j <= sum; j++) {
            dp[n][j] = j==target;
        }

        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j <= sum; j++) {
                boolean flag ;

                flag = dp[i+1][j];
                if(j + arr[i] <= sum){
                    flag = flag | dp[i+1][j + arr[i]];
                }
                dp[i][j] = flag;
            }
        }
        return dp[0][0];
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值