測試開發面試真題—演算法(百度)
1、請列舉常用的演算法,並說明其時間複雜讀,並說明排序思想?
①氣泡排序:量量比較待排序資料元素的大小,發現兩個資料元素的次序相反時進行交換,直到沒有反序的資料元素為止。時間複雜度是O(n*2)。穩定的。
public class bubble1 { public static void main(String[] args) { int[] array = { 7, 8, 3, 1, 5, 2, 9, 6, 4 }; printArray(array); bubbleSort(array); printArray(array); } private static void bubbleSort(int[] array) { for (int i = 0; i < array.length; i++) { for (int j = i + 1; j < array.length; j++) { if (array[i] > array[j]) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } } } } private static void printArray(int[] array) { if (array.length == 0 || array == null) { return; } for (int i = 0; i < array.length - 1; i++) { System.out.print(array[i] + " "); } System.out.println(array[array.length - 1]); } }
public class bubble2 { public static void main(String[] args) { int[] array = { 7, 8, 3, 1, 5, 2, 9, 6, 4 }; printArray(array); bubbleSort(array); printArray(array); } private static void bubbleSort(int[] array) { for (int i = 0; i < array.length; i++) { for (int j = array.length - 2; j >= i; j--) { if (array[j] > array[j + 1]) { int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } } private static void printArray(int[] array) { if (array.length == 0 || array == null) { return; } for (int i = 0; i < array.length - 1; i++) { System.out.print(array[i] + " "); } System.out.println(array[array.length - 1]); } }
②選擇排序:每一趟從待排序的資料元素中選出最小(最大)的一個元素,順序放在已經排好序的數列的最後面,
直到全部待排序的資料元素排完,演算法複雜度是O(n*2)。不穩定的。
public class select { public static void main(String[] args) { int[] array = { 0, 8, 3, 1, 5, 2, 9, 6, 4 }; printArray(array); selectSort(array); printArray(array); } private static void selectSort(int[] array) { int k = 0; for (int i = 0; i < array.length; i++) { k = i; for (int j = i; j < array.length; j++) { if (array[j] < array[k]) { k = j; } } int temp = array[i]; array[i] = array[k]; array[k] = temp; } } private static void printArray(int[] array) { if (array.length == 0 || array == null) { return; } for (int i = 0; i < array.length - 1; i++) { System.out.print(array[i] + " "); } System.out.println(array[array.length - 1]); } }
③插入排序:每次將一個待排序的資料元素,插入到前面已經排好序的數列中的適當位置,使數列依然有序;直到待排序資料元素全部插入完為止。
演算法的時間複雜度是o(n*2)。穩定的。
public class insert {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
insertSort(array);
printArray(array);
}
private static void insertSort(int[] array) {
int temp = 0;
for (int i = 1; i < array.length; i++) {
temp = array[i];
int j = 0;
for (j = i; j > 0; j--) {
if (array[j - 1] > temp) {
array[j] = array[j - 1];
} else {
break;
}
}
array[j] = temp;
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
④快速排序:在當前無序區 R[1..H]中任取一個數據元素作為比較的"基準"(不妨記為 X),用此基準將當前無序區劃分為左右兩個較小的無序區:R[1..I-1]和 R[I+1..H],
且左邊的無序子區中資料元素均小於等於基準元素,右邊的無序子區中資料元素均大於等於基準元素,而基準 X 則位於最終排序的位置上,
即R[1..I-1]≤X.Key≤R[I+1..H](1≤I≤H),當 R[1..I-1]和 R[I+1..H]均非空時,分別對它們進行上述的劃分過程,直至所有無序子區中的資料元素均已排序為止。
最理想情況演算法時間複雜度 O(nlogn),最壞O(n^2) 。不穩定。
public class Quick {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1, 0, 2, 9, 6, 4 };
printArray(array);
quickSort(array);
printArray(array);
}
private static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int low, int high) {
int key = 0;
if (low < high) {
key = partition(array, low, high);
quick(array, low, key - 1);
quick(array, key + 1, high);
}
}
private static int partition(int[] array, int low, int high) {
int key = array[low];
while (low < high) {
while (low < high && array[high] >= key) {
high--;
}
swap(array, low, high);
while (low < high && array[low] <= key) {
low++;
}
swap(array, low, high);
}
return low;
}
private static void swap(int[] array, int low, int high) {
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
⑤堆排序:堆排序是一樹形選擇排序,在排序過程中,將 R[1..N]看成是一顆完全二叉樹的順序儲存結構,
利用完全二叉樹中雙親結點和孩子結點之間的內在關係來選擇最小的元素。 演算法時間複雜度O(nlogn)。不wend
public class Heap {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
heapSort(array);
printArray(array);
}
private static void heapSort(int[] array) {
if (array == null || array.length < 0) {
return;
}
buildMaxHeap(array);
for (int i = array.length - 1; i >= 1; i--) {
swap(array, 0, i);
maxHeap(array, i, 0);
}
}
private static void buildMaxHeap(int[] array) {
if (array == null || array.length < 0) {
return;
}
int half = array.length / 2;
for (int i = half; i >= 0; i--) {
maxHeap(array, array.length, i);
}
}
private static void maxHeap(int[] array, int heapSize, int index) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int largest = index;
if (left < heapSize && array[left] > array[index]) {
largest = left;
}
if (right < heapSize && array[right] > array[largest]) {
largest = right;
}
if (index != largest) {
swap(array, index, largest);
maxHeap(array, heapSize, largest);
}
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
⑥希爾排序:其實就是用步長控制的插入排序,希爾排序通過加大插入排序中元素之間的間隔,並在這些有間隔的元素中進行插入排序,從而讓資料項可以大幅度移動,
這樣的方式可以使每次移動之後的資料離他們在最終序列中的位置相差不大,保證資料的基本有序,大大提升了排序速度,運算時間複雜度 N*logN,不穩定。
public class Shell {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1,1, 2, 9, 6, 4 };
printArray(array);
shellSort(array);
printArray(array);
}
private static void shellSort(int[] array) {
int j = 0;
int temp = 0;
for (int increment = array.length / 2; increment > 0; increment /= 2) {
for (int i = increment; i < array.length; i++) {
temp = array[i];
for (j = i; j >= increment; j -= increment) {
if (temp < array[j - increment]) {
array[j] = array[j - increment];
} else {
break;
}
}
array[j] = temp;
}
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
⑦歸併排序:Divide: 把長度為 n 的輸入序列分成兩個長度為 n/2 的子序列。Conquer: 對這兩個子序列分別採用歸併排序。
Combine: 將兩個排序好的子序列合併成一個最終的排序序列。時間複雜度是 O(nlogn)。 穩定的 。
public class merge {
public static void main(String[] args) {
int[] array = { 7, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
mergeSort(array);
printArray(array);
}
private static void mergeSort(int[] array) {
sort(array, 0, array.length - 1);
}
private static void sort(int[] array, int left, int right) {
if (left >= right) {
return;
}
int center = (left + right) / 2;
sort(array, left, center);
sort(array, center + 1, right);
mergesort(array, left, center, right);
}
private static void mergesort(int[] array, int left, int center, int right) {
int[] tempArray = new int[array.length];
int mid = center + 1;
int third = left;
int temp = left;
while (left <= center && mid <= right) {
if (array[left] <= array[mid]) {
tempArray[third++] = array[left++];
} else {
tempArray[third++] = array[mid++];
}
}
while (left <= center) {
tempArray[third++] = array[left++];
}
while (mid <= right) {
tempArray[third++] = array[mid++];
}
while (temp <= right) {
array[temp] = tempArray[temp++];
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
2、快速排序的平均時間複雜度是多少?最壞時間複雜度是多少?在哪些情況下會遇到最壞的時間複雜度。快速排序的平均時間複雜度 O(nlogn),最壞時間複雜度 O(n^2)。
最壞情況:待排序的序列為正序或者逆序。每次劃分只得到一個比上一次劃分少一個記錄的子序列,另一個為空。如果遞迴樹畫出來,它就是一棵斜樹。
當每次 pivot 選擇恰好都把列表元素分成了(1,n-1)。
採取措施:pivot 的選取是通過 random 來進行 。
3、各個排序演算法的穩定性,並給出理由。
選擇排序、快速排序、希爾排序、堆排序不是穩定的排序演算法,而氣泡排序、插入排序、歸併排序和基數排序是穩定的排序演算法。
4、兩個單項鍊表求交點。單向連結串列有交點意思是交點後的節點都是一樣的;因此,如果兩個單向連結串列相交,是成 Y 字形的。
思路:求出第一個連結串列長 m,第二個長 n。假設 m>=n,那麼就去掉第一個連結串列的前 m-n 個元素,使之等長,
然後依次比較第一個、第二個、第三個元素,直到找到或者結束。
public class FindNode {
public Node findNode(Node head1, Node head2) {
Node p1 = head1;
Node p2 = head2;
int i = 1, j = 1, k = 0, f = 0;
if (head1 == null || head2 == null) {
return null;
}
while (p1.next != null) {
p1 = p1.next;
i++;
}
while (p2.next != null) {
p2 = p2.next;
j++;
}
if (p1 != p2) {
return null;
} else {
p1 = head1;
p2 = head2;
f = Math.abs(i - j);
if (i > j) {
for (k = 0; k < f; k++) {
p1 = p1.next;
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
} else {
for (k = 0; k < f; k++) {
p2 = p2.next;
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
}
}
}
class Node {
int data;
Node next;
Node(int data) {
this.data = data;
}
}
5、遞增數列中每一項都可以表示為3^i*5^j*7^k(0<=i,j,k),即
1,3,5,7,9,15,21,25,27…,實現演算法,求出該數列中的第n項。先放置幾個佇列:
L1: 3^i (i >=0)
L2: 3^i * 5^j (j >= 1)
L3: 3^i * 5^j * 7^k (k >= 1)
Step1: 清空三個佇列、 分別把 3,5,7 放入三個佇列的首位。 準備一個新佇列 L(目前為空)。
Step2: 比較三個佇列的頭,找出最小的那個。把這個元素從隊列出隊,並加到 L 的尾部。
Step3:如果 Step2 中的元素是從 L1 中獲取的,假設是 3^m,則在 L1 的尾部加入 3^(m+1),L2 的尾部加入 3^m*5,L3 的尾部加入 3^m*7。
如果 Step2 中的元素是從 L2 中獲取的,假設是 3^m * 5^n,則在 L2 的尾部加入 3^m * 5^(n+1),L3 的尾部加入 3^m * 5^n *7。
如果 Step3 中的元素是從 L3 中獲取的,假設是 3^m * 5^n * 7^p,則在 L3 的尾部加入 3^m *5^n * 7^(p+1)。
Step4: L 的長度到達 N 了嗎?如果沒到達,重複
Step2-Step4。如果到達,則進行
Step5。
Step5: 取得 L 的末尾元素,即為所求。
import java.util.*;
public class Print357 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] a = new int[sc.nextInt()];
a[1] = 1;
TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
Deque<Integer> Q3 = new ArrayDeque<Integer>();
Deque<Integer> Q5 = new ArrayDeque<Integer>();
Deque<Integer> Q7 = new ArrayDeque<Integer>();
map.put(3, 3);
map.put(5, 5);
map.put(7, 7);
for (int i = 2; i < a.length; i++) {
if (map.isEmpty()) {
break;
}
Map.Entry<Integer, Integer> e = map.pollFirstEntry();
int key = e.getKey();
int val = e.getValue();
if (val == 5) {
Q5.add(key * 5);
map.put(Q5.pollFirst(), 5);
} else if (val == 3) {
Q5.add(key * 5);
Q3.add(key * 3);
map.put(Q3.pollFirst(), 3);
} else {
Q5.add(key * 5);
Q3.add(key * 3);
Q7.add(key * 7);
map.put(Q7.pollFirst(), 7);
}
a[i] = key;
}
while (sc.hasNext()) {
System.out.println(a[sc.nextInt()]);
}
}
}
6、走臺階問題,一次可以走1,2,3級,都
N級臺階的方法數 。初始:f(0) = 0; f(1) =1; f(2) = 1 + 1 = 2;
遞推公式:f(n) = f(n - 1) + f(n-2) + f(n - 3)
public class JumpFloor {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(getSum(sc.nextInt()));
}
private static int getSum(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
return getSum(n - 1) + getSum(n - 2) + getSum(n - 3);
}
}
7、10進位制數轉2
進位制數題目:2 進位制除了 0,1,還可以用 2 表示。例如:
1-> 1
2-> 10 or 02
3->11
4 ->100 or 020 or 012
問題:這樣一個十進位制數轉為二進位制數,就不是唯一的了。現求十進位制數 N 轉換為這種二進位制數的所有表示方法數。
f(0)=1, f(1)=1, f(2)=2,
f(n) = f( (n-1)/2) 當 n 為奇數
f(n)= f(n/2)+f((n-2)/2 )當 n 為偶數
public class TenToTwo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(getRes(sc.nextInt()));
}
private static int getRes(int n) {
if (n == 0) {
return 1;
}
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
if (n % 2 == 1) {
return getRes((n - 1) / 2);
} else {
return getRes(n / 2) + getRes((n - 2) / 2);
}
}
}
8、一個環狀連結串列(收尾相連),兩個指標 head1和head2
從同一個節點出發,head1每次走一步,
head2 每次走兩步,請證明,兩個指標第一次相遇於出發的節點。設兩個指標走的次數為 x,使用簡單的數學公式即可證明。難度 1 面。考察基本的數學 知識。
設連結串列有 m 個元素,head1 在第一次相遇時走了 n 步,c 為 head1 和 head2 第一次相遇的節點距離出發
節點的距離。
則: head1 走過的路程為 c = n;
head2 走過的路程為 c + k *m = 2n; (k 為整數)
因此,c = k*m,即 c 恰好是連結串列長度的整數倍,即兩個指標第一次相遇一定是在出發的節點。
9、一個連結串列中含有環。請找出環的起始節點 。
讓兩個指標 head1 和 head2 從同一個節點出發,head1 每次走一步,head2 每次走兩步,當二者重合時,讓 head2 回到連結串列的頭部,
以每次一步的步長走,當 head1 和 head2 再次相遇時,就是環路的起始節點了。
10、給定 N 個數,其中有一個數的出現次數超過N/2,請找出這個數,O(
n)演算法 。
private static int moreThanHalfNumber(int[] array) {
if (array == null || array.length == 0) {
return 0;
}
int mid = array.length / 2;
int start = 0;
int end = array.length - 1;
int index = partition(array, start, end);
while (index != mid) {
if (index >= mid) {
end = index - 1;
index = partition(array, start, end);
} else {
start = index + 1;
index = partition(array, start, end);
}
}
int res = array[mid];
if (!checkMoreThanHalf(array, res)) {
res = 0;
}
return res;
}
private static boolean checkMoreThanHalf(int[] array, int res) {
int times = 0;
for (int i = 0; i < array.length; i++) {
if (array[i] == res) {
times++;
}
}
boolean isMoreThanHalf = true;
if (times < array.length / 2) {
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
private static int partition(int[] array, int start, int end) {
int key = array[(int) (start + (int) Math.random() * (end - start))];
while (start < end) {
while (start < end && array[end] >= key) {
end--;
}
swap(array, start, end);
while (start < end && array[start] <= key) {
start++;
}
swap(array, start, end);
}
return start;
}
11、最長連續子序列之和(和最接近0的子序列),環形陣列的最大子序列和。
12、字串按字母 a-z 排序
題目要求:
(1)不是用排序庫函式;
(2)程式碼實現;
(3)演算法複雜度估算;
(4)測試自己的程式碼;
(5)能否用另一種排序方式,比較優缺點;(plus)
13、已知一個亂序的整數陣列,求該陣列排序相鄰兩數的最大間隔,要求時間複雜度為 O(n)。
14、求兩個相同大小已排序陣列的中位數 。
15、已知一個數組 a1, a2, ..., an, b1, b2, ..., bn,設計一個演算法把陣列變成a1, b1, a2, b2, ..., an,bn。
private static int[] swapPrintArray(int[] a, int[] b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
if (a == null && b == null) {
return null;
}
int[] res = new int[a.length + b.length];
int i = 0;
int j = 0;
int k = 0;
while (i < a.length && j < b.length) {
if (i == j) {
res[k++] = a[i++];
} else if (i > j) {
res[k++] = b[j++];
}
}
while (i < a.length) {
res[k++] = a[i++];
}
while (j < b.length) {
res[k++] = b[j++];
}
return res;
}
16、全排序演算法。
全排序演算法就是列舉一些字元的所有排列順序 。
private static void arrange(String[] str, int start, int len) {
if (start == len - 1) {
for (int i = 0; i < len; i++) {
System.out.print(str[i] + " ");
}
System.out.println();
total++;
} else {
for (int i = start; i < len; i++) {
swap(str, start, i);
arrange(str, start + 1, len);
swap(str, start, i);
}
}
}
private static void swap(String[] str, int i, int j) {
String tmp = new String();
tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}