经典笔试算法题之打小怪兽

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;

/**
 * @author liusandao
 * @description
 *              有N只怪兽,每只怪兽有血量a[i],你有M支箭,每支箭可以造成b[i]点伤害,
 *              会消耗c[i]点能量。你要用箭杀死某只怪兽,该箭的伤害必须大于等于怪兽的
 *              血量,打一只怪兽只能用一支箭,每支箭也只能用一次。求,杀死所有怪兽的
 *              最小能量。如果无法杀死所有怪兽,则输出“NO”
 *
 *              第一行T,表示有T组样例
 *              每组样例第一行N,M
 *              每组样例第二行N个数,表示N个怪兽的血量
 *              每组样例第三行M个数,表示每支箭的伤害
 *              每组样例第四行M个数,表示每支箭的消耗
 *
 *              例子
 *              1
 *              3 3
 *              1 2 3
 *              2 3 4
 *              1 2 3
 *
 *              输出:6
 *
 *
 *
 * @date 2020-4-1 18:20
 */
public class Main {
    public static class Arrow{
        public int damage = 0;
        public int cost = 0;
        public Arrow() {
        }
        public Arrow(int damage, int cost) {
            this.damage = damage;
            this.cost = cost;
        }
    }
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int T;
        T = sc.nextInt();
        for (int t = 0; t < T; t++) {
            int N,M;
            N = sc.nextInt();
            M = sc.nextInt();
            int[] a = new int[N];
            Arrow[] arrows = new Arrow[M];
            for (int i = 0; i < M; i++) {
                arrows[i] = new Arrow();
            }
            for (int i = 0; i < N; i++) {
                a[i] = sc.nextInt();   //HP
            }
            for (int i = 0; i < M; i++) {
                arrows[i].damage = sc.nextInt();  //damage
            }
            for (int i = 0; i < M; i++) {
                arrows[i].cost = sc.nextInt();  //cost
            }
            /*
            血量从高到低杀,每次取伤害高于血量的箭矢中,耗费最低的
             */
            PriorityQueue<Arrow> p = new PriorityQueue<>(new Comparator<Arrow>() {
                @Override
                public int compare(Arrow o1, Arrow o2) {
                    return o1.cost - o2.cost;
                }
            });
            if (M < N){
                System.out.println("No");
            }
            else {
                int ans = 0;
                boolean can = true;
                Arrays.sort(a);
                Arrays.sort(arrows, new Comparator<Arrow>() {
                    @Override
                    public int compare(Arrow o1, Arrow o2) {
                        return o1.damage - o2.damage;
                    }
                });
                int j = M - 1;
                for (int i = N - 1; i >= 0; i--) {
                    while(j >= 0 && arrows[j].damage >= a[i]){
                        p.offer(arrows[j]);
                        j--;
                    }
                    //把伤害超过的弓箭加进去
                    if (p.size() == 0){
                        System.out.println("No");
                        can = false;
                        break;
                    }
                    else {
                        Arrow ar = p.poll();
                        ans += ar.cost;
                    }
                }
                if (can){
                    System.out.println(ans);
                }
            }
        }
    }
}

很多人看到的第一反应是动态规划,感觉和背包问题很像,但是这题其实有更简便的方法,就是贪心。

将怪物按血量从高到低排序,把箭支按伤害从高到低排序,从血量最高的怪物开始遍历,每次把超过当前怪物血量的箭支加入到我们维护的一个最小堆中(代码中我写的堆是Arrow的堆,其实好像可以直接用Integer堆存耗费),堆中的Arrow对象,按照箭支消耗排序。这样,我们每次只需取出当前可用箭支中,消耗最小的那一根即可,这里利用了贪心的思想,不去考虑该箭支用做杀后面的怪物是否更优,因为箭支对于每支怪物消耗的体力一样,而怪物血量从高到底排序,则遍历到后面,箭支数量一定是更多的(因为约束缩小)。这样的话,代码的时间复杂度就是排序的复杂度加上核心遍历的复杂度O(max(nlogn,mlogn)),又因为M一定大于N,因为箭支数量小于N则直接输出No。所以时间复杂度位O(mlogn)。

我个人感觉这题其实还是比较简单的。但是似乎很多人钻进了动态规划的死胡同里,出不来了。这题能否用动态规划求解我不敢妄下定论,但我思考的时候发现这题要用动态规划求解,其状态转移方程十分麻烦。我们都知道0-1背包问题状态转换方程,是不需要考虑之前状态装了哪些物品的,我们只需要知道背包剩余容量,以及装入当前该物品,是否比不装入好(跟两个值比较)。但是这题不仅要记录状态,还要记录在当前最优状态下,已使用过哪些箭矢(因为箭矢不能重复使用),这是此题与0-1背包问题不同的地方。

如果有想到用动态规划解题的好思路,可以评论,我只是个算法小菜鸡。欢迎指教。

package exam.written.alibaba;

import java.util.Arrays;import java.util.Comparator;import java.util.PriorityQueue;import java.util.Scanner;

/** * @author liusandao * @description Alibaba42 *              有N只怪兽,每只怪兽有血量a[i],你有M支箭,每支箭可以造成b[i]点伤害, *              会消耗c[i]点能量。你要用箭杀死某只怪兽,该箭的伤害必须大于等于怪兽的 *              血量,打一只怪兽只能用一支箭,每支箭也只能用一次。求,杀死所有怪兽的 *              最小能量。如果无法杀死所有怪兽,则输出“NO” * *              第一行T,表示有T组样例 *              每组样例第一行N,M *              每组样例第二行N个数,表示N个怪兽的血量 *              每组样例第三行M个数,表示每支箭的伤害 *              每组样例第四行M个数,表示每支箭的消耗 * *              例子 *              1 *              3 3 *              1 2 3 *              2 3 4 *              1 2 3 * *              输出:6 * * * * @date 2020-4-1 18:20 */

public class Alibaba42 {

    public static class Arrow{        public int damage = 0;        public int cost = 0;

        public Arrow() {        }

        public Arrow(int damage, int cost) {            this.damage = damage;            this.cost = cost;        }

    }

    public static void main(String[] args){        Scanner sc = new Scanner(System.in);        int T;        T = sc.nextInt();        for (int t = 0; t < T; t++) {

            int N,M;            N = sc.nextInt();            M = sc.nextInt();

            int[] a = new int[N];            Arrow[] arrows = new Arrow[M];            for (int i = 0; i < M; i++) {                arrows[i] = new Arrow();            }

            for (int i = 0; i < N; i++) {                a[i] = sc.nextInt();   //HP}            for (int i = 0; i < M; i++) {                arrows[i].damage = sc.nextInt();  //damage}            for (int i = 0; i < M; i++) {                arrows[i].cost = sc.nextInt();  //cost}

            /*            血量从高到低杀,每次取伤害高于血量的箭矢中,耗费最低的             */

PriorityQueue<Arrow> p = new PriorityQueue<>(new Comparator<Arrow>() {                @Overridepublic int compare(Arrow o1, Arrow o2) {                    return o1.cost - o2.cost;                }            });

            if (M < N){                System.out.println("No");            }            else {                int ans = 0;                boolean can = true;                Arrays.sort(a);                Arrays.sort(arrows, new Comparator<Arrow>() {                    @Overridepublic int compare(Arrow o1, Arrow o2) {                        return o1.damage - o2.damage;                    }                });                int j = M - 1;                for (int i = N - 1; i >= 0; i--) {                    while(j >= 0 && arrows[j].damage >= a[i]){                        p.offer(arrows[j]);                        j--;                    }                    //把伤害超过的弓箭加进去if (p.size() == 0){                        System.out.println("No");                        can = false;                        break;                    }                    else {                        Arrow ar = p.poll();                        ans += ar.cost;                    }                }                if (can){                    System.out.println(ans);                }            }        }

    }

}

原文地址:https://www.cnblogs.com/liusandao/p/12623917.html

时间: 2024-08-03 19:25:13

经典笔试算法题之打小怪兽的相关文章

笔试算法题(09):查找指定和值的两个数 &amp; 构造BST镜像树

出题:输入一个已经升序排序的数组和一个数字:要求在数组中查找两个数,这两个数的和正好等于输入的那个数字,输出任意一对数字就可以,要求时间复杂度是O(n): 分析:对于升序排序的数组{-i-j-k-m--},只有可能是i+m=j+k(j和k可能是同一个数),所以可以从两边往中间收缩而忽视其他交叉相加的情况: 解题: 1 void FindSumFactor(int *array, int length, int sum) { 2 int left=0, right=length-1; 3 whil

笔试算法题(08):输出倒数第K个节点

出题:输入一个单向链表,要求输出链表中倒数第K个节点 分析:利用等差指针,指针A先行K步,然后指针B从链表头与A同步前进,当A到达链表尾时B指向的节点就是倒数第K个节点: 解题: 1 struct Node { 2 int v; 3 Node *next; 4 }; 5 Node* FindLastKth(Node *head, int k) { 6 if(head==NULL) { 7 printf("\nhead is NULL\n"); 8 exit(0); 9 } 10 Nod

笔试算法题(07):还原后序遍历数组 &amp; 半翻转英文句段

出题:输入一个整数数组,判断该数组是否符合一个二元查找树的后序遍历(给定整数数组,判定其是否满足某二元查找树的后序遍历): 分析:利用后序遍历对应到二元查找树的性质(序列最后一个元素必定是根节点,从左向右第一个比根节点大的元素开始直到根节点之前的所有元素必定在右子树,之前的所有元素必定在左子树): 解题: 1 bool PostOrderCheck(int *array, int i, int j) { 2 /** 3 * 如快速排序一样,解决小子文件 4 * */ 5 if(j-i+1 ==

笔试算法题(50):简介 - 广度优先 &amp; 深度优先 &amp; 最小生成树算法

广度优先搜索&深度优先搜索(Breadth First Search & Depth First Search) BFS优缺点: 同一层的所有节点都会加入队列,所以耗用大量空间: 仅能非递归实现: 相比DFS较快,空间换时间: 适合广度大的图: 空间复杂度:邻接矩阵O(N^2):邻接表O(N+E): 时间复杂度:O(V+E): DFS优缺点: 无论是系统栈还是用户栈保存的节点数都只是树的深度,所以空间耗用小: 有递归和非递归实现: 由于有大量栈操作(特别是递归实现时候的系统调用),执行速度

笔试算法题(51):简介 - 红黑树(RedBlack Tree)

红黑树(Red-Black Tree) 红黑树是一种BST,但是每个节点上增加一个存储位表示该节点的颜色(R或者B):通过对任何一条从root到leaf的路径上节点着色方式的显示,红黑树确保所有路径的差值不会超过一倍,最终使得BST接近平衡: 红黑树内每个节点包含五个属性:color, key, left, right和p,p表示指向父亲节点的指针:一棵BST需要同时满足下述五个性质才能称作红黑树: 每个节点只能是红色或者黑色节点中的一种: 根节点必须是黑色: 每个叶节点(NULL)必须是黑色:

笔试算法题(24):找出出现次数超过一半的元素 &amp; 二叉树最近公共父节点

出题:数组中有一个数字出现的次数超过了数组长度的一半,请找出这个数字: 分析: 解法1:首先对数组进行排序,时间复杂度为O(NlogN),由于有一个数字出现次数超过了数组的一半,所以如果二分数组的话,划分元素肯定就是这个数字: 解法2:首先创建1/2数组大小的Hash Table(哈希表可以替代排序时间,由于一个数字出现超过了数组的一半,所以不同元素个数肯定不大于数组的一半),空间复杂度O(N),顺序扫描映射数 组元素到Hash Table中并计数,最后顺序扫描Hash Table,计数超过数组

笔试算法题(06):最大连续子数组和 &amp; 二叉树路径和值

出题:预先输入一个整型数组,数组中有正数也有负数:数组中连续一个或者多个整数组成一个子数组,每个子数组有一个和:求所有子数组中和的最大值,要求时间复杂度O(n): 分析: 时间复杂度为线性表明只允许一遍扫描,当然如果最终的最大值为0表明所有元素都是负数,可以用线性时间O(N)查找最大的元素.具体算法策略请见代码和注释: 子数组的起始元素肯定是非负数,如果添加的元素为正数则记录最大和值并且继续添加:如果添加的元素为负数,则判断新的和是否大于0,如果小于0则以下一个元素作为起始元素重新开始,如果大于

笔试算法题(47):简介 - B树 &amp; B+树 &amp; B*树

B树(B-Tree) 1970年由R. Bayer和E. Mccreight提出的一种适用于外查找的树,一种由BST推广到多叉查找的平衡查找树,由于磁盘的操作速度远小于存储器的读写速度,所以要求在尽量少 的操作次数内完成CPU分配的任务,B树就按照此原则设计,B树与红黑树的主要区别在于B树节点可以有超过2个子女,从而大大降低树的高度以减少查询时 间: 一棵M阶B树(Balanced Tree of Order M)是一棵平衡的M路搜索树,满足性质: 根节点至少有两个子女: 除根节点和叶子节点外的

笔试算法题(42):线段树(区间树,Interval Tree)

议题:线段树(Interval Tree) 分析: 线段树是一种二叉搜索树,将一个大区间划分成单元区间,每个单元区间对应一个叶子节点:内部节点对应部分区间,如对于一个内部节点[a, b]而言,其左子节点表示的区间为[a, (a+b)/2],其右子节点表示的区间为[1+(a+b)/2, b]: 对于区间长度为N的线段树,由于其单元节点都是[a, a]的叶子节点,所以其叶子节点数为N,并且整棵树为平衡二叉树,所以总节点数为2N-1,树的深度为log(N)+1: 插入操作:将一条线段[a, b]插入到