贪心策略+暴力递归+动态规划
- 切金条问题
- 项目资金最大化问题
- 会议室占用
- 介绍递归
- 动态规划
(from左神算法初级班第七节)
1.切金条问题
【问题】一块金条切成两半,是需要花费和长度数值一样的铜板的。比如 长度为20的 金条,不管切成长度多大的两半,都要花费20个铜 板。一群人想整分整块金条,怎么分最省铜板?
例:给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60,金条要分成10,20,30三个部分。如果,先把长度60的金条分成10和50,花费60 再把长度50的金条分成20和30,花费50 一共花费110铜板。但是如果,先把长度60的金条分成30和30,花费60再把长度30金条分成10和20,花费30一共花费90铜板。
1)哈夫曼编码问题
生成树的非叶节点之和代价最小
2)方法:
- 每次将数组放入到小跟堆中,每次取出两个堆顶的值(都是最小值),不放回
- 将两个值相加然后再放入到数组中,直到数组最后剩下最后一个数,就是生成哈夫曼树的代价。
3)代码
public static int lessMoney(int[] arr) {
PriorityQueue<Integer> pQ = new PriorityQueue<>();
for (int i = 0; i < arr.length; i++) {
pQ.add(arr[i]);
}
int sum = 0;
int cur = 0;
while (pQ.size() > 1) {
cur = pQ.poll() + pQ.poll();
sum += cur;
pQ.add(cur);
}
return sum;
}
2.项目资金最大化问题
给定两个数组利润数组和花费数组,以及启动资金变量w。
只有当w大于花费数组中的数值时,才能做对应的项目。求n个项目后,资金最大化是多少?一次只能做一个项目。
例如:数组0位置需要花费100,1位置需要花费150,启动资金w为120,所以目前只能做0号项目,不能做1号项目。
1)贪心策略
使用一个大根堆和一个小根堆
- 将项目信息(以花费作为排序)放入小根堆中,与启动资金w比较,项目的花费比w小项目就弹出,全部放入到大根堆中
- 然后每次从大根堆里取最大值。
- 项目完成后,用新的w跟小根堆里花费进行比较,看看能不能够弹出新的项目。有就更新大根堆。
2)代码
import java.util.Comparator;
import java.util.PriorityQueue;
public class Code_03_IPO {
public static class Node {
public int p;//收益
public int c;//花费
public Node(int p, int c) {
this.p = p;
this.c = c;
}
}
public static class MinCostComparator implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) {
return o1.c - o2.c;
}
}
public static class MaxProfitComparator implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) {//按照收益
return o2.p - o1.p;
}
}
public static int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
Node[] nodes = new Node[Profits.length];
for (int i = 0; i < Profits.length; i++) {//生成Node
nodes[i] = new Node(Profits[i], Capital[i]);
}
PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());//代价小根堆
PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());//利益大根堆
for (int i = 0; i < nodes.length; i++) {//把所有项目加到小根堆中去
minCostQ.add(nodes[i]);
}
for (int i = 0; i < k; i++) {//最多做k个项目
while (!minCostQ.isEmpty() && minCostQ.peek().c <= W) {//如果堆顶的项目花费小于w
maxProfitQ.add(minCostQ.poll());
}
if (maxProfitQ.isEmpty()) {//没做到k个项目就得停,因为没有项目可以做了
return W;
}
W += maxProfitQ.poll().p;
}
return W;
}
}
3.会议室占用问题
【问题】一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目 的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数 组,里面 是一个个具体的项目),你来安排宣讲的日程,要求会 议室进行 的宣讲的场次最多。返回这个最多的宣讲场次。
1)贪心策略
按照最早结束时间安排项目
2)代码:
import java.util.Arrays;
import java.util.Comparator;
public class Code_06_BestArrange {
public static class Program {
public int start;
public int end;
public Program(int start, int end) {
this.start = start;
this.end = end;
}
}
public static class ProgramComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {//谁结束早,就选谁
return o1.end - o2.end;
}
}
public static int bestArrange(Program[] programs, int start) {
Arrays.sort(programs, new ProgramComparator());//
int result = 0;
for (int i = 0; i < programs.length; i++) {
if (start <= programs[i].start) {
result++;
start = programs[i].end;
}
}
return result;
}
}
4.介绍递归
1)求n!问题
代码:
public class Code_01_Factorial {
public static long getFactorial1(int n) {
if (n == 1) {
return 1L;
}
return (long) n * getFactorial1(n - 1);
}
public static long getFactorial2(int n) {
long result = 1L;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
public static void main(String[] args) {
int n = 5;
System.out.println(getFactorial1(n));
System.out.println(getFactorial2(n));
}
}
2)汉诺塔问题
- 1~n-1从from挪到help上去
- n从from挪到to上去
- 1~n-1从help挪到to上面去
代码:
public static void process(int N,String from,String to,String help) {
if(N==1) {
System.out.println("Move 1 from"+from+"to "+to);
}else {
process(N-1,from,help,to);
System.out.println("Move "+N+"from "+from+" to"+to);
process(N-1,help,to,from);
}
}
3)打印一个字符串的全部子序列问题,包括空字符串
每个位置都是要还是不要
public static void printAllSub(char[] str,int i,String res) {
if(i==str.length) {
System.out.println(res);
return;
}
printAllSub(str,i+1,res);
printAllSub(str,i+1,res+String.valueOf(str[i]));
}
4)母牛问题
母牛每年生一只母牛,新生的母牛成长三年后也能每年生一只母牛,假设都不会死。求N年后,母牛的数量。
- 公式为:T(N)=T(N-1)+T(N-3)
public static int cowNumber1(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
return cowNumber1(n - 1) + cowNumber1(n - 3);
}
5.动态规划
1)二维数组问题
给你一个二维数组,二维数组中每个数都是正数,要求从右下角走到左上角,每一步只能向左或者向上。沿途经过的数字要累加起来。返回最小的路径和。
求解过程:
从[i,j]出发,走向左上角,要么向上走,要么向左走。分为3种情况。情况1:走到了最左边还没有到最上边。情况2:走到了最上边,还没有走到最左边。情况3:既可以往左走,也可以向上走。
- 递归代码:
public static int minPath1(int[][] matrix) {
return process1(matrix, matrix.length - 1, matrix[0].length - 1);
}
public static int process1(int[][] matrix, int i, int j) {
int res = matrix[i][j];
if (i == 0 && j == 0) {//到达左上角
return res;
}
if (i == 0 && j != 0) {//到达最左边,还没有达到最上边
return res + process1(matrix, i, j - 1);
}
if (i != 0 && j == 0) {//达到最上面,还没达到最左边
return res + process1(matrix, i - 1, j);
}
return res + Math.min(process1(matrix, i, j - 1), process1(matrix, i - 1, j));//普遍情况,既可以向上走,也可以向左走,找最小的
}
- 递归改动态规划:
递归改动态规划条件(无后效性问题):
当递归有重复状态,并且与到达路径状态没有关系
暴力递归改动态规划:
public static int minPath2(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) {
return 0;
}
int row = m.length;
int col = m[0].length;
int[][] dp = new int[row][col];
dp[0][0] = m[0][0];
for (int i = 1; i < row; i++) {
dp[i][0] = dp[i - 1][0] + m[i][0];
}
for (int j = 1; j < col; j++) {
dp[0][j] = dp[0][j - 1] + m[0][j];
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + m[i][j];
}
}
return dp[row - 1][col - 1];
}
2)给定一个数组arr,和一个整数aim。如果可以任意选择arr中的数字,能不能累加得到aim,返回true或者false。
递归方法还是要还是不要的问题
public static boolean isSum(int[] arr,int i,int sum,int aim) {
if(i==arr.length) {
return sum==aim;
}
return isSum(arr,i+1,sum,aim)||isSum(arr,i+1,sum+arr[i],aim);
}
暴力递归改动态规划: