最经典的内容在最底下,前面的是知识铺垫
有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进行排序。 因此每次都从大根堆中取出一个项目去做。
把该题封装成类, 那么一种实现比较器的就是下面这样,当然也可以传递泛型。
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 {
@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);
}
目的是为了掌握尝试的思想
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
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]] 这两个位置的值。 然后一步步把表填满,完成!
boolean isEqualsDP(int target, int arr[]) {
int n = arr.length;
int sum = 0;
for(int i=0;i 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];
}