编程之旅-Day17

目录

Day17-学习内容:

1.剑指Offer

面试题27:二叉树镜像

面试题49:丑数

 2.Leetcode

例1:没有重复字符的最长子串长度

例2:颠倒整数

3.2018年校招编程题

例1:最大点的集合

例2:翻转数列

4.2017年阿里巴巴秋招笔试题

 


1.剑指Offer

面试题27:二叉树镜像

题目描述:

操作给定的二叉树,将其变换为源二叉树的镜像。

输入描述:

二叉树的镜像定义:源二叉树 
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9 11
    	镜像二叉树
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7  5

思路:对二叉树进行前序遍历,遇到非叶子节点就将其左右节点互换,直到所有非叶子节点都交换完。

代码:

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==nullptr) return;
        if(pRoot->left==nullptr&&pRoot->right==nullptr) return;
        TreeNode *pNode=pRoot->left;
        pRoot->left=pRoot->right;
        pRoot->right=pNode;
        
        if(pRoot->left){
            Mirror(pRoot->left);
        }
        if(pRoot->right){
            Mirror(pRoot->right);
        }
        return;
    }
};

 

面试题49:丑数

题目描述:

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路:方法1:时间换空间-逐个对每个数判断其是否为丑数,直到找到第index个丑数。

            方法2:空间换时间-使用数组记录找到的丑数,只对丑数进行判断直到找到第index个丑数。

代码:

方法1:时间换空间

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if(index<=0){
            return 0;
        }
        int number=0;
        int uglyindex=0;
        while(uglyindex

错误:您的代码已保存
运行超时:您的程序未能在规定时间内运行结束,请检查是否循环有错或算法复杂度过大。
case通过率为0.00%

不能通过调试!原因是复杂度太大,不够高效!

方法2:空间换时间

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if(index<=0){
            return 0;
        }
        int *UglyNumber=new int[index];
        UglyNumber[0]=1;
        int nextindex=1;
        
        int *mutiply2=UglyNumber;
        int *mutiply3=UglyNumber;
        int *mutiply5=UglyNumber;
        
        while(nextindex

 

 2.Leetcode

例1:没有重复字符的最长子串长度

题目描述:

Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the length is 3. For "bbbbb" the longest substring is "b", with the length of 1.

思路:从头遍历字符串,用哈希表记录每个字符出现的最新位置,当遇到重复字符修改start指针,统计i-start的连续不重复子串的长度,判断是否大于已有的最大长度,是则更新。

代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        map map;
        int num=0;
        int start=-1;
        for(int i=0;i

 

例2:颠倒整数

题目描述:

Reverse digits of an integer.

Example1: x = 123, return 321
Example2: x = -123, return -321

Have you thought about this?

Here are some good questions to ask before coding. Bonus points for you if you have already thought through this!

If the integer's last digit is 0, what should the output be? ie, cases such as 10, 100.

Did you notice that the reversed integer might overflow? Assume the input is a 32-bit integer, then the reverse of 1000000003 overflows. How should you handle such cases?

Throw an exception? Good, but what if throwing an exception is not an option? You would then have to re-design the function (ie, add an extra parameter).

解析:1.除法

1、直接除 : a/b 这样子会得出一个整数
2、位运算 :a >> x 表示a除以2^x
3、浮点预算 : (double)a/b 这样就可以得到一个浮点数

1:关于除法,不管是正数还是负数都是向0取整的:10/4 = 2,10/(-4) = -2
2:负数取余,通过取模来判定

2.取余

|小| % |大| = |小| 符号同前    |大| % |小| = |余| 符号同前

3%4 = 3 ; -3%4 = -3 ; -3%-4 = -3 ; 3%-4 = 3;

5%3 = 2 ; 5%-3 = 2  ;-5%-3 = -2 ; -5%3 = -2;
3:浮点数转化为int整形时,小数部分会被省略,注意不是四舍五入~~

4:严谨的情况下,不同的编译器在负数的除法运算下是存在不同情况的;但是在GCC编译器下,余数和被除数的符号一致

(防止有小白~“14➗2”读作“十四除以二”,除号前面的是“被除数”,除号后面的是“除数”)

记住这一点即可解决C/C++的大部分情况。取余运算时首先全取绝对值进行计算,再进行符号的判断,且符号同被除数。

代码:

自己解法

class Solution {
public:
    int reverse(int x) {
        if(x==0){
            return 0;
        }
        else{
            bool flag=false;
            int target=0;
            if(x<0){
                x=-x;
                flag=true;
            }
            while(x){
                target=target*10+x%10;
                x/=10;
            }
            if(flag){
                target=-target;
            }
            if(target<=INT_MIN||target>=INT_MAX){
                return 0;
            }
            else{
                return target; 
            }    
        } 
    }
};

精简版:

class Solution {
public:
    int reverse(int x)
    {
       long long res = 0;
       while(x!=0)
       {
           res = res*10+x%10;
           x/=10;
           if(res >= INT_MAX || res <= INT_MIN)
               return 0;
       }
        return res;
    }
};

 

3.2018年校招编程题

例1:最大点的集合

题目描述:

P为给定的二维平面整数点集。定义 P 中某点x,如果x满足 P 中任意点都不在 x 的右上方区域内(横纵坐标都大于x),则称其为“最大的”。求出所有“最大的”点的集合。(所有点的横坐标和纵坐标都不重复, 坐标轴范围在[0, 1e9) 内)

如下图:实心点为满足条件的点的集合。请实现代码找到集合 P 中的所有 ”最大“ 点的集合并输出。

输入描述:

第一行输入点集的个数 N, 接下来 N 行,每行两个数字代表点的 X 轴和 Y 轴。
对于 50%的数据,  1 <= N <= 10000;
对于 100%的数据, 1 <= N <= 500000;

输出描述:
输出“最大的” 点集合, 按照 X 轴从小到大的方式输出,每行两个数字分别代表点的 X 轴和 Y轴。

示例1

输入

5
1 2
5 3
4 6
7 5
9 0

输出
4 6
7 5
9 0

思路:一个点右上方没有点,也就是一个点右边的点全都在它的下边。所以将点按照横坐标从左到右排序,从右开始扫,找出右边点最大纵坐标。那么一个点的纵坐标比右边的点纵坐标最大值还大,就说明这个点右边的点全在它的下面,这样选择点就可以选出所有符合条件的点了。(因为最大点集中的点横纵坐标都要大于其周围的点)

解析:1.万能头文件#include包含了目前c++所包含的所有头文件

           2.类模板:template struct pair
参数:T1是第一个值的数据类型,T2是第二个值的数据类型。
功能:pair将一对值组合成一个值,这一对值可以具有不同的数据类型(T1和T2),两个值可以分别用pair的两个公有函数first和second访问。

举例:pair a; 表示a中有两个类型,第一个元素是int型的,第二个元素师string类型的,如果创建pair的时候没有对其进行初始化,则调用默认构造函数对其初始化。
具体用法:
1.定义(构造):

pair p1; //使用默认构造函数

pair p2(1, 2.4); //用给定值初始化

pair p3(p2); //拷贝构造函数
2.访问两个元素(通过first和second):

pair p1;		//使用默认构造函数
p1.first = 1;
p1.second = 2.5;
cout << p1.first << ' ' << p1.second << endl;
输出结果:1 2.5

3.赋值operator = :
(1)利用make_pair:

对已经初始化的进行修改:

pair p1;
p1 = make_pair(1, 1.2);

(2)变量间赋值:

pair p1(1, 1.2);

pair p2 = p1;

代码:

#include 

using namespace std;

//#define pb push_back    学习宏定义
//#define pr make_pair
const int MAXN=5e5+5;
typedef pair pi;
vector p;
pi ans[MAXN];

int main(){
    int n,x,y;
    scanf("%d",&n);
    for(int i=0;i=0;i--){
        if(p[i].second>limit){
            ans[num]=p[i];
            ++num;
            limit=p[i].second;
        }
    }
    for(int i=num-1;i>=0;i--){  //按x值从小到大输出
        printf("%d %d\n",ans[i].first,ans[i].second);
    }
    return 0;
}

 

例2:翻转数列

题目描述:

小Q定义了一种数列称为翻转数列:
给定整数n和m, 满足n能被2m整除。对于一串连续递增整数数列1, 2, 3, 4..., 每隔m个符号翻转一次, 最初符号为'-';。
例如n = 8, m = 2, 数列就是: -1, -2, +3, +4, -5, -6, +7, +8.
而n = 4, m = 1, 数列就是: -1, +2, -3, + 4.
小Q现在希望你能帮他算算前n项和为多少。

输入描述:

输入包括两个整数n和m(2 <= n <= 109, 1 <= m), 并且满足n能被2m整除。

输出描述:

输出一个整数, 表示前n项和。

输入例子1:

8 2

输出例子1:

8

思路:

这是一个规律可循的数列,题目条件说明n能被2m整除。

举例说明,输入

8  2

则数列应该为

-1  -2  3  4  -5  -6  7  8

第一个2m项为(m = 2)

-1  -2  3  4

这几项之和为m*m,计算方式为第m+1项减去第1项,第m+2项减去第2项,直至第2m项减去第m项

第二个2m项为可依次类推,和依然是m*m

-5  -6  7  8

最后计算有多少个这样的2m项,num = n / (2*m),总结果为 result = num * m * m  = n * m /2

输入其他的数据仍然可以按照该方式简化计算

 

代码:

错误解法:

#include 
using namespace std;
int main(){
    int n,m;
    cin>>n>>m;
    if(n%(2*m)!=0||n<2||m<1){
        printf("input error\n");
        return 0;
    }
    int res=0;
    int sign=-1;
    int count=0;
    for(int i=1;i<=n;i++){
        ++count;
        res+=i*sign;
        if(count==m){
            sign=-1*sign;
            count=0;
        } 
    }
    printf("%d\n",res);
    return 0;   
}

答案错误:您提交的程序没有通过所有的测试用例
case通过率为30.00%

用例:
323445962 371

对应输出应该为:

59999225951

你的输出为:

-130316193

正确代码:

#include 
using namespace std;
int main(){
    int n,m;
    cin>>n>>m;
    if(n%(2*m)!=0||n<2||m<1){
        printf("input error\n");
        return 0;
    }
    long res=(long)n*m/2;
    cout<

解析:1.printf

在printf中%d用于int或者比int小的整数类型。比int小的类型被转型成int。

%ld用于long类型,%lld用于long long类型。

%x标识的数会被当成int进行读取,所以long long类型的数如果超过int的范围会被截断,得不到正确的结果。而且因为它多占了4个字节,还会影响后面的其它标识符的输出。

另外%f标识的数会被当成double读取,即取出8个字节读取。

2.在从键盘获得输入的时候注意整型 int 和长整型 long 区别

由于题目条件中说明 2 < n < 10^9,输出最后可能大于 int 所能表示最大值 2^{32},推荐使用第3种方法

    int n = in.nextInt();
    int m = in.nextInt();
     
    long sum = n * m / 2;       //1.无法得到正确结果,运算从左边开始, n * m 结果转化为int,结果错误
     
    long sum = (long)n * m / 2; //2.可以这样在计算前转化,n * m 结果为long,结果正确
     
    long n = in.nextLong();
    long m = in.nextLong();
     
    long sum = n * m / 2;       //3.不需要转化,n * m 结果为long,结果正确

参考:https://blog.csdn.net/lsh159357/article/details/81130487

 

4.2017年阿里巴巴秋招笔试题

例6:多项式 P(X)=a+bx+cx^2+dx^3 ,对于任意 x ,计算 P(X) 中最少需要用到乘法操作的次数是多少?(A)       错选(D)

A.3 B.4 C.5 D.6 E.1 F.2

解析:

一般地,一元n次多项式的求值需要经过[n(n+1)]/2次乘法和n次加法,而秦九韶算法只需要n次乘法和n次加法。

本题中:((dx+c)x+b)x+a 三次乘法

 

例7:有一个班31人,女同学15人,男同学16人,现在要玩一个结组游戏,每组由男女两名同学构成,每个同学最多只能在一个组。结组的同学可以到老师那里领100元钱,然后按照预先分配的协议进行分配,钱最小可分单元为1元。未能结组的同学无法领到钱,不允许在组之间传递利益。那么一下命题正确的是: (A)

A.男生和女生可以得到一样多的钱
B.男生最多得49元
C.男生最多得47元
D.男生最多得46元
E.男生最多得1元
F.男生最多得0元

解析:

一共15组,每组一男一女,这15组去分100元,前14组均分98元,每组分7元,第15组分2元,

第一组男生分3元,第二组男生分4元,第三组男生分3元.......第14组男生分4元,第15组男生分1元,

按照这种方案,男女分到的钱各50元

 

例8:现代的企业是建立在大规模协作的基础上的,员工之间,团队之间,部门之间,企业之间的协作都是成功的重要因素。好的企业在协作上是高效的。以下说法中不合适的是(B)

A.一个项目能容纳的人员是有限的,当增加到一定规模项目进度反而会变慢。
B.一个项目协作为了办证信息对称,多方参与的情况下直接召集多方在一起开会就能协调好
C.协作建立的条件包括互补和共赢

D.能力结构类似的成员之间较多样互补型员工之间更容易产生竞争关系
E.协作中的权利和责任应当相称
F.如果有可能的话,信息交互较多的事务更合适在一个团队内或有一个人完成,相较于进行分工。

例9:以下程序的运行结果是?

A.foobar
B.barfoo
C.foobar或者barfoo都有可能
D.Bar
E.Foo
F.程序无法正常运行

解析:线程的启动方式只能通过start这种方式启动才能真正的实现多线程的效果,如果是手动调用run方法和普通方法调用没有区别,所以这个还是按照顺序执行首先执行run方法之后,执行输出语句所以最终得到结果foobar.

补充:1.线程的创建和启动方式

  Java使用Thread代表线程,所有的线程对象都必须是Thread类或其子类的实例。每条线程的作用是完成一条、定的任务,实际上就是执行一段程序流(一段顺

序执行的代码)。Java使用run方法来封装这样一段程序流。

(1)Thread类创建线程类

通过继承Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就是代表了线程需要完成的任务。因此,我们经常把run方法称为线程执行体。

  2. 创建Thread子类的实例,即创建了线程对象。

  3. 用线程对象的start方法来启动该线程。

下面程序示范了通过继承Thread类来创建、并启动多线程的程序。

public class FirstThread extends Thread {

    private int i;

    //重写run方法,run方法体就是线程执行体

    @Override

    public void run() {

        for (; i < 100; i++) {

            //当线程类继承Thread类时,可以直接调用getName()方法来返回当前线程的名。

            //如果想获取当前线程,直接使用this即可

            //Thread对象的getName()返回当前该线程的名字

            System.out.println(getName() + " " + i+i);

        }

    }

    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {

            //调用Thread的currentThread方法获取当前线程

            System.out.println(Thread.currentThread().getName() + " " + i);

            if (i == 20) {

                //创建、并启动第一条线程

                new FirstThread().start();

                //创建、并启动第二条线程

                new FirstThread().start();

            }

        }

    }

}

注意:使用继承Thread类的方法来创建线程类,多条线程之间无法共享线程类的实例变量。

2. 实现Runnable接口创建线程类

实现Runnable接口来创建并启动多条线程的步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run方法,该run方法的方法体同样是该线程的线程执行体。

  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。代码如下所示:


//创建Runnable实现类的对象

SecondThread st = new SecondThread();

//以Runnable实现类的对象作为Thread的target来创建Thread对象,即线程对象

new Thread(st);

//也可以在创建Thread对象时为该对象指定一个名字

//创建Thread对象时指定target和新线程的名字

new Thread(st,"新线程1");

下面程序示范了通过实现Runnable接口来创建并启动多线程的程序:


public class SecondThread implements Runnable {

    private int i;

    //run方法同样是线程执行体

    @Override

    public void run() {

        for (; i < 100; i++) {

            //当线程类实现Runnable接口时,

            //如果想获取当前线程,只能用Thread.currentThread()方法

            System.out.println(Thread.currentThread().getName() + " " + i);

        }

    }

    public static void main(String[] args) {

        for (int i = 0; i < 100; i++) {

            System.out.println(Thread.currentThread().getName() + " " + i);

            if(i == 20){

                SecondThread st = new SecondThread();

                //通过new Thread(target,name)方法创建新线程

                new Thread(st,"新线程1").start();

                new Thread(st,"新线程2").start();

            }

        }

    }

}

 

(3) 两种方式所创建线程的对比

通过继承Thread或实现Runnable接口都可以实现多线程,但两种方式存在一定的差别,相比之下两种方式的主要差别如下:

采用实现Runnable接口方式的多线程:

  1. 线程类只是实现了Runnable接口,还可以继承其他类。

  2. 在这种方式下,可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。

  3. 劣势是:编程稍稍复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

采用继承Thread类方式的多线程:

  1. 劣势是:因为线程类已经继承了Thread类,所以不能再继承其他父类。

  2. 劣势是:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

实际上几乎所有的多线程应用都可采用第一种方式,也就是实现Runnable接口的方式。

2.方法调用

1) start:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2) run:run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,而run方法只是thread的一
个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.。

两种方式的比较 :实际中往往采用实现Runable接口,一方面因为java只支持单继承,继承了Thread类就无法再继续继承其它类,而且Runable接口只有一个run方法;另一方面通过结果可以看出实现Runable接口才是真正的多线程……

 

例10.输入图片大小为200×200,依次经过一层卷积(kernel size 5×5,padding 1,stride 2),pooling(kernel size 3×3,padding 0,stride 1),又一层卷积(kernel size 3×3,padding 1,stride 1)之后,输出特征图大小为: (C)

A.95 B.96 C.97 D.98 E.99 F.100

解析:输出尺寸=(输入尺寸-filter尺寸+2*padding)/stride+1

计算尺寸不被整除只在GoogLeNet中遇到过。卷积向下取整,池化向上取整。

本题 (200-5+2*1)/2+1 为99.5,取99

(99-3)/1+1 为97

(97-3+2*1)/1+1 为97

研究过网络的话看到stride为1的时候,当kernel为 3 padding为1或者kernel为5 padding为2 一看就是卷积前后尺寸不变。

计算GoogLeNet全过程的尺寸也一样。

你可能感兴趣的:(计算机,编程,算法,数据结构,算法,数据结构)