移动涂鸦电话面笔记--游戏开发岗

目录

1、快速排列

2、STL的迭代器

3、new和malloc的区别

4、引用和指针的区别

5、进程与线程

6、Static

7、多态

8、从1到N的质数(N如果很大)

9、笔试题优化


1、快速排列

1、判断参数条件,其实这是递归的出口;

2、以数组的第一个元素为哨兵元素,让其他元素和它比较大小;(记住这时候第一个元素位置是口的,因为里面的值被作为哨兵元素保存起来了)

3、开始从数组尾部往前循环得到一个小于哨兵元素的  元素A ,把该  元素A  放到第一个元素位置(也就是哨兵元素位置上,因为哨兵元素位置是空的);(这时候要记住 元素A  的位置是空的了)

4、开始从数组头部往后循环得到一个大于哨兵元素的   元素B ,把该  元素B  放在上一步中移出的  元素A  的位置上;

5、依次循环上面3、4步,直到最后一个元素为止,那么最后一个元素就存放哨兵元素了。

6、把小于哨兵元素的那一部分和大于哨兵元素的那一部分分别递归调用本函数,依次递归排序好所有元素;

平局时间复杂度:O(n*logn)

                    T[n]= 2*T[n/2]+f(n)                         f(n)为平分这个数组花费的时间

令n = n/2           = 2  { 2 T[n/4] + (n/2) } + n      //第二次递归                

                          = 2^2 *T[ n/ (2^2) ] + 2n                                              

令 n = n/(2^2)    = 2^2 {2 T[n/ (2^3) ] + n/(2^2)}  +  2n    //第三次递归                  

                          =2^3 *T[ n/ (2^3) ] + 3n                                               

令:n = n/( 2^(m-1) )   T[n] = 2^m T[1]+ mn            //第m次递归                  

综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )

复杂时间复杂度:

最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)

综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )

 

2、STL的迭代器

STL基本的6个组成部件,各自是:容器算法迭代器仿函数适配器空间分配器

迭代器是连接容器和算法的一种重要桥梁。

想了解迭代器,先观察下面一段代码:

void f()

{

int *p=new int(42);

此处发生异常

delete p;

}

假设在两条语句中间发生异常,会导致指针p所指的那块内存泄漏。由于在执行delete之前发生异常,就不会自己主动释放堆。然而。假设使用auto_ptr(智能指针)对象取代常规指针,将会自己主动释放内存,由于编译器可以保证提前执行其析构函数。可把上述代码更改为:

 

#include<memory>//auto_ptr的头文件

void f()

{

auto_ptr<int>p(new int (42));

}

迭代器iterator就是一种智能指针。它对原始指针进行了封装,而且提供一些等价于原始指针的操作,做到既方便又安全。

容器的迭代器都是定身制作的,每一个容器相应的迭代器都是依据容器的特点来实现的,以求达到最高效率。

//迭代器失效的情况

1、在C#中,集合的更改都会是迭代器失效(foreach情况下,如插入与删除,C#中的迭代器无法更改集合的值,item属于仅读)

2、在C++中:

    2.1使vector迭代器失效的操作

        2.1.1  向vector容器内加入元素(push_back,insert)

            A.若向vector加入元素后,整个vector又一次载入。即前后两次vector的capacity()的返回值不同一时候,此时该容器 内的全部元素相应的迭代器都将失效。

            B.若该加入操作不会导致整个vector容器载入,则指向新插入元素后面的那些元素的迭代器都将失效。

注:

1、capacity()用于获取当前容器所能够储存的最大元素值

2、所谓的载入,就是当容器元素大于当前所能容纳的最大值之后,容器会重新加载其最大容量

2.1.2  删除操作(erase,pop_back,clear)

    vector运行删除操作后,被删除元素相应的迭代器以及其后面元素相应的迭代器都将失效。

2.1.3  resize操作:调整当前容器的size

    A.若调整后size>capacity,则会引起整个容器又一次载入,整个容器的迭代器都将失效。

    B.若调整后size<capacity,则不会又一次载入,详细情况例如以下:

        B1.若调整后容器的size>调整前容器的size,则原来vector的全部迭代器都不会失效。

        B2.若调整后容器的size<调整前容器的size,则容器中那些别切掉的元素相应的迭代器都将失效。

2.1.4  赋值操作(v1=v2||v1.assign(v2))

    会导致左操作数v1的全部迭代器都失效,显然右操作数v2的迭代器都不会失效。

2.2使deque迭代器失效的操作

    2.2.1  插入操作(push_front,push_back,insert)

        A.在deque容器首部或尾部插入元素不会是不论什么迭代器失效;

        B.在除去首尾的其它位置插入元素会使该容器的全部迭代器失效。

    2.2.2  删除操作

        A.在deque首、尾删除元素仅仅会使被删除元素相应的迭代器失效;

        B.在其它不论什么位置的删除操作都会使得整个迭代器失效。

2.3使list/map/set迭代器失效的操作

  因为list/map/set容器内的元素都是通过指针连接的。list实现的数据结构是双向链表,而map/set实现的数据结构是红黑树,故这些容器的插入和删除操作都只需更改指针的指向,不会移动容器内的元素。故在容器内添加元素时,不会使不论什么迭代器失效,而在删除元素时,只会使得指向被删除的迭代器失效。

 

3、new和malloc的区别

1、属性

new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

2、参数

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

3、返回类型

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

4、分配失败

new内存分配失败时,会抛出异常。malloc分配内存失败时返回NULL。

5、自定义类型

new会先调用new函数,申请足够的内存(通常底层使用malloc实现)。然后调用该类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用delete函数释放内存(通常底层使用free实现)。

malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

6、重载

C++允许重载new/delete操作符,而malloc不允许重载。

7、内存区域

new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。

 

4、引用和指针的区别

1、指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。

2、引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL)

3、有多级指针,但是没有多级引用,只能有一级引用。

4、指针和引用的自增运算结果不一样(指针是指向下一个空间,引用时引用的变量值加1)

5、sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。

6、引用访问一个变量是直接访问,而指针访问一个变量是间接访问。

 

5、进程与线程

进程就是一个应用程序在处理机上的一次执行过程。

线程是指进程内的一个执行单元,也是进程内的可调度实体。

区别:

1、一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动。

2、资源分配给进程,同一个进程的所有线程共享该进程所有资源。

3、CPU分配给线程,即真正在处理器运行的是线程。

4、线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。

 

6、Static

进程在内存中的布局:

一、static全局变量

.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量。

当一个进程的全局变量被声明为static之后,静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效,其他源文件无法访问它。

二、static局部变量

在程序刚开始就会完成初始化,它的生存周期为整个源程序,其作用域和自动变量相同,只能在定义该变量的函数内进行调用,但是退出这个函数后,依然会存在,只是无法使用。

不管是全局静态变量还是静态局部变量默认初始化为0。

三、类成员声明static

1、类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。

2、不能将静态成员函数定义为虚函数。

3、由于没有this指针的额外开销,静态成员函数与类的非静态成员函数相比速度上会有少许的增长。

4、没有对象实例,static成员依然存在类中;

5、static 数据成员必须在类外初始化,如果在类中进行初始化,需要加上关键字const(static成员函数不能声明为const); 

6、为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。

 

7、多态

概念:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。简单的说:就是用基类的引用指向子类的对象。

多态的好处:

1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承 
2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用,

 

8、从1到N的质数(N如果很大)

第一想法就是设置一个函数用于判断这个数是否为质数,然后依次遍历,面试官不满意,问是否还有更好的办法,一开始博主的想法是,除了挨个儿遍历,还有更快的方法?

后来反应过来,也许是在判断是否为质数的函数上面进行优化吧,心塞塞。

遍历的时候可以考虑,这个数是否为2到根号当前这个数的素数的倍数,如果不是,说明也是一个素数,那就保存起来,进行下一次遍历。

 

9、笔试题优化

爱德华有1个包含N个整数的数组A,他定义1个数组的美丽值为数组中所有不同整数的和。现在爱德华想知道数组A的所有连续子序列的美丽值之和。

使用递归,按子序列的长度,依次递减遍历累加,得到最后的结果。

参考代码如下:

package 测试;

public class Test1 {

	public static void main(String[] args) {
		int arr[] = { 1,1,3,3 };
		System.out.println(beauty_of_array(arr));
	}

	/*
	 * 实现代码:Java 
	 * 该函数用于计算数组中所有连续子序列的美丽值之和
	 */
	private static int beauty_of_array(int[] array) {
		// 数组的长度
		int length = array.length;
		// 如果输入的数组不合法,返回0,表示没有美丽值
		if (array == null || length <= 0)
			return 0;

		// 用于统计美丽值的总和
		int beautyValue = 0;

		// 统计所有可能存在的子序列的和
		while (length > 0) {
			beautyValue += beauty_of_array(array, length);
			length--;
		}
		return beautyValue;
	}

	/*
	 * 该函数用于统计连续的n个数作为一个子序列可得到的美丽值之和
	 */
	private static int beauty_of_array(int array[], int count) {
		// 如果已经把所有的子串都统计完毕,则不用再进行递归,返回0退出
		if (count <= 0)
			return 0;

		// 用于记录美丽值
		int beautyValue = 0;
		// 用于存放数组的长度
		int length = array.length;
		// 用于储存子串的数量
		int num = length - count + 1;
		// 循环时的计数器
		int i = 0, j = 0, k = 1;
		// 用于记录当前子串是否存在相同的其他子串
		boolean exsist = false;

		// 计算子序列个数为count的所有不重复子序列的和
		for (i = 0; i < num; i++) {
			exsist=false;
			// 检查数组中是否存在相同的子串
			for (j = 0; j < num; j++) {
				k = 0;
				if (array[j] == array[i] && j != i) {
					k = 1;
					while (k < count && array[j + k] == array[i + k])
						k++;
				}
                //如果存在一个相同的子序列,令exsisit为空,跳出检查循环进行下一个子序列的判断
				if (k == count) {
					exsist = true;
					break;
				}
			}

			// 如果子串并没有出现过,则相加
			if (!exsist) {
				beautyValue += getBeautyValue(array, count, i);
			}
		}
		return beautyValue;
	}
	/*
	 * 获取下标从startPos开始往后count个数字的和
	 * */
	private static int getBeautyValue(int[] array, int count, int startPos) {
		int value=0;
		for(int i=startPos;i<startPos+count;i++)
			value+=array[i];
		
		return value;
	}
}

/*
 *测试案例:
 *1--》1
 *1,2--》6
 *1,1,2--》11
 *1,2,3,3--》40
 **/

面试官提起的暴力解决的方法没有想出来。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值