48天笔试训练错题——day36

目录

选择题

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

编程题

1. Pre-Post

2. Rational Arithmetic(有理数运算)


选择题

1.

耗时的操作由一个线程单独运行,那这个耗时的操作就可以和其他操作同时执行了。

进程是资源分配的基本单位,一个进程包含多个线程,线程使用的内存是属于进程的,所以对于是否耗内存来说,都是线程占用了所在进程的一部分内存,并不存在提高内存利用率这一说法。

一个进程包含多个线程,有了多个线程之后,就可以同时利用这些 CPU。

多个客户端创建多个线程,当多个客户端同时请求时,多个线程也能第一时间响应,能提高响应速度。

2.

文件是按块存储的,如果块大小设置的大一点,读取的时候一次性就读取的更多,磁盘吞吐量提升,但是文件可能不能占满整个块,导致利用率下降。

3.

并发性:指多个进程实体同存于内存中,且在一段时间内同时运行。并发性是进程的重要特征,同时也成为操作系统的重要特征。

动态性:进程的实质是进程实体的一次执行过程,动态性是进程最基本的特征。

独立性:进程实体是一个独立运行,独立分配资源,和独立接受调度的基本单位。

异步性:指进程按各自独立的,不可预知的速度向前推进,或者说实体按异步方式运行。

4.

等待队列:用于使线程等待某一特定的事件发生而无需频繁的轮询,进程在等待期间睡眠,在某件事发生时由内核唤醒。

作业后备队列:操作系统首先从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。然后再将新创建的进程插入就绪队列,准备执行。

5.

Cahce 是缓存。

Cache 出现的原因就是为了解决 CPU 与主存之间的速度匹配问题,CPU 速度 > Cache 速度 > 主存速度。

程序访问的局部性原理是一个程序在运行的某一时段,它访问的代码或数据大部分是集中在某一块区域的。(感觉跟代码要高内聚差不多)

Cache 是 CPU 缓存,Cache 的地址与主存的地址是两码事,不统一编址,也没有从属关系。

Cache 是由硬件实现。

6.

页面的频繁更换,导致整个系统效率急剧下降,这个现象称为内存抖动。

抖动一般是内存分配算法不好,内存太小或者程序的算法不佳,引起的页面频繁从内存调入调出。

可以减少进程来防止抖动。

7.

响应时间是任务到达和任务开始被处理(响应)之间的时间。

周转时间是到达和处理结束之间的时间。

实时调度算法:指系统能够在限定的响应时间内提供所需水平的服务,如果系统的时间约束条件得不到满足,将会发生系统错误。

短任务优先算法:执行时间短的任务优先执行。

时间片轮转算法:由系统调度就绪队列中的进程,并为每个进程分配一段时间片,如果时间片结束但进程还没完成,则会回到就绪队列末尾,等待下一次调度。

先来先服务算法:按照顺序执行。

以上调度算法,时间片轮转算法是抢占式算法。

采用短任务优先算法的平均等待时间,平均周转时间最短。

8.

9.

页的划分是操作系统做的。

死锁必须满足四个条件:①互斥 ②循环等待 ③不剥夺 ④请求保持    所有进程都挂起不一定形成了循环等待的关系,有些进程定时器结束后就会自动唤醒。

系统调用就是调用库函数,可以被优先级更高的线程中断。时间轮转调度算法,时间片内线程未完成,则线程会被中断,然后返回到就绪队列末尾等待下一次的系统调度。

有静态优先级调度(优先级是固定不变的)。

10.

用缓存可以提高数据的访问速度。

计算密集型:比如使用加减乘除等操作的数量很多。

I/O 密集型:比如读写数据这种 I/O 操作的数量很多。

因为 IO 密集型的一个操作(一个IO)会让线程进入等待,就算用多线程了也依然等待,所以并不能用来程序调优。如果是 IO 密集型的多个操作应用(多个IO),那就可以用多线程。

数据库连接池可以反复使用,而数据库必须得先建立连接然后再使用最后还得关闭连接,其性能没有数据库连接池好。

递归优化方案:① 迭代   ② 尾递归

一个远程调用只能发送一次文件,多个远程调用就要多次发送文件,将多个远程调用合并成一个可以提高速率,减少等待时间。

如果大家都有冗余数据,如果能共享的话,那就能提高访问冗余数据的速度。

编程题

1. Pre-Post

我们可以下个有道翻译,不会的单词就搜一下。

译文:

我们都非常熟悉二叉树的前序遍历,中序遍历和后序遍历,有一个在数据结构类的普遍的问题,就是给你中序遍历和后序遍历,来去找二叉树的前序遍历。或者,给你中序遍历和前序遍历,让你找后序遍历。然而,当给你前序遍历和后序遍历时,你不能确定二叉树的中序遍历。看看下面的四个二叉树:

这些二叉树都有相同的前序遍历和后序遍历,这种现象不局限于二叉树,可以把它推广到 m 叉树。

输入描述:有多组输入,每个实例由一行 m s1 s2 这个格式,m 就是 m 叉树,s1 是前序遍历结果,s2 是后序遍历结果,遍历结果全都是小写字母,1 <=  m <= 20,s1 的长度和 s2 的长度在 1 到 26 之间。如果 s1 长度 = s2 长度 = k,字母表的前 k 个字符会被用于字符串中,输入 0 的话表示结束。

输出描述:对于每个实例,你应该输出一行,一个数字包含树的可能个数(根据前序和后序的结果来确认),数的范围要在 int 范围之内,对于每个实例,可以保证至少有一个树具有给定的前序遍历和后序遍历。

总结:用递归,先分离子树:去前序遍历中找子树的根节点,然后再去后序遍历中找根节点所在的位置,然后计算出子树的总节点个数 count,然后返回前序遍历让下标往后走 count ,继续重复以上过程,直到越界。此时就能得知根节点一共有多少个孩子,并且能知道子树的前序遍历后后序遍历,然后再计算以 m 为底,挑选孩子个数的组合,然后遍历子树集合。继续去寻找每个子树的可能性,并将子树可能性的组合相乘计算出总的可能性,最后返回总可能性即可。

代码实现:

import java.util.*;

class SubTree {
    String prev;
    String post;

    public SubTree(String prev, String post) {
        this.prev = prev;
        this.post = post;
    }
}


// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {

    // 计算 n 的阶乘
    public static long fac(int n) {
        int ans = 1;
        for (int i = 1; i <= n; i++) {
            ans *= i;
        }
        return ans;
    }


    // 计算组合
    public static long calcCom(int m, int n) {

        // 组合就是用 m * m-1 * m-2 *... *(m-n + 1)
        // 在用上面结果除以 n 的阶乘
        long ans = 1;
        for (int i = m; i >= m - n + 1; i--) {
            ans *= i;
        }

        return ans / fac(n);
    }


    
    // 分离子树
    public static List<SubTree> subTreeFunc(String prev, String post) {
        // 首先初始化一个 list 来存储返回的子树对象
        List<SubTree> subTreeList = new ArrayList<>();
        // 前序遍历:根节点 子树1  子树2 ... 子树n
        // 后序遍历:子树1  子树2  子树3 ... 根节点

        // 在前序遍历中,子树的开始节点下标
        int prevFirstIndex = 1;
        // 在后序遍历中,子树的开始节点下标
        int postFirstIndex = 0;
        // 然后从前序遍历中找子树的根节点
        while (prevFirstIndex < prev.length()) {
            char root = prev.charAt(prevFirstIndex);
            // 从后序遍历中找该子树的根节点,并计算出该子树的节点个数
            int postSubTreeRootIndex = post.indexOf(root);
            // 子树节点总个数
            int subTreeCount = postSubTreeRootIndex - postFirstIndex + 1;
            // 截取子树的前序遍历结果,以及子树的后序遍历结果
            String subTreePrev = prev.substring(prevFirstIndex,
                    prevFirstIndex + subTreeCount);
            String subTreePost = post.substring(postFirstIndex,
                    postFirstIndex + subTreeCount);
            // 将子树的前序遍历和后序遍历存到对象中, 并放入 list 里
            SubTree subTree = new SubTree(subTreePrev, subTreePost);
            subTreeList.add(subTree);

            // 继续寻找下一个子树
            prevFirstIndex = prevFirstIndex + subTreeCount;
            postFirstIndex = postFirstIndex + subTreeCount;
        }

        return subTreeList;

    }

    // 根据先序遍历和后序遍历,计算所有树的可能性
    public static long calcTreePossiable(int m, String prev, String post) {
        // 首先判断,如果节点只有一个,或者没有节点,那可能性为 1
        if (prev.isEmpty() || prev.length() == 1) {
            return 1;
        }
        // 树非空时
        // 首先要分离子树, 可以构建个对象来存储子树的前后遍历结果
        List<SubTree> subTreeList = subTreeFunc(prev, post);
        // 计算子树的组合
        long result = calcCom(m, subTreeList.size());
        for (SubTree e : subTreeList) {
            // 计算每棵子树的可能性
            result *= calcTreePossiable(m, e.prev, e.post);
        }
        // 然后返回可能性
        return result;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int m = in.nextInt();
            if (m == 0) {
                // 如果 m 为 0,说明要结束输入
                break;
            }
            String prev = in.next();
            String post = in.next();
            // 根据树的前序遍历和后序遍历,计算树的可能性
            long count = calcTreePossiable(m, prev, post);
            System.out.println(count);
        }
    }
}

2. Rational Arithmetic(有理数运算)

 译文:题目要求计算两个有理数的加减乘除。

输入描述:对于每个测试用例,会输入一行,包括两个有理数 ,以"a1/b1 a2/b2" 的形式。分子和分母都会在 long 范围内。负号一定会在分子的前面,分母确保一定不是 0。

输出描述:每个测试用例,需要输出四行,分别是两个有理数的加减乘除,每行格式是:

"num1 操作符 num2 = 结果",有理数必须为最简格式如"k a/b",k 是整数部分, a/b 是分数的最简式,如果数字为负数,则整个有理数都要被括号括起来,如果分母是 0,就得输出"Inf" 来作为结果。能保证所有的数字都在 long 范围内。

思路:这个加减乘除的实现并不难,难的是输出有理数是的化简,需要分多种情况讨论。

代码实现:

import java.util.*;

class Rational {
    // 有整数部分 + 分数部分
    // 负号,是否为 0
    public long integer;// 整数
    public long numerator;// 展示的分子
    public long denominator;// 分母
    public boolean negative;// 是否是负数
    public boolean isZero;// 如果分母是 0
    public long calcNumerator;// 计算时使用的分子

    // 解析分子    2/3
    public static long parseNumerator(String str) {
        int index = str.indexOf("/");
        return Long.parseLong(str.substring(0, index));
    }

    // 解析分母    2/3
    public static long parseDenominator(String str) {
        int index = str.indexOf("/");
        return Long.parseLong(str.substring(index + 1, str.length()));
    }

    // 初始化
    public Rational(long n, long d) {
        // 输入时,分母不可能为 0,但是经过计算后,分母可能为 0
        if (d == 0) {
            this.isZero = !isZero;
            return;
        }
        // 如果分子小于 0,那就是负数
        if (n < 0) {
            this.negative = !negative;
        }
        // 经过计算后,分母可能小于 0
        if (d < 0) {
            this.negative = !negative;
        }

        // 将 5/3 ---> 1 2/3
        this.integer = n / d;
        this.numerator = n - integer * d;
        this.denominator = Math.abs(d);
        if (this.numerator > 1 || this.numerator < -1) {
            // 找 n 和 d 的最大公约数, 化为最简式
            //   6/9  3
            long gcd = getGCD(Math.abs(this.numerator), this.denominator);
            // 如果公约数存在,就得化简
            if (gcd > 0) {
                this.denominator = this.denominator / gcd;
                this.numerator = this.numerator / gcd;
            }
        }
        // 计算时需要用到假分数
        this.calcNumerator = this.integer * this.denominator + this.numerator;

    }
    
    // 求 a 和 b 的最大公约数
    public static long getGCD(long a, long b) {
        while (a % b != 0) {
            long c = a % b;
            a = b;
            b = c;
        }
        return b;
    }

    // 加  5/3 + 1/2 = (5*2 + 1*3) / (3 * 2)
    public Rational add(Rational r2) {
        // 先通分,然后再相加
        long calcNumerator = this.calcNumerator * r2.denominator + this.denominator * r2.calcNumerator;
        long denominator = this.denominator * r2.denominator;
        return new Rational(calcNumerator, denominator);
    }

    // 减 与加同理,改个符号即可
    public Rational sub(Rational r2) {
        // 先通分,然后再相减
        long calcNumerator = this.calcNumerator * r2.denominator - this.denominator * r2.calcNumerator;
        long denominator = this.denominator * r2.denominator;
        return new Rational(calcNumerator, denominator);
    }

    // 乘  1/2 * 3/4 = 1*3 / (2*4)
    public Rational mul(Rational r2) {
        // 分子相乘,分母相乘
        return new Rational(this.calcNumerator * r2.calcNumerator, this.denominator * r2.denominator);
    }

    // 除   1/2 / 3/4 = 1/2 * 4/3
    public Rational div(Rational r2) {
        return new Rational(this.calcNumerator * r2.denominator, this.denominator * r2.calcNumerator);
    }

    public String toString() {
        StringBuilder s = new StringBuilder();
        // 如果分母为 0,则输出 Inf
        if (isZero) {
            s.append("Inf");
            return s.toString();
        }
        // 整数部分为 0 并且分子也为 0,说明整个有理数都是 0
        if (this.integer == 0 && this.numerator == 0) {
            s.append("0");
            return s.toString();
        }

        // 输出 Rational ,包括整数部分和小数部分
        // 负数要加括号
        if (this.negative) {
            s.append("(-");
        }
        // 整数部分
        if (this.integer != 0) {
            // 前面负数时已经加了负号,所以这里应该要用绝对值
            s.append(Math.abs(this.integer));
            // 整数部分和分数部分需要用空格隔开
            if (numerator != 0) {
                s.append(" ");
            }
        }

        // 分数部分  可能存在,也可能不存在
        if (this.numerator != 0) {
            // 前面负数时已经加了负号,所以这里应该要用绝对值
            s.append(Math.abs(this.numerator) + "/" + this.denominator);
        }

        // 负数要加括号
        if (this.negative) {
            s.append(")");
        }
        return s.toString();
    }
}

// 注意类名必须为 Main, 不要有任何 package xxx 信息
class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNext()) { // 注意 while 处理多个 case
            String n1 = in.next();
            String n2 = in.next();
            // 创建个对象,来表示有理数
            Rational r1 = new Rational(Rational.parseNumerator(n1), Rational.parseDenominator(n1));
            Rational r2 = new Rational(Rational.parseNumerator(n2), Rational.parseDenominator(n2));
            // 依次打印 加减乘除即可
            System.out.println(r1 + " + " + r2 + " = " + r1.add(r2));
            System.out.println(r1 + " - " + r2 + " = " + r1.sub(r2));
            System.out.println(r1 + " * " + r2 + " = " + r1.mul(r2));
            System.out.println(r1 + " / " + r2 + " = " + r1.div(r2));
        }
    }
}

  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值