一:贪心算法
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铜板。
输入一个数组,返回分割的最小代价。
/**
* 分金条,求最小代价和,可以使用哈弗曼树来做
*/
public class GoldBullion {
private static class Node implements Comparable<Node> {
public Node(Integer value) {
this.value = value;
}
public Integer value;
public Node parent;
public Node left;
public Node right;
public boolean isLeaf() {
return left == null && right == null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Node other = (Node) obj;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
@Override
public int compareTo(Node o) {
return this.value - o.value;
}
}
private static PriorityQueue<Node> pq = new PriorityQueue<>();
private static int sum;
//分金条的过程,就是构造哈弗曼树的过程
//当哈弗曼树构造好,根节点就是金条的总长度,叶子节点就是想分出的小金条长度
//叶子节点的路径权重和就是划分所需要的代价
public static int bull(int[] input) {
//初始化多个树
for (int i = 0; i < input.length; i++) {
pq.add(new Node(input[i]));
}
//开始创建
while (pq.size() > 1) {
Node left= pq.poll();
Node right = pq.poll();
Node parent = new Node(left.value+right.value);
parent.left = left;
parent.right = right;
pq.add(parent);
}
Node root = pq.poll();
return count(root,0);
}
//从哈弗曼树求出所有叶子节点的路径权值和
private static int count(Node node,int count){
if (node == null)
return 0;
if (node.isLeaf())
sum += count* node.value;
else
{
if (node.left != null)
count(node.left,count+1);
if (node.right != null)
count(node.right,count+1);
}
return sum;
}
public static void main(String[] args) {
int[] arr = {10,20,30};
System.out.println(bull(arr));
}
}
方法2:
public static int bull2(int[] arr){
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int i = 0; i < arr.length; i++) {
queue.add(arr[i]);
}
int count = 0;
while (queue.size() > 1)
{
int a = queue.poll();
int b = queue.poll();
int sum = a + b;
queue.add(sum);
count += sum;
}
return count;
}
2. 做项目
输入: 参数1,正数数组costs 参数2,正数数组profits 参数3,正数k 参数4,正数m
costs[i]表示i号项目的花费
profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)
k表示你不能并行、只能串行的最多做k个项目
m表示你初始的资金
说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个 项目。
输出: 你最后获得的最大钱数。
/**
* 做项目
*/
public class ProjectTest {
//封装数据项
static class Process implements Comparable{
int cost;
int profit;
public Process(int cost, int profit) {
this.cost = cost;
this.profit = profit;
}
@Override
public int compareTo(Object o) {
return this.cost - ((Process)o).cost;
}
}
//实现两个堆,代价的最小堆和收益的最大堆
//我们希望在花费最小代价的情况下获得最大收益
static class CostComparator implements Comparator<Process>{
@Override
public int compare(Process o1, Process o2) {
return o1.cost - o2.cost;
}
}
static class ProfitComparator implements Comparator<Process>{
@Override
public int compare(Process o1, Process o2) {
return o2.profit - o1.profit;
}
}
static PriorityQueue<Process> costQueue = new PriorityQueue<>(new CostComparator());
static PriorityQueue<Process> profitQueue = new PriorityQueue<>(new ProfitComparator());
public static int doProject(int[] costs,int[] profits,int k,int m){
//初始化代价堆
for (int i = 0; i < costs.length; i++) {
Process process = new Process(costs[i],profits[i]);
costQueue.add(process);
}
//在不超出工程步骤的情况下
for (int i = 0; i < k; i++) {
//找出能够接受的项目
while (!costQueue.isEmpty() && costQueue.peek().cost <= m)
profitQueue.add(costQueue.poll());
//在这些项目中找到最大收益
if (profitQueue.isEmpty())
return m;
m += profitQueue.poll().profit;
}
return m;
}
public static void main(String[] args) {
int[]c = new int[]{10,20,100};
int[]p = new int[]{11,10,200};
System.out.println(doProject(c,p,c.length,50));//71
}
}
3. 安排活动
一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目 的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数组,里面 是一个个具体的项目),你来安排宣讲的日程,要求会议室进行 的宣讲的场次最多。返回这个最多的宣讲场次。
public class ActivityTest {
//数据项
static class Activity implements Comparable{
int index;
int start;
int end;
public Activity(int index, int start, int end) {
this.index = index;
this.start = start;
this.end = end;
}
@Override
public String toString() {
return "Activity{" +
"index=" + index +
", start=" + start +
", end=" + end +
'}';
}
@Override
public int compareTo(Object o) {
return this.end - ((Activity)o).end;
}
}
private static PriorityQueue<Activity> endQueue = new PriorityQueue<>();
public static int select(int[] start,int[] end){
Activity[] activities = new Activity[start.length];
for (int i = 0; i < start.length; i++) {
Activity activity = new Activity(i,start[i],end[i]);
activities[i] = activity;
}
endQueue.addAll(Arrays.asList(activities));
//初始化场次
int count = 0;
//选出最早下课时间
while (!endQueue.isEmpty()){
Activity last = endQueue.poll();
count++;
//下一次上课时间应该比上一次上课的时间晚
while (!endQueue.isEmpty() && endQueue.peek().start < last.end)
endQueue.poll();
}
return count;
}
public static void main(String[] args) {
int start[] = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12};
int end[] = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
System.out.println(select(start,end));
}
}
二:递归
1. 求n!的结果
//计算阶乘
public static int fac(int n){
if(n <= 1)
return n;
return n*fac(n-1);
}
2. 汉诺塔问题
打印n层汉诺塔从最左边移动到最右边的全部过程
public static void move(int n,char from, char temp,char to){
//只有一个,直接移动
if(n == 1)
{
System.out.printf("move the %d :%s-->%s%n",n,from,to);
}
else
{
move(n-1,from,to,temp); //把A塔上编号1~n-1的圆盘移动到辅助盘上
System.out.printf("move the %d :%s-->%s%n",n,from,to); // 把最后一个圆盘直接移动到目标盘
move(n-1,temp,from,to); //再把1~n-1的圆盘移到目标盘回来
}
}
3. 打印字符串
打印一个字符串的全部子串
/**
* 打印字符串的所有子串
*/
public class StringTest {
public static void printAllSubString(String s){
if (s == null)
return;
int low = 0;
int high = s.length();
printAllSubString(s,low,high);
}
private static void printAllSubString(String s, int start,int end) {
if (start == s.length())
return;
if (end == s.length() + 1) {
printAllSubString(s,start + 1,start + 2);
}
else {
System.out.printf("%s ", s.substring(start,end));
printAllSubString(s, start, end + 1);
}
}
public static void main(String[] args) {
String s = "abcd";
printAllSubString(s);
}
}
打印一个字符串的全部子序列,包括空字符串(所有组合)
//全部子序列
public static void printAllSubSequence(String s){
char[] chars = s.toCharArray();
int index = 0;
printAllSubSequence(chars,“”,index);
}
private static void printAllSubSequence(char[] s,String res,int index) {
// if (index == s.length) {
// String temp = String.valueOf(s);
// System.out.printf("%s ", temp);
// return;
// }
if (index == s.length) {
//避免重复
String temp = res.trim();
if (!set.contains(temp))
{
set.add(temp);
System.out.printf("%s ", temp);
}
return;
}
//我想要当前字符
printAllSubSequence(s,res+s[index],index + 1);
//我不想要当前字符
printAllSubSequence(s,res,index + 1);
}
打印一个字符串的全部排列,要求不要出现重复的排列
//全排列,每个节点 i 有 n - i 种选择
public static void printAllPermutations(String str) {
char[] chs = str.toCharArray();
printAllPermutations(chs, 0);
}
private static void printAllPermutations(char[] chs, int i) {
//无法与最后的字符交换,打印
if (i == chs.length) {
System.out.printf("%s ",String.valueOf(chs));
}
// 用于保证每次交换的字符不存在重复的字符
HashSet<Character> set1 = new HashSet<>();
//依次与后面的字符交换
for (int j = i; j < chs.length; j++) {
//防止重复
if (!set1.contains(chs[j])){
set1.add(chs[j]);
//我想要第j个字符
swap(chs, i, j);
printAllPermutations(chs, i + 1);
//我不想要第j个字符
swap(chs, i, j);
}
// //我想要第j个字符
// swap(chs, i, j);
// printAllPermutations(chs, i + 1);
// //我不想要第j个字符
// swap(chs, i, j);
}
}
4. 母牛问题
母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只 母牛,假设不会死。求N年后,母牛的数量。
public int noDeath(int i){
if (i <= 0)
return 0;
//因为一头牛需要3年才能生,所以前3年都是不生的,有几头牛就只有几头牛,他们不能再生了
else if (i <= 3)
return i;
//如果超过3年,那么可以先看看上一年剩下的牛,再加上除了近3年生不了的牛,3年前的牛他们还能再生
return noDeath(i-1) + noDeath(i-3);
}
如果每只母牛只能活10年,求N年后,母牛的数量
public static int death(int i,int year){
if (i <= 0)
return 0;
//因为一头牛需要3年才能生,所以前3年都是不生的,有几头牛就只有几头牛,他们不能再生了
else if (i <= 3)
return i;
//如果还未超过死亡期限,则按照上一方法处理
else if (i <= year)
return noDeath(i);
//如果到达死亡期限,可以先看看没死的时候应该有多少牛,再减去死掉的牛个数
return death(i-1,year) + death(i-3,year) - death(i - year,year);
}
5. 给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能 使用递归函数。如何实现?
/**
* 逆序栈
*/
public class ReverseStack {
//这个函数递归栈用于存储最后一个元素
public static void reverse(Stack<Integer> stack){
if (stack.isEmpty())
return;
//从栈底到栈顶出栈-->进入辅助栈
int last = getLast(stack);
reverse(stack);
//从栈顶到栈底入栈-->从辅助栈出来
stack.push(last);
}
//这个函数递归栈用于存储其他 元素
//获取栈底
public static int getLast(Stack<Integer> stack){
int res = stack.pop();
//只剩一个,直接返回
if (stack.isEmpty())
return res;
else {
//从栈顶到栈底出栈-->进入辅助栈
int last = getLast(stack);
//把除了最后一个的其他入栈-->离开辅助栈
stack.push(res);
return last;
}
}
}
三:动态规划(有重复解,无后效性)
- 根据可变参数的个数,确定N维表
- 确定可变参数的变化范围,确定表的范围
- 确定目标的参数,在表中标记出来
- 确定最终的可以确定的参数,在表中标记出来
- 从边界开始填表,把所有可以确定的值填出来
- 回到普通的递归函数,填表
1. 最小路径和
给你一个二维数组,二维数组中的每个数都是正数,要求从左上 角走到右下角,每一步只能向右或者向下。沿途经过的数字要累 加起来。返回最小的路径和。
递归的做法
public static int min(int[][] matrix){
return min(matrix,0,0,matrix[0].length - 1,matrix.length - 1);
}
public static int min(int[][] matrix,int x1,int y1,int x2,int y2){
//走到最下面,则向右走
if (y1 > y2)
return min(matrix, x1 + 1, y2, x2, y2);
//走到最右边,则向下走
if (x1 > x2)
return min(matrix, x2, y1 + 1, x2, y2);
//走到终点,返回
if (x1 == x2 && y1 == y2)
return matrix[y2][x2];
//每个点有两个选择:向右尝试,向下尝试
//再取最小值
int temp1 = matrix[y1][x1] + min(matrix, x1 + 1, y1, x2, y2);
int temp2 = matrix[y1][x1] + min(matrix, x1, y1 + 1, x2, y2);
return Math.min(temp1,temp2);
}
动态规划的做法:
//动态规划
public static int minDP(int[][] matrix){
int row = matrix.length - 1;
int col = matrix[0].length - 1;
//缓存表
int[][] dp = new int[row+1][col+1];
//边界值
dp[row][col] = matrix[row][col];
//填充最后一列
for (int i = row - 1; i >= 0; i--) {
dp[i][col] = dp[i + 1][col] + matrix[i][col];
}
//填充最后一行
for (int i = col - 1; i >= 0; i--) {
dp[row][i] = dp[row][i + 1] + matrix[row][i];
}
//填充表格
for (int i = row - 1; i >= 0; i--) {
for (int j = col - 1; j >= 0; j--) {
dp[i][j] = Math.min(dp[i + 1][j],dp[i][j + 1]) + matrix[i][j];
}
}
//目标值
return dp[0][0];
}
2. 数组的累加
给定一个数组arr,返回所有子数组的累加和中,最大的累加和
//递归
public static int max2(int[] arr){
if (arr.length < 1)
return 0;
return max2(arr,0,1);
}
private static int max2(int[] arr, int start,int end) {
if (start == arr.length){
return 0;
}
if (end == arr.length + 1)
return max2(arr,start + 1,start + 2);
int sum = 0;
for (int i = start; i < end; i++) {
sum += arr[i];
}
return Math.max(sum,max2(arr,start,end + 1));
}
//迭代
public static int maxSubArray(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int cur = 0;
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
cur += arr[i];
max = Math.max(cur, max);
if (cur < 0) {
cur = 0;
}
}
return max;
}
//动态规划
public static int max2(int[] arr){
int len = arr.length - 1;
//变化的参数只有一个——索引,建立一维数组
int[] dp = new int[len + 1];
//已知最后一个元素
dp[len] = arr[len];
//开始填表,把相对较大和的数填入
for (int i = len - 1; i >= 0; i--)
dp[i] = Math.max(arr[i] + dp[i + 1],dp[i + 1]);
//目标是第0个元素
return dp[0];
}
给你一个整数aim,能不能累加得到aim,返回true或者false
//递归
public static boolean isSum(int[] arr,int aim){
return isSum(arr,0,0,aim);
}
public static boolean isSum(int[] arr,int index,int sum,int aim){
//从头开始迭代,直接到数组尾部
if (index == arr.length)
return sum == aim;
//选择要当前的数
boolean temp1 = isSum(arr, index + 1, sum + arr[index], aim);
//选择不要当前的数
boolean temp2 = isSum(arr, index + 1, sum, aim);
return temp1 || temp2;
}
//动态规划
private static boolean isSumDP(int[] arr,int aim){
int len = arr.length;
int sum = 0;
for (int i = 0; i < len; i++) {
sum += arr[i];
}
if (sum < aim)
return false;
//根据数组的长度和最大和,建立缓存表
boolean[][] dp = new boolean[len + 1][sum + 1];
//最后一行是确定值,先填充
for (int i = 0; i <= sum; i++) {
dp[len][i] = i == aim;
}
//从最后一行向上,开始填充普通值
for (int i = len - 1; i >= 0; i--) {
for (int j = sum; j >= 0; j--) {
//可以选择当前值或者不选择当前值
if (arr[i] + j <= sum)
dp[i][j] = dp[i + 1][j] || dp[i + 1][arr[i] + j];
//无法选择当前值,因为他之前已经被选了
else
dp[i][j] = dp[i + 1][j];
}
}
return dp[0][0];
}
有负数的情况:
private static boolean isSumDP1(int[] arr,int aim){
int len = arr.length;
int min = 0,max = 0;
//统计最小值与最大值
for (int i = 0; i < len; i++) {
if (arr[i] >= 0)
max += arr[i];
else
min += arr[i];
}
if (max < aim || min > aim)
return false;
//根据数组的长度和最大和,建立缓存表
boolean[][] dp = new boolean[len + 1][Math.abs(min) + max + 1];
//最后一行是确定值,先填充
for (int i = 0,j = min; i <= Math.abs(min) + max; i++,j++) {
dp[len][i] = j == aim;
}
//从最后一行向上,开始填充普通值
for (int i = len - 1; i >= 0; i--) {
for (int j = Math.abs(min) + max; j >= 0; j--) {
//可以选择当前值或者不选择当前值
if (arr[i] + j <= Math.abs(min) + max && arr[i] + j >= 0)
dp[i][j] = dp[i + 1][j] || dp[i + 1][arr[i] + j];
//无法选择当前值,因为他之前已经被选了
else
dp[i][j] = dp[i + 1][j];
}
}
//目标是第“(0,0)”个,也就是偏移min个负数后的位置
return dp[0][Math.abs(min)];
}
3. 揹包问题
给定两个数组w和v,两个数组长度相等,w[i]表示第i件商品的 重量,v[i]表示第i件商品的价值。 再给定一个整数bag,要求你挑选商品的重量加起来一定不能超 过bag,返回满足这个条件下,你能获得的最大价值。
//递归
public static int maxValue(int[] w,int[] v,int bag){
return maxValue(w, v, bag,0,0,0);
}
public static int maxValue(int[] w,int[] v,int bag,int index,int weight,int value){
//如果当前重量太重了,或者没有物品了,就不放入,返回当前价值
if (index == w.length || weight + w[index] > bag)
return value;
//选择要当前的数
int yes = maxValue(w, v, bag, index+1,weight + w[index],value + v[index]);
//选择不要当前的数
int no = maxValue(w, v, bag, index+1,weight,value);
//获得最大的价值
return Math.max(yes,no);
}
//动态规划
public static int maxValueDP(int[] w, int[] v, int bag) {
int len = w.length;
//参数变化的有重量和物品的个数。以此建立表,并记录价值
int[][] dp = new int[len + 1][bag + 1];
//最后一
for (int i = 0; i < len; i++) {
dp[i][bag] = v[i];
}
for (int i = len - 1; i >= 0; i--) {
for (int j = bag; j >= 0; j--) {
//可以选择当前值或者不选择当前值
if (w[i] + j <= bag)
dp[i][j] = Math.max(dp[i + 1][j], v[i] + dp[i + 1][w[i] + j]);
else
dp[i][j] = dp[i + 1][j];
}
}
for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[0].length; j++) {
System.out.print(dp[i][j] + "\t");
}
System.out.println();
}
//目标是第“(0,0)”个,也就是偏移min个负数后的位置
return dp[0][0];
}