本文是Android面試題整理中的一篇,結合右下角目錄食用更佳,包括:html
每一個節點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。java
若設二叉樹的高度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第h層有葉子結點,而且葉子結點都是從左到右依次排布,這就是徹底二叉樹。
特性:徹底二叉樹中任何一層最左的節點編號n,則其左子樹爲2n,右子樹爲2n+1,利用這種特性,能夠用數組做爲二叉樹的物理結構node
除了葉結點外每個結點都有左右子葉且葉子結點都處在最底層的二叉樹。
特色:滿二叉樹有2^k-1個節點(k爲高度)linux
它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹git
是一種自平衡二叉查找樹
特性:github
- 節點是紅色或黑色。
- 根節點是黑色。
- 每一個葉節點(NIL節點,空節點)是黑色的。
- 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
- 從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。
// 節點
class Node{
int value;
Node leftNode;
Node rightNode;
}
//如下知識簡略步驟,省去判空等操做,每種只選取了一種簡單實現方式
// 遍歷的順序指訪問根結點的操做發生在遍歷其左右子樹的先後中
// 前序 (遞歸)
public void preOrder(Node node){
System.out.print(node.value);
preOrder(node.leftNode);
preOrder(node.rightNode);
}
// 中序 (遞歸)
public void inOrder(Node node){
inOrder(node.leftNode);
System.out.print(node.value);
inOrder(node.rightNode);
}
// 後序 (遞歸)
public void posOrder(Node node){
posOrder(node.leftNode);
posOrder(node.rightNode);
System.out.print(node.value);
}
// 計算深度 (遞歸)
public int level(Node node){
if (node == null)
return 0;
return level(node.leftNode) + 1 > level(node.rightNode) + 1 ? level(node.leftNode) + 1
: level(node.rightNode) + 1;
}
// 層序 (非遞歸)
public void levelOrder(Node node){
Queue<Node> nodes = new LinkedList<>();
nodes.add(node);
while (!nodes.isEmpty()){
Node node1 = nodes.poll();
System.out.print(node1.value);
nodes.offer(node1.leftNode);
nodes.offer(node1.rightNode);
}
}
複製代碼
堆是一棵徹底二叉樹,分爲大根堆(父結點>子節點)和小根堆(父結點<子節點)面試
//冒泡排序(時間複雜度n^2)
public int[] blueblueSort(int[] array){
for (int i = 0 ; i < array.length ; i++){
for (int j = 0 ; j< array.length -i-1; j++){
if (array[j] > array[j+1]){
int temp = array[j+1]; // 元素交換
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
//選擇排序 (n^2)
public int[] sellectionSort(int[] array){
for (int i = 0 ; i < array.length - 1 ; i++){
int tempk = i;
for (int j = i+1 ; j < array.length; j++){
if (array[tempk] > array[j]){
tempk = j;
}
}
int temp = array[i];
array[i] = array[tempk];
array[tempk] = temp;
}
return array;
}
//插入排序 (n^2)
public int[] insertSort(int[] array){
for (int i = 1 ; i < array.length; i++){
int j = i-1;
int current = array[i];
while (j >= 0 && array[j] > current ){
array[j+1] = array[j];
j--;
}
array[j+1] = current;
}
return array;
}
// 歸併排序 (n log n)
public int[] mergeSort(int[] array){
int length = array.length;
if (length < 2){
return array;
}
int[] left = new int[array.length/2];
int[] right = new int[array.length - array.length/2];
for (int i = 0; i < array.length/2 ; i++){
left[i] = array[i];
}
for (int j = 0;j < right.length;j++){
right[j] = array[array.length/2+j];
}
return merge(mergeSort(left),mergeSort(right));
}
public int[] merge(int[] left, int[] right){
int[] result = new int[left.length+right.length];
int i = 0;
int j = 0;
int k = 0;
while (i < left.length && j < right.length){
if (left[i] > right[j]){
result[k] = right[j];
j++;
} else {
result[k] = left[i];
i++;
}
k++;
}
while (i<left.length){
result[k] = left[i];
k++;
i++;
}
while (j<right.length){
result[k] = right[j];
k++;
j++;
}
return result;
}
// 快排 (n log n)
public void quickSort1(int[] array,int begin ,int end) {
if (begin<end){
int p = partition(array,begin,end);
quickSort1(array,begin,p-1);
quickSort1(array,p+1,end);
}
}
public int partition(int[] array,int begin ,int end){
int key = array[begin];
int left = begin;
int right = end;
while (left < right){
while (array[right] > key && left < right)
right--;
while (array[left] <= key && left < right)
left++;
if (left < right){
int temp1 = array[left];
array[left] = array[right];
array[right] = temp1;
}
}
array[begin] = array[left];
array[left] = key;
return left;
}
複製代碼
時間複雜度log n算法
//非遞歸
public static int binarySearch(Integer[] srcArray, int des) {
//定義初始最小、最大索引
int low = 0;
int high = srcArray.length - 1;
//確保不會出現重複查找,越界
while (low <= high) {
//計算出中間索引值
int middle = (high + low)>>>1 ;//防止溢出
if (des == srcArray[middle]) {
return middle;
//判斷下限
} else if (des < srcArray[middle]) {
high = middle - 1;
//判斷上限
} else {
low = middle + 1;
}
}
//若沒有,則返回-1
return -1;
}
//遞歸
public int binarySearch(int[] array,int begin,int end,int key){
if ( begin > end ){
return -1;
}
int mid = begin + end >>> 1;
if (array[mid] == key){
return mid;
} else if (array[mid] > key){
return binarySearch(array,begin,mid-1,key);
} else {
return binarySearch(array,mid+1,end,key);
}
}
複製代碼
若是在面試中有面試官要求你寫一個O(n)時間複雜度的排序算法,你千萬不要馬上說:這不可能!雖然前面基於比較的排序的下限是O(nlogn)。可是確實也有線性時間複雜度的排序,只不過有前提條件,就是待排序的數要知足必定的範圍的整數,並且計數排序須要比較多的輔助空間。其基本思想是,用待排序的數做爲計數數組的下標,統計每一個數字的個數。而後依次輸出便可獲得有序序列。segmentfault
public void heapSort(int[] array){
for (int i = array.length / 2 ; i >= 0 ; i--){ //初始化堆
heapAdjust(array,i,array.length-1);
}
for (int i = array.length-1;i>0;i--){ //排序
int temp = array[i];
array[i] = array[0];
array[0] = temp;
heapAdjust(array,0,i);
}
}
private void heapAdjust(int[] array , int parent, int length){
int temp = array[parent];
int child = parent*2+1;
while (child < length){
if (child+1<length && array[child]<array[child+1] )
child++;
if (temp > array[child])
break;
array[parent] = array[child];
parent = child;
child = 2*child+1;
}
array[parent] = temp;
}
複製代碼
public static <T extends Comparable<T>> void quickSort(T[] array, int left, int right) {
if (left < right){
int p = partition(array,left,right);
quickSort(array,left,p-1);
quickSort(array,p+1,right);
}
}
private static <T extends Comparable<T>> int partition(T[] array, int left, int right) {
T key = array[left];
int start = left;
while (left<right){
while (left < right && array[right].compareTo(key) > 0)
right --;
while (left < right && array[left].compareTo(key) <= 0)
left ++;
if (left < right){
T temp1 = array[left];
array[left] = array[right];
array[right] = temp1;
}
}
array[start] = array[left];
array[left] = key;
return left;
}
複製代碼
public static <T extends Comparable<T>> void bubbleSort(T[] array){
for (int i = 0 ; i < array.length - 1 ; i++){ //外層循環控制排序趟數
for (int j = 0 ; j < array.length - i- 1 ; j++){
if (array[j].compareTo(array[j+1])>0){ //內層循環控制每一趟排序多少次
T temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
複製代碼
public class CountSort {
public static void countSort(int[] arr) {
if(arr == null || arr.length == 0)
return ;
int max = max(arr);
int[] count = new int[max+1];
Arrays.fill(count, 0);
for(int i=0; i<arr.length; i++) {
count[arr[i]] ++;
}
int k = 0;
for(int i=0; i<=max; i++) {
for(int j=0; j<count[i]; j++) {
arr[k++] = i;
}
}
}
public static int max(int[] arr) {
int max = Integer.MIN_VALUE;
for(int ele : arr) {
if(ele > max)
max = ele;
}
return max;
}
}
複製代碼
Base64 是一種編碼方式,不具備可讀性,便與傳輸數組
- 壓縮性:任意長度的數據,算出的MD5值長度都是固定的。
- 容易計算:從原數據計算出MD5值很容易。
- 抗修改性:對原數據進行任何改動,哪怕只修改1個字節,所獲得的MD5值都有很大區別。
- 強抗碰撞:已知原數據和其MD5值,想找到一個具備相同MD5值的數據(即僞造數據)是很是困難的。
- 不可逆:知道md5,不能還原出加密前數據
使用相似快排的方式:
- 隨機選一個基值X,分爲將數組分爲兩塊Sa > X > Sb
- Sa個數大於k,則遞歸Sa找第k大數;Sa個數小於k,遞歸Sb找第(k-Sa.size)大數
複雜度O(n)
- X[n/2],Y[n/2];比較兩數的大小,若X[n/2]>Y[n/2],那麼咱們能夠捨去X[n/2]以後和Y[n/2]以前的數;若X[n/2]<Y[n/2],對應的,咱們能夠捨去X[n/2]以前和Y[n/2]以後的數;若X[n/2]=Y[n/2],那代表當前值就是所求的中位數。
- 遞歸的在剩餘的數中進行比較篩選
兩個指針一快一慢,有環的話快慢確定會相遇
平衡二叉樹的定義:空樹或者左右子樹高度相差不超過1;子樹也是平衡二叉樹
利用這一特性,咱們能夠用遞歸的方式判斷
class TreeNode{
int val;
TreeNode left=null;
TreeNode right=null;
public TreeNode(int val) {
this.val = val;
}
}
public boolean IsBalanced_Solution(TreeNode root) {
if(root==null)
return true;
//若是樹爲 null 返回 TRUE。不然判斷根的左右子樹的高度差的絕對值是否大於1,若大於1 則返回false。
// 不然判斷樹的左右孩子是不是平衡二叉樹,當二者都是平衡二叉樹時返回TRUE,不然返回false.
else if(Math.abs(TreeDepth(root.left)-TreeDepth(root.right))>1)
return false;
else return IsBalanced_Solution(root.left)&&IsBalanced_Solution(root.right);
}
//求樹的深度。
public int TreeDepth(TreeNode root){
if(root==null)
return 0;
//若是樹爲 null 返回0 不然返回左右孩子的最大值+1。
return Math.max(TreeDepth(root.left), TreeDepth(root.right))+1;
}
由於上述方法會屢次計算相同子樹深度,優化:
public class IsBalancedTree {
boolean isBalance=true;
public boolean IsBalanced_Solution(TreeNode root) {
TreeDepth1(root);
return isBalance;
//isBalance 會在 TreeDepth1(root)中賦值。
}
public int TreeDepth1(TreeNode root)
{
if(root==null)
return 0;
int left=TreeDepth1(root.left);
//左子樹高度
int right=TreeDepth1(root.right);
//右子樹高度
if(Math.abs(left-right)>1)
{
isBalance=false;
//只要有一個子樹的左右子樹的高度絕對值大於 1 isBalance=false
}
return Math.max(left, right)+1;
}
複製代碼
解題思路:咱們能夠把5張牌當作是由5個數字組成的俄數組。大小王是特殊的數字,咱們能夠把它們都定義爲0,這樣就能夠和其餘的牌區分開來。
首先把數組排序,再統計數組中0的個數,最後統計排序以後的數組中相鄰數字之間的空缺總數。若是空缺的總數小於或者等於0的個數,那麼這個數組就是連續的,反之則不連續。若是數組中的非0數字重複出現,則該數組是不連續的。換成撲克牌的描述方式就是若是一幅牌裏含有對子,則不多是順子。
public boolean isContinuous(int[] array){
Arrays.sort(array);
int wang = 0;
int gap = 0;
for (int i : array){
if (array[i] == 0){
wang++;
continue;
}
if (i < array.length-1){
if (array[i] == array[i+1]){
return false;
} else {
gap += array[i+1] - array[i]-1;
}
}
}
return wang >= gap;
}
複製代碼
題目:0,1,...,n-1這n個數字排成一個圓圈,從數字0開始每次從這個圓圈裏刪除第m個數字。求這個圓圈裏剩下的最後一個數字。
變形:標號1-n的n我的首尾相接,1到3報數,報到3的退出,求最後一我的的標號
解答:
一、環形鏈表模擬圓圈
建立一個n個節點的環形鏈表,而後每次在這個鏈表中刪除第m個節點;
能夠用std::list來模擬環形鏈表,list自己不是環形結構,所以每當迭代器掃描到鏈表末尾的時候,須要將迭代器移到鏈表的頭部。
二、分析每次被刪除的數字的規律,動態規劃
假設從0-n-1中刪除了第m個數字,則下一輪的數字排列爲m,m+1,.....n,1,2,3...m-2,將該數字排列從新映射爲0~n-2,則爲
m    0
m+1   1  
....    ....
n-1   n-1-m
0    n-m
1    n-m+1
...    ....
m-2   n-2
能夠看出從右往左的映射關係爲left=(right+m)%n,即0~n-1序列中最後剩下的數字等於(0~n-2序列中最後剩下的數字+m)%n,很明顯當n=1時,只有一個數,那麼剩下的數字就是0.
問題轉化爲動態規劃問題,關係表示爲:
f(n)=(f(n-1)+m)%n; 當n=1,f(1)=0;
代碼以下:
public static int lastRemaining(int n, int m){
if(n < 1 || m < 1){
return -1;
}
int last = 0;
for(int i = 2; i <= n; i++){
last = (last + m) % i;
}
return last;
}
複製代碼
思路:和計數排序相似,經過一個數組來表示字符(數組太長時也能夠考慮bitmap),統計每一個位置的個數。
題目:輸入二叉樹的前序遍歷和中序遍歷的結果,重建出該二叉樹。假設前序遍歷和中序遍歷結果中都不包含重複的數字,例如輸入的前序遍歷序列 {1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6}重建出如圖所示的二叉樹。
前序遍歷第一個地址是父地址,在中序遍歷中,此地址前是左子樹(個目m),右邊是右子樹(個數n);前序遍歷第一個地址後的m個是其左子樹,其他是其右子樹。有了左右子樹的前序遍歷和中序遍歷,咱們就能夠用遞歸的方法來構建樹了
public class BinaryTreeNode {
public static int value;
public BinaryTreeNode leftNode;
public BinaryTreeNode rightNode;
}
public BinaryTreeNode build(int[] pre, int[] in){
if(pre == null || in == null || pre.length != in.length){
throw new Exception("heiheihei");
}
BinaryTreeNode root = new BinaryTreeNode();
root.value = pre[0];
for(int i = 0; i < in.length ;i++){
if(pre[0] == in[i]){
root.leftNode = build(Array.copyOfRange(pre,1,i+1),Array.copyOfRange(in,0,i));
root.rightNode = build(Array.copyOfRange(pre,i+1,pre.length),Array.copyOfRange(in,i+1,in.length));
break;
}
}
return root;
}
複製代碼
給定一個數組和一個目標值target,在數組中找到一組和爲target的數,並輸出他們的下標,而且要求下標1 < 下標2
public int[] find(int[] array,int target){
HashMap<int,int> map = new HashMap<>();
int[] result = new int[2];
for(int i = 0;i<array.length;array++){
if(map.get(array[i])!=null){
result[0] = map.get(array[i])+1;
result[1] = i+1;
break;
}
}
return result;
}
複製代碼
思路:經過兩個棧來實現,一個棧正常存儲,另外一個棧記錄最小數據
思路:將棧1數據存入棧2,即完成了讀取棧2的內容,便是隊列。
解法:1.add時將數據存入棧1 2.讀取時讀取棧2,若棧2爲空,將棧1數據存入棧2
class Node{
int value;
Node next;
}
// 非遞歸
public Node reverse(Node node){
Node pre = null;
Node now = node;
while(now!=null){
Node temp = now.next;
now.next = pre;
pre = now;
now = temp;
}
return pre;
}
//遞歸
public Node reverse3(Node node) {
if(node.next==null)return node;
Node next = node.next;
node.next = null;
Node re = reverse3(next);
next.next = node;
return re;
}
複製代碼
final int size = data.length;
for(int i = 0; i< size; i++){
if(data[i] == 0xffffffff)
data[i] = 0x80ffffff;
}
複製代碼
//方法1
a = a+b;
b = a-b;
a = a-b;
//方法2
a = a^b;
b = b^a;
a = a^b;
複製代碼
任意給定一個32位無符號整數n,求n的二進制表示中1的個數,好比n = 5(0101)時,返回2,n = 15(1111)時,返回4
思路:利用n&(n-1)消除n轉換成二進制後最低位的1
public void count(int n){
int count = 0;
while(n>0){
count++;
n = n&(n-1)
}
複製代碼
擴展:求一個數是否是偶數:n > 0 && ((n & (n - 1)) == 0 )
思路:和快排思想相似,從前向後找到偶數,從後向前找到奇數,交換
private void jiOu(int a[]) //將數組a中奇數放在前面,偶數放在後面
{
int len = a.length;
if(len <= 0) //數組長度爲0則返回
return ;
int front = 0, end = len-1;//設置兩個指針,一個指向頭部,一個指向尾部
while(front<end)
{
while(front<len && (a[front]&1)==1) //從前日後找偶數
front++;
while(end>=0 && (a[end]&1)==0) //從後往前找奇數
end--;
if(front<end)
{
int swap = a[front]; //將奇數往前挪,偶數日後挪
a[front] = a[end];
a[end] = swap;
}
}
}
複製代碼
輸入兩行數據,第一行爲所有員工的 id,第二行爲某一天打卡的員工 id,已知只有一個員工沒有打卡,求出未打卡員工的 id。(員工 id 不重複,每行輸入的 id 未排序)
- 思路1:遍歷一邊員工id,將員工id填入HashMap或計數排序;再遍歷一遍打卡員工,從HashMap或者技術排序的數組中查找。由於HashMap和技術排序的數組查找的時間複雜度都是1,隨意總體的時間複雜度是兩次遍歷即2n
- 思路2:咱們有兩個數組,打卡的員工id確定會在兩個數組裏各出現一次,一共2次,未打卡的員工id只出現了一次;利用a^a = 0;0^b = b;的特性。
int result = 0;
for (int i = 0; i < ids.length; i++) {
result ^= Integer.parseInt(ids[i]);
}
for (int i = 0; i < marks.length; i++) {
result ^= Integer.parseInt(marks[i]);
}
複製代碼
25匹馬,速度都不一樣,但每匹馬的速度都是定值。如今只有5條賽道,沒法計時,即每賽一場最多隻能知道5匹馬的相對快慢。問最少賽幾場能夠找出25匹馬中速度最快的前3名?
- 25匹馬分紅5組,先進行5場比賽
- 再將剛纔5場的冠軍進行第6場比賽,獲得第一名。按照第6場比賽的名詞把前面5場比賽所在的組命名爲 A、B、C、D、E 組,即 A 組的冠軍是第6場第一名,B 組的冠軍是第二名 …
- 分析第2名和第3名的可能性,若是肯定有多於3匹馬比某匹馬快,那它能夠被淘汰了。由於 D 組是第6場的第四名,整個D 組被淘汰了,贊成整個 E 組被淘汰。剩下多是總體的第二、3名的就是C組的第1名、B組的一、2名、A組的第二、3名。取這5匹馬進行第7場比賽
- 因此,一共須要7場比賽
對於52張牌,實現一個隨機打算撲克牌順序的程序。52張牌使用 int 數組模擬
思路:隨機選取一張,和第一張交換;再剩下的牌中選取一張,和第二張交換,以此類推
public void randomCards() {
int[] data = new int[52];
Random random= new Random();
for (int i = 0; i < data.length; i++)
data[i] = i;
for (int i = data.length - 1; i > 0; i--) {
int temp = random.nextInt(i+1); //產生 [0,i] 之間的隨機數
swap(data,i,temp);
}
}
複製代碼
某個字符串只包括 ( 和 ) ,判斷其中的括號是否匹配正確,好比 (()()) 正確, ((())() 錯誤, 不容許使用棧 。
思路:這種相似題的常見思路是棧,對於左括號入棧,若是遇到右括號,判斷此時棧頂是否是左括號,是則將其出棧,不是則該括號序列不合法;面試官要求不能使用棧,可使用計數器,利用 int count 字段。
public static boolean checkBrackets(String str) {
char[] cs = str.toCharArray();
int count = 0;
for (int i = 0; i < cs.length; i++) {
if (cs[i] == '(')
count++;
else {
count--;
if (count < 0) {
return false;
}
}
}
return count == 0;
}
複製代碼
假設這有一個各類字母組成的字符串A,和另一個字符串B,字符串裏B的字母數相對少一些。什麼方法能最快的查出全部小字符串B裏的字母在大字符串A裏都有?
思路1:使用計數排序,複雜度m+n 思路2: 藉助hashmap
一個單詞單詞字母交換,可得另外一個單詞,如army->mary,成爲兄弟單詞。提供一個單詞,在字典中找到它的兄弟。描述數據結構和查詢過程
思路:使用HashMap和鏈表,單詞排序後的值做爲HashMap的key,鏈表做爲value
一個url指向的頁面裏面有另外一個url,最終有一個url指向以前出現過的url或空,這兩種情形都定義爲null。這樣構成一個單鏈表。給兩條這樣單鏈表,判斷裏面是否存在一樣的url。url以億級計,資源不足以hash
思路:先判斷是否有環路(快慢指針),再判斷是否交叉(有交叉最後指向相同節點)
- 遍歷字符串,過程當中將出現過的字符存入字典,key爲字符,value爲字符下標
- 用maxLength保存遍歷過程當中找到的最大不重複子串的長度
- 用start保存最長子串的開始下標
- 若是字符已經出如今字典中,更新start的值
- 若是字符不在字典中,更新maxLength的值
- return maxLength
https://blog.csdn.net/fengqiangfeng/article/details/8049903