本文首發於微信公衆號「玉剛說」java
原文連接:面試必備:List 算法面試
題目:請利用數組實現一個簡易版的List,須要實現poll和push兩個接口,前者爲移除並得到隊頭元素,後者爲向隊尾添加一個元素,並要求可以自動擴容。算法
仍是以「hello world」爲例,做圖分析下。
數組
注:
(1) 初始化List,數組默認容量len爲8,size=0。(容量小一點方便做圖,實際容量看需求而定。)微信
(2) 隊尾添加字符 h ,size++。app
(3) 添加len-1個字符後,size指向數組最後一個位置。ide
(4) 若是再添加字符 O ,因爲size++知足條件:大於等於len,此時須要先對List先擴容,擴容後,再進行添加字符操做。測試
(5) 接着繼續添加,直到「hello world」都push到List中。ui
(6) 這是一個poll過程,能夠看出即獲取了對頭元素 h ,而且整個數組中元素向左移動一位來實現移除效果。this
關於擴容:每次擴容多少?上圖例子是變爲原來的2倍。像ArrayList則是這樣 int newCapacity = oldCapacity + (oldCapacity >> 1),能夠看出擴容後大小 = 原來大小 + 原來大小/2。因此擴容多少由你本身決定。
此題關鍵是在怎麼實現poll和push兩個接口上:
push(添加元素):按索引添加到數組中,size大於等於數組長度時就先擴容。
poll(獲取並移動對頭元素):移動數組並置空最後一個元素。
Java實現
private static final int DEFAULT_CAPACITY = 16;
private Object[] elementData;
// 實際存儲的元素數量
// The size of the List (the number of elements it contains).
private int size;
public CustomList() {
elementData = new Object[DEFAULT_CAPACITY];
}
public CustomList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = new Object[DEFAULT_CAPACITY];
} else {
throw new IllegalArgumentException("Illegal Capacity: " +
initialCapacity);
}
}
/**
* 移除並得到隊頭元素
*
* @return
*/
public Object poll() {
if (size <= 0){
throw new IndexOutOfBoundsException(" list is empty .");
}
// 獲取隊頭第一個元素
Object oldValue = elementData[0];
// 數組元素左移一位 & 最後一位元素置空
System.arraycopy(elementData, 1, elementData, 0, size - 1);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* 向隊尾添加一個元素
*
* @param item
*/
public void push(Object item) {
ensureExplicitCapacity(size + 1); // Increments modCount!!
elementData[size++] = item;
}
@Override
public String toString() {
return Arrays.toString(elementData);
}
// 這裏擴容參考的ArrayList,具體實現請點擊文末源代碼連接前往查看。
private void ensureExplicitCapacity(int minCapacity) {
// 指望的最小容量大於等於現有數組的長度,則進行擴容
if (minCapacity - elementData.length >= 0)
grow(minCapacity);
}
複製代碼
C++實現
class List {
private:
int expansionSize = 16;//每次擴容個數
int elemsSize = 0;//數組長度
int dataIndex = -1;//最後一位元素下標
T* elems; //元素
public:
List(){
elemsSize = 0;
dataIndex = -1;
}
List(int initialCapacity){
if (initialCapacity<=0) {
throw out_of_range("initialCapacity must > 0");
}
elemsSize = initialCapacity;
elems = new T[initialCapacity];
}
void push(T const&); // 入棧
T poll(); // 出棧
int size();
~List(){
if(elemsSize>0){
delete []elems;
}
}
};
template <class T>
void List<T>::push (T const& elem)
{
if(elemsSize <= 0){//初始化數組
elemsSize = expansionSize;
elems = new T[elemsSize];
}
if(dataIndex+1 >= elemsSize){//數組擴容
elemsSize += expansionSize;
T* newElems = new T[elemsSize];
for(int i=0;i<=dataIndex;i++){
newElems[i] = elems[i];
}
delete[]elems;
elems = newElems;
}
dataIndex++;
elems[dataIndex] = elem;
}
template <class T>
T List<T>::poll ()
{
if (dataIndex<0) {
throw out_of_range("List<>::poll(): empty List");
}
T poll = elems[0]; //獲取第一位
for(int i=1;i<=dataIndex;i++){//後面元素向左移
elems[i-1] = elems[i];
}
dataIndex--;
return poll;
}
template <class T>
int List<T>::size ()
{
return dataIndex+1;
}
複製代碼
題目: 一個整數數組中有一個數字出現的次數超過了數組長度的一半,請找出這個數字。如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2},因爲2出現了5次,超過了數組長度的一半,所以應輸出2。
若是咱們將數組排序,那麼排序後位於數組中間的的數字必定是那個出現次數超過數組長度一半的數字。這個數就是統計學上的中位數。
此題關鍵在於快速排序算法,咱們一塊兒看看下面這張圖,來理解下快排的思想。
快速排序使用分治法(Divide and conquer)策略來把一個序列(list)分爲兩個子序列(sub-lists)。
步驟爲:
遞歸到最底部時,數列的大小是零或一,也就是已經排序好了。這個算法必定會結束,由於在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。
Java實現
private int v4_0_solution(int[] array) {
if (array == null || array.length < 1) {
throw new IllegalArgumentException(" array is empty. ");
}
int head = 0;
int tail = array.length - 1;
// 快速排序
qSort(array, head, tail);
int middle = array.length >> 1;
int result = array[middle];
// 判斷中位數是否爲超過數組長度一半的數。
if (checkMoreThanHalf(array, result)) {
return result;
} else {
throw new IllegalArgumentException("not find the number.");
}
}
public void qSort(int[] arr, int head, int tail) {
// 參數合法性及結束條件
if (head >= tail || arr == null || arr.length <= 1) {
return;
}
// 取中間數爲基準值
int i = head, j = tail, pivot = arr[(head + tail) / 2];
while (i <= j) {
// 處理大於等於基準數狀況
while (arr[i] < pivot) {
++i;
}
while (arr[j] > pivot) {
--j;
}
// 直接互換,沒有基準數歸位操做
if (i < j) {
swap(arr, i, j);
++i;
--j;
} else if (i == j) {
++i;
}
}
// 遞歸處理基準數分隔的兩個子數列。
qSort(arr, head, j);
qSort(arr, i, tail);
}
private boolean checkMoreThanHalf(int[] nums, int number) {
int times = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == number) {
times++;
}
}
return times * 2 > nums.length;
}
複製代碼
C++ 實現
// 快速排序:遞歸方式 參考Wikipedia
void quick_sort_recursive(int arr[], int start, int end) {
if (start >= end)
return;
int mid = arr[end];
int left = start, right = end - 1;
while (left < right) {
while (arr[left] < mid && left < right)
left++;
while (arr[right] >= mid && left < right)
right--;
std::swap(arr[left], arr[right]);
}
if (arr[left] >= arr[end])
std::swap(arr[left], arr[end]);
else
left++;
quick_sort_recursive(arr, start, left - 1);
quick_sort_recursive(arr, left + 1, end);
}
int main()
{
//存在出現次數超過數組長度一半的數字
//int data[] = {1, 2, 3, 2, 2, 2, 5, 4, 2};
//不存在出現次數超過數組長度一半的數字
//int data[] = {4, 5, 1, 6, 2, 7, 3, 8};
// 出現次數超過數組長度一半的數字都出如今數組的前/後半部分
int data[] = {2, 2, 2, 2, 2, 1, 3, 4, 5};
//int data[] = {1, 3, 4, 5, 2, 2, 2, 2, 2};
int len = sizeof(data)/sizeof(int);
printf("length = %d \n", len);
quick_sort_recursive(data, 0, len -1);
for(int i=0;i<len;i++){
printf(" %d ", data[i]);
}
printf("\n");
int middle = len >> 1;
int result = data[middle];
if(CheckMoreThanHalf(data, len, result)){
printf("the number is %d ", result);
}else{
printf("not find the number.");
}
return 0;
}
複製代碼
有經驗的面試官又來了,題目難度須要升下級,😶~
題目:這個題目有不少變種,其中一個引伸爲輸入的是一個對象數組,該對象沒法比較大小,只能利用equals()方法比較是否相等,此時該如何解(若要用到O(n)的輔助空間,可否避免?)。
數組中有一個元素出現的次數超過數組長度的一半,也就是說它出現的次數比其餘全部元素出現次數的和還要多。
所以咱們能夠考慮在遍歷數組的時候保存兩個值: 一個是數組中的一個元素, 一個是次數。當咱們遍歷到下一個元素的時候,若是下一個元素和咱們以前保存的元素相等(equals返回true),則次數加1;若是下一個元素和咱們以前保存的不相等,則次數減1。若是次數爲0,咱們須要保存下一個元素,並把次數設爲1。因爲咱們要找的數字出現的次數比其餘全部數字出現的次數之和還要多,那麼要找的數字確定是最後一次把次數設爲1時對應的那個元素。
怎麼樣簡單吧,仍是畫張圖來理解一下。
注:雖然途中數組元素類型是整型,但其思想適用於任何類型。
(1) 數組初始狀態,times只是一個標記變量,默認爲0, result爲最後一次設置times=1時的那個元素,默認爲NULL。
(2) 開始循環,i=0時,times設置爲1,並將第一個元素 1 賦值給result變量。
(3) i=1時,因爲此時Array[i]的值爲 2 ,不等於result,因此times--,操做後times等於0,result不變。
(4) i=2時,因爲此時times==0,因此從新設置times=1,result= Array[2]= 3 。
(5) i=3時,和(3)相似,因爲此時Array[i]的爲2,不等於result,因此times--,操做後times等於0,result不變仍是等於3。
(6) 依次邏輯,一直遍歷到末尾,即i=8時,邏輯同上,能夠求出times=1,result=2;ok,循環結束。
到這裏得出result=2,那這個2是否是咱們要找的那個元素呢? 答案是:不必定。 若是輸入數組中存在次數超過超過數組長度一半的數,那result就是那個數,不然就不是。因此,咱們還須要對這個數進行檢查,檢查過程請參看下方代碼。
此思路:空間複雜度O(1),時間複雜度O(n)。
Java實現
private Object v4_1_solution(Object[] objects) {
if (objects == null || objects.length < 1) {
throw new IllegalArgumentException(" array is empty. ");
}
// 假設第一個元素就是超過長度一半的那個
Object result = objects[0];
int times = 1;
// 從第二個元素開始遍歷
for (int i = 1; i < objects.length; i++) {
if (times == 0) {
// 從新設置
result = objects[i];
times = 1;
} else if (objects[i].equals(result)) {
times++;
} else {
times--;
}
}
if (checkMoreThanHalf(objects, result)) {
return result;
} else {
throw new IllegalArgumentException(" array is invalid ");
}
}
private boolean checkMoreThanHalf(Object[] objects, Object obj) {
int times = 0;
for (int i = 0; i < objects.length; i++) {
if (objects[i].equals(obj)) {
times++;
}
}
return times * 2 > objects.length;
}
// 測試類,重點在於實現了equals和hashcode方法。
private static class TestObject {
String unique;
public TestObject(String unique) {
this.unique = unique;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestObject that = (TestObject) o;
return unique != null ? unique.equals(that.unique) : that.unique == null;
}
@Override
public int hashCode() {
return unique != null ? unique.hashCode() : 0;
}
@Override
public String toString() {
return "TestObject{" +
"unique='" + unique + '\'' +
'}';
}
}
複製代碼
C++實現
template <class T>
class Array {
private:
bool checkMoreThanHalf(T *objects,unsigned int len,T obj)
{
unsigned int times = 0;
for (int i = 0; i < len; i++) {
if (objects[i] == obj) {
times++;
}
}
return times * 2 > len;
};
public:
T v4_1_solution(T *objects,unsigned int len);
};
template <class T>
T Array<T>::v4_1_solution (T *objects,unsigned int len)
{
if (!objects || len < 1) {
throw out_of_range(" array is empty. ");
}
// 假設第一個元素就是超過長度一半的那個
T result = objects[0];
if(len == 1){
return result;
}
int times = 1;
// 從第二個元素開始遍歷
for (int i = 1; i < len; i++) {
if (times == 0) {
// 從新設置
result = objects[i];
times = 1;
} else if (objects[i] == result) {
times++;
} else {
times--;
}
}
if (checkMoreThanHalf(objects,len, result)) {
return result;
} else {
throw out_of_range(" array is invalid ");
}
}
複製代碼