java 数据结构与算法 面试题_Java面试题4-数据结构与算法基础

说一下几种常见的排序算法和分别的复杂度

倒排一个LinkedList

Collecionts.reverse(List> list)

什么是跳表

1577179803928-8d43c4d5-35d1-4ce8-9828-7801f8a4506b.png

如何确认一个链表有环?进一步,确认环的位置

这是一道很常见的面试问题,,只用两个变量通过O(n)的时间复杂度就可以解决。

Floyd cycle detection算法,也叫做tortoise and hare算法,龟兔算法吧。

http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare

以下内容为对思路重新梳理了一下,参考了stackoverflow论坛的讨论

http://stackoverflow.com/questions/2936213/explain-how-finding-cycle-start-node-in-cycle-linked-list-work

http://stackoverflow.com/questions/494830/how-to-determine-if-a-linked-list-has-a-cycle-using-only-two-memory-locations。

和thestoryofsnow的博客

http://blog.csdn.net/thestoryofsnow/article/details/6822576

代码来自thefutureisour

http://blog.csdn.net/thefutureisour/article/details/8174313

原文链接:https://blog.csdn.net/duxinyuhi/article/details/53379239

龟兔解法的基本思想可以用我们跑步的例子来解释,如果两个人同时出发,如果赛道有环,那么快的一方总能追上慢的一方。进一步想,追上时快的一方肯定比慢的一方多跑了几圈,即多跑的路的长度是圈的长度的倍数。

基于上面的想法,Floyd用两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的,只要一个比另一个快就行,从后面的讨论我们可以看出这一点。ps:考虑一个链表A有1000000个结点的环,链表B是有1000000个的非环的结点,然后再加一个只有3个结点的小环,如果兔子跑的更快点,即每次前进多于2步,那么能更快的检测链表A,但链表B就很慢,因为要一直绕圈等乌龟,所以选择2步是一个tradeoff)。如果两者在链表头以外的某一点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了链表的结尾,那么说明没环。

1579068145735-6fba268a-f3e2-40fa-b0a8-377784e9cca9.png

证明如下:

这样做的道理我用下图解释

假设起点到环的起点距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针移动的总距离为i,因为快指针移动速度为慢指针的两倍,那么快指针的移动距离为2i

1) i = m + p * n + k

2) 2i = m + q * n + ki

其中,p和q分别为慢指针和快指针在第一次相遇时转过的圈数。

2 ( m + p * n + k ) = m + q * n + k

=> 2m + 2pn + 2k = m + nq + k

=> m + k = ( q - 2p ) n

只要有一组p,q,k满足这个式子,假设就成立了。

我们假设

p = 0

q = m

k = m n - m

那么我们证明如下

m + k = ( q - 2p ) n

=> m + mn - m = ( m - 2*0) n

=> mn = mn.

这时,i为

i = m + p n + k

=> m + 0 * n + mn - m = mn.

假设成立。

环的检测

环的检测是Floyd解法的第二部分。

一旦乌龟和兔子相遇,将慢指针移到链表起点,快指针还在他们相遇的地方,即离环的起点距离k的地方。然后慢指针和快指针同时移动,每次移动一步,那么两者再次相遇的地方就是环的起点。

证明如下:

让乌龟和兔子都同时移动m+k步,乌龟到了他们最初遇见的地方(远离环起点k的位置)

之前我们得到 m + k = (q - 2p) n

即兔子走了q-2p圈,也到了和乌龟同样的位置

现在我们不让乌龟走 m+k 步,让乌龟只走 m 步,兔子也后退k步,即到了环的起点,这是他们第一次相遇的地方。

当他们相遇时,乌龟走的步数就是环的地点在的位置。

求环的长度的问题,第一次相遇后,再次相遇时,走的距离就是环的长度。

如何遍历一棵二叉树

1579068263858-e8b0b87a-c87e-4cb6-9810-1bdec0cde08e.png

1579068281365-60b1a97d-a4a2-46d4-9e0d-8451c03fe463.png

二叉树的深度优先遍历和广度优先遍历

1579067506847-885991a3-4d78-4ca0-9ba8-6e324fe79e17.png

广度优先遍历

英文缩写为BFS即Breadth FirstSearch。其过程检验来说是对每一层节点依次访问,访问完一层进入下一层,而且每个节点只能访问一次。对于上面的例子来说,广度优先遍历的 结果是:A,B,C,D,E,F,G,H,I(假设每层节点从左到右访问)。

先往队列中插入左节点,再插右节点,这样出队就是先左节点后右节点了。

广度优先遍历树,需要用到队列(Queue)来存储节点对象,队列的特点就是先进先出。例如,上面这颗树的访问如下:

首先将A节点插入队列中,队列中有元素(A);

将A节点弹出,同时将A节点的左、右节点依次插入队列,B在队首,C在队尾,(B,C),此时得到A节点;

继续弹出队首元素,即弹出B,并将B的左、右节点插入队列,C在队首,E在队尾(C,D,E),此时得到B节点;

继续弹出,即弹出C,并将C节点的左、中、右节点依次插入队列,(D,E,F,G,H),此时得到C节点;

将D弹出,此时D没有子节点,队列中元素为(E,F,G,H),得到D节点;

深度优先遍历

英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。对于上面的例子来说深度优先遍历的结果就是:A,B,D,E,I,C,F,G,H.(假设先走子节点的的左侧)。

深度优先遍历各个节点,需要使用到栈(Stack)这种数据结构。stack的特点是是先进后出。整个遍历过程如下:

先往栈中压入右节点,再压左节点,这样出栈就是先左节点后右节点了。

首先将A节点压入栈中,stack(A);

将A节点弹出,同时将A的子节点C,B压入栈中,此时B在栈的顶部,stack(B,C);

将B节点弹出,同时将B的子节点E,D压入栈中,此时D在栈的顶部,stack(D,E,C);

将D节点弹出,没有子节点压入,此时E在栈的顶部,stack(E,C);

将E节点弹出,同时将E的子节点I压入,stack(I,C);

…依次往下,最终遍历完成。

HashSet的实现方式

HashSet概述:

HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素

堆和栈在内存中的区别是什么

栈内存存储的是局部变量而堆内存存储的是实体;

栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收

什么是深拷贝和浅拷贝

浅拷贝: 是一个传址,也就是把a的值赋给b的时候同时也把a的址赋给了b,当b(a)的值改变的时候,a(b)的值同时也会改变

深拷贝:深拷贝是指,拷贝对象的具体内容,二内存地址是自主分配的,拷贝结束之后俩个对象虽然存的值是一样的,但是内存地址不一样,俩个对象页互相不影响,互不干涉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值