牛客网算法小结(3)

一:贪心算法

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;
        }
    }
}

三:动态规划(有重复解,无后效性)

  1. 根据可变参数的个数,确定N维表
  2. 确定可变参数的变化范围,确定表的范围
  3. 确定目标的参数,在表中标记出来
  4. 确定最终的可以确定的参数,在表中标记出来
  5. 从边界开始填表,把所有可以确定的值填出来
  6. 回到普通的递归函数,填表

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];
    }

点赞