【算法设计与分析(课后答案)】暴力

1. 求解一元三次方程问题

【问题描述】 有形如 ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值>=1。求三个实根,并精确到小数点后两位。

【输入描述】包含4个实数a、b、c、d。
【输出描述】从小到大的3个实根。

【输入样例】1 -5 -4 20
【输出样例】-2.00 2.00 5.00

public class Solution1 {

    /**
     *【解法一:暴力法】
     * 每次i+=0.01,然后带入方程看看是否成立
     * 值得注意的是,double运算存在精度丢失,因此方程成立条件不是正好==0,而是<0.0000001
     */
    private String[] solveRquation(double a, double b, double c, double d) {
        String[] res = new String[3];
        int index = 0;
        for (double i = -100; i <= 100; i += 0.01) {
            if (Math.abs(a * i * i * i + b * i * i + c * i + d) < 0.0000001) {
                res[index++] = String.format("%.2f", i);
            }
            if (index == 3) {
                break;
            }
        }
        return res;
    }

    /**
     *【解法二:二分法】
     * 题目中有一个极其重要的条件:两个根的绝对值之差>=1
     * 我们可以这样:每次i+=1,然后在[i, i+0.99]这个区间内二分出根,上面的条件保证了这个区间内最多1个根
     * 另外,在二分减治时,每次收缩0.01(left=mid+1, right=m)。
     */
    private String[] solveRquation2(double a, double b, double c, double d) {
        String[] res = new String[3];
        int index = 0;
        for (double i = -100; i <= 100; i++) {
            double l = i;
            double r = i + 0.99;
            if ((a * l * l * l + b * l * l + c * l + d) * (a * r * r * r + b * r * r + c * r + d) > 0) {
                continue;
            } else {
                while (l < r) {
                    double m = l + (r - l) / 2.0;
                    if ((a * l * l * l + b * l * l + c * l + d) * (a * m * m * m + b * m * m + c * m + d) > 0) {
                        l = m + 0.01;
                    } else {
                        r = m;
                    }
                }
                res[index++] = String.format("%.2f", l);
            }
            if (index == 3) {
                break;
            }
        }
        return res;
    }


    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double a = scanner.nextDouble();
        double b = scanner.nextDouble();
        double c = scanner.nextDouble();
        double d = scanner.nextDouble();
        String[] res = new Solution1().solveRquation2(a, b, c, d);
        System.out.println(Arrays.toString(res));
    }
}

 


2. 求解完数问题

【问题描述】 完数的定义:如果一个大于1的正整数的所有真因子(不包括自己的那些因子)之和等于它的本身,则称这个数是完数,比如6,28都是完数:6=1+2+3;28=1+2+4+7+14。本题的任务是判断两个正整数之间完数的个数。

【输入描述】第一行是一个正整数n,表示测试实例的个数,然后就是n个测试实例,每个实例占一行,由两个正整数num1和num2组成,(1<num1,num2<10000) 。
【输出描述】对于每组测试数据,请输出num1和num2之间(包括num1和num2)存在的完数个数。

【输入样例】2
         2 5
         5 7
【输出样例】0
         1

public class Solution2 {

    /**
     *【解法一:暴力法】
     * 根据定义直接解出此题
     * 多说一句,因子的定义是"所有可以整除这个数的数,但不包括这个数自身",即"包含1,但不包含自身"
     * 再多说一句,题目中没说mum1<=num2,但这种文字游戏实在没有必要,因此未予以处理
     */
    private int countPerfectNumber(int min, int max) {
        int cnt = 0;
        for (int n = min; n <= max; n++) {
            if (isPerfectNumber(n)) {
                cnt++;
                System.out.println(n);
            }
        }
        return cnt;
    }

    private boolean isPerfectNumber(int n) {
        int sum = 0;
        for (int i = 1; i < n; i++) {
            if (n % i == 0) {
                sum += i;
            }
        }
        return sum == n;
    }


    /**
     *【解法二:伪暴力法】
     * 审题可知输入的范围是1<num1<num2<10000,这个范围内最多也就4个完全数———6、28、496、8182
     */
    private int countPerfectNumber2(int min, int max) {
        int cnt = 0;
        if (min <= 6 && max >= 6) {
            cnt++;
        }
        if (min <= 28 && max >= 28) {
            cnt++;
        }
        if (min <= 496 && max >= 496) {
            cnt++;
        }
        if (min <= 8128 && max >= 8128) {
            cnt++;
        }
        return cnt;
    }


    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int len = scanner.nextInt();
        int[] res = new int[len];
        int index = 0;
        Solution2 solution2 = new Solution2();
        for (int i = 0; i < len; i++) {
            int min = scanner.nextInt();
            int max = scanner.nextInt();
            res[index++] = solution2.countPerfectNumber2(min, max);
        }
        for (int n : res) {
            System.out.println(n);
        }
    }
}

 


3. 求解好多鱼问题

【问题描述】 牛牛有一个鱼缸。鱼缸里面已经有n条鱼,每条鱼的大小为fishSize[i] (1 ≤ i ≤ n,均为正整数),牛牛现在想把新捕捉的鱼放入鱼缸。鱼缸内存在着大鱼吃小鱼的定律。经过观察,牛牛发现一条鱼A的大小为另外一条鱼B大小的2倍到10倍(包括2倍大小和10倍大小),鱼A会吃掉鱼B。考虑到这个,牛牛要放入的鱼就需要保证:
1、放进去的鱼是安全的,不会被其他鱼吃掉
2、这条鱼放进去也不能吃掉其他鱼
鱼缸里面已经存在的鱼已经相处了很久,不考虑他们互相捕食。现在知道新放入鱼的大小范围minSize,maxSize(考虑鱼的大小都是整数表示),牛牛想知道有多少种大小的鱼可以放入这个鱼缸。

【输入描述】输入数据包括3行。 第一行为新放入鱼的尺寸范围minSize,maxSize(1 ≤ minSize,maxSize ≤ 1000),以空格分隔。 第二行为鱼缸里面已经有鱼的数量n(1 ≤ n ≤ 50) 第三行为已经有的鱼的大小fishSize[i](1≤ fishSize[i] ≤ 1000),以空格分隔。
【输出描述】输出有多少种大小的鱼可以放入这个鱼缸。考虑鱼的大小都是整数表示。

【输入样例】1 12
         1
         1
【输出样例】3

public class Solution3 {

    /**
     *【解法一:暴力法】
     * 第一层for循环是[min,max]中每一种鱼的大小,第二层for循环是鱼缸里已有的每一条鱼,判断是否有大鱼吃小鱼的情况
     * 代码略
     */

    /**
     *【解法二:数组法】
     * 不妨开一个大小为1001的数组,记录每种大小的鱼是否合法;鱼缸里已有的每一条鱼,从吃与被吃的角度修改这个数组
     * 思路不难,代码不难,但有两个细节是难点:
     * 细节1:
     * 假设鱼缸里有一条大小为21的鱼,那么哪个范围内的鱼会被它吃呢?答案是[2.1, 10.5],如何处理取整的问题呢?
     * 下限是向上取整,上限是向下取整(2不会被吃,10会被吃),最终答案是[3, 10]
     * 细节2:
     * 向下取整直接就是java默认的地板除,但如何向上取整呢?
     * 可以用这个技巧:Math.ceil(n/10.0) == (n+9)/10
     */
    private int cntSpeciesOfFish(int[] fishes, int min, int max) {
        boolean[] illegalSize = new boolean[1001];
        for (int fish : fishes) {
            for (int i = fish * 2; i <= max && i <= fish * 10; i++) {
                illegalSize[i] = true;
            }
            for (int i = fish / 2; i >= min && i >= (fish + 9) / 10; i--) {
                illegalSize[i] = true;
            }
        }
        int cnt = 0;
        for (int i = min; i <= max; i++) {
            cnt += illegalSize[i] ? 0 : 1;
        }
        return cnt;
    }


    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int min = scanner.nextInt();
        int max = scanner.nextInt();
        int len = scanner.nextInt();
        int[] fishes = new int[len];
        for (int i = 0; i < len; i++) {
            fishes[i] = scanner.nextInt();
        }
        System.out.println(new Solution3().cntSpeciesOfFish(fishes, min, max));
    }
}

 


4. 求解推箱子问题

【问题描述】推箱子是一款风靡全球的益智小游戏,玩家需要将箱子推到仓库中的目标位置。

游戏地图用大小为 n * m 的网格 grid 表示,其中每个元素可以是墙、地板或者是箱子。

现在你将作为玩家参与游戏,按规则将箱子 ‘B’ 移动到目标位置 ‘T’ :

玩家用字符 ‘X’ 表示,只要他在地板上,就可以在网格中向上、下、左、右四个方向移动。
地板用字符 ‘.’ 表示,意味着可以自由行走。
墙用字符 ‘#’ 表示,意味着障碍物,不能通行。
箱子仅有一个,用字符 ‘*’ 表示。相应地,网格上有一个目标位置 ‘@’。
玩家需要站在箱子旁边,然后沿着箱子的方向进行移动,此时箱子会被移动到相邻的地板单元格。记作一次「推动」。
玩家无法越过箱子。

返回将箱子推到目标位置的最小推动次数,如果无法做到,请返回 -1。

在这里插入图片描述

public class Solution4{

    /**
     *【BFS+DFS】
     * 以箱子的视角进行BFS
     * 以人的视角进行DFS
     * 后者作为前者得以进行的前提
     */
    public int minPushBox(char[][] grid) {
        int m = grid.length;
        int n = grid[0].length;

        // 遍历一次,找出箱子起点/终点,人的初始位置
        int startX = -1;
        int startY = -1;
        int targetX = -1;
        int targetY = -1;
        int personX = -1;
        int personY = -1;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '*') {
                    startX = i;
                    startY = j;
                }
                if (grid[i][j] == '@') {
                    targetX = i;
                    targetY = j;
                    grid[i][j] = '.';
                }
                if (grid[i][j] == 'X') {
                    personX = i;
                    personY = j;
                    grid[i][j] = '.';
                }
            }
        }

        // 初始化队列,加入元素以启动BFS
        boolean[][][] visited = new boolean[m][n][4];
        Queue<Box> queue = new LinkedList<>();
        for (int i = 0; i < 4; i++) {
            int[] direction = directions[i];
            if (personCanReach(grid, m, n, personX, personY, startX - direction[0], startY - direction[1], new boolean[m][n])) {
                queue.add(new Box(startX, startY, i));
                visited[startX][startY][i] = true;
            }
        }

        // 以箱子的视角开始BFS
        int step = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            while (size-- > 0) {
                Box box = queue.poll();
                grid[box.x][box.y] = '*';
                personX = box.x - directions[box.from][0];
                personY = box.y - directions[box.from][1];
                if (box.x == targetX && box.y == targetY) {
                    return step;
                }
                for (int i = 0; i < 4; i++) {
                    int[] direction = directions[i];
                    int nextX = box.x + direction[0];
                    int nextY = box.y + direction[1];
                    // 人是否能绕到箱子的后面?
                    if (!personCanReach(grid, m, n, personX, personY, box.x - direction[0], box.y - direction[1], new boolean[m][n])) {
                        continue;
                    }
                    // 箱子的下个位置是否合法?
                    if (!isValid(grid, m, n, nextX, nextY)) {
                        continue;
                    }
                    // 箱子的下一个状态是不是重复了?
                    if (visited[nextX][nextY][i]) {
                        continue;
                    }
                    queue.add(new Box(nextX, nextY, i));
                    visited[nextX][nextY][i] = true;
                }
                grid[box.x][box.y] = '.';
            }
            step++;
        }
        return -1;
    }

    // 其含义是从【上】【下】【左】【右】
    private final static int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    // 静态内部类是个顶级类,可当成外部类来看
    private static class Box {
        int x;
        int y;
        int from;
        public Box(int x, int y, int from) {
            this.x = x;
            this.y = y;
            this.from = from;
        }
    }

    // 人是否可以某一位置(startX, startY)到达另一位置(targetX, targetY)
    private boolean personCanReach(char[][] grid, int m, int n, int startX, int startY, int targetX, int targetY, boolean[][] visited) {
        if (startX == targetX && startY == targetY) {
            return true;
        }
        visited[startX][startY] = true;
        for (int[] direction : directions) {
            int nextX = startX + direction[0];
            int nextY = startY + direction[1];
            if (isValid(grid, m, n, nextX, nextY) && !visited[nextX][nextY]) {
                if (personCanReach(grid, m, n, nextX, nextY, targetX, targetY, visited)) {
                    return true;
                }
            }
        }
        return false;
    }

    // 某位置是否可以踏足
    private boolean isValid(char[][] grid, int m, int n, int x, int y) {
        return x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '.';
    }



    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        List<Integer> res = new ArrayList<>();
        while (true) {
            int len1 = scanner.nextInt();
            if (len1 == 0) {
                break;
            }
            int len2 = scanner.nextInt();
            char[][] grid = new char[len1][len2];
            for (int i = 0; i < len1; i++) {
                for (int j = 0; j < len2; j++) {
                    grid[i][j] = scanner.next().charAt(0);
                }
            }
            res.add(new Solution4().minPushBox(grid));
        }
        for (int n : res) {
            System.out.println(n);
        }

    }

}

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

⭐️ 这一定是全网最优美的Java解法 >_<

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法设计与分析基础 课后答案 第二版 中英文 作者简介:莱维丁是Villanova大学计算科学系的教授。他的论文 A New Road Map of Algorithm Design Techniques:Picking Up Where the Traditional Classification Leaves Off(《算法设计技术新途径:弥补传统分类法的缺憾》)受到业内人士极高的评价。在SIGCSE会议上,作者做过多次关于算法教学的演讲。 译者简介:潘彦,计算机专业人士,国际电气电子工程师学会(IEEE)会员。 作者基于丰富的教学经验,开发了一套对算法进行分类的新方法。这套方法站在通用问题求解策略的高度,能对现有的大多数算法都能进行准确分类,从而使本书的读者能够沿着一条清晰的、一致的、连贯的思路来探索算法设计与分析这一迷人领域。本书作为第2版,相对第1版增加了新的习题,还增加了“迭代改进”一章,使得原来的分类方法更加完善。 本书十分适合作为算法设计和分析的基础教材,也适合任何有兴趣探究算法奥秘的读者使用,只要读者具备数据结构和离散数学的知识。 This file contains the exercises, hints, and solutions for Chapter 1 of the book ”Introduction to the Design and Analysis of Algorithms,” 2nd edition, by A. Levitin. The problems that might be challenging for at least some students are marked by ; those that might be difficult for a majority of students are marked by  . Exercises 1.1…………
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值