1.判斷鏈表是否存在環型鏈表問題:判斷一個鏈表是否存在環,例如下面這個鏈表就存在一個環:
例如N1->N2->N3->N4->N5->N2就是一個有環的鏈表,環的開始結點是N5這裏有一個比較簡單的解法。設置兩個指針p1,p2。每次循環p1向前走一步,p2向前走兩步。直到p2碰到NULL指針或者兩個指針相等結束循環。如果兩個指針相等則說明存在環。
struct link
{
int data;
link* next;
};
bool IsLoop(link* head)
{
link* p1=head, *p2 = head;
if (head ==NULL || head->next ==NULL)
{
return false;
}
do{
p1= p1->next;
p2 = p2->next->next;
} while(p2 && p2->next && p1!=p2);
if(p1 == p2)
return true;
else
return false;
}
2,鏈表反轉 單向鏈表的反轉是一個經常被問到的一個面試題,也是一個非常基礎的問題。比如一個鏈表是這樣的: 1->2->3->4->5 通過反轉後成爲5->4->3->2->1。最容易想到的方法遍歷一遍鏈表,利用一個輔助指針,存儲遍歷過程中當前指針指向的下一個元素,然後將當前節點元素的指針反轉後,利用已經存儲的指針往後面繼續遍歷。源代碼如下:
struct linka {
int data;
linka* next;
};
void reverse(linka*& head)
{
if(head ==NULL)
return;
linka*pre, *cur, *ne;
pre=head;
cur=head->next;
while(cur)
{
ne = cur->next;
cur->next = pre;
pre = cur;
cur = ne;
}
head->next = NULL;
head = pre;
}
還有一種利用遞歸的方法。這種方法的基本思想是在反轉當前節點之前先調用遞歸函數反轉後續節點。源代碼如下。不過這個方法有一個缺點,就是在反轉後的最後一個結點會形成一個環,所以必須將函數的返回的節點的next域置爲NULL。因爲要改變head指針,所以我用了引用。算法的源代碼如下:
linka* reverse(linka* p,linka*& head)
{
if(p == NULL || p->next == NULL)
{
head=p;
return p;
}
else
{
linka* tmp = reverse(p->next,head);
tmp->next = p;
return p;
}
}
3,判斷兩個數組中是否存在相同的數字 給定兩個排好序的數組,怎樣高效得判斷這兩個數組中存在相同的數字?
這個問題首先想到的是一個O(nlogn)的算法。就是任意挑選一個數組,遍歷這個數組的所有元素,遍歷過程中,在另一個數組中對第一個數組中的每個元素進行binary search。用C++實現代碼如下:
bool findcommon(int a[],int size1,int b[],int size2)
{
int i;
for(i=0;i<size1;i++)
{
int start=0,end=size2-1,mid;
while(start<=end)
{
mid=(start+end)/2;
if(a[i]==b[mid])
return true;
else if (a[i]<b[mid])
end=mid-1;
else
start=mid+1;
}
}
return false;
}
後來發現有一個 O(n)算法。因爲兩個數組都是排好序的。所以只要一次遍歷就行了。首先設兩個下標,分別初始化爲兩個數組的起始地址,依次向前推進。推進的規則是比較兩個數組中的數字,小的那個數組的下標向前推進一步,直到任何一個數組的下標到達數組末尾時,如果這時還沒碰到相同的數字,說明數組中沒有相同的數字。
bool findcommon2(int a[], int size1, int b[], int size2)
{
int i=0,j=0;
while(i<size1 && j<size2)
{
if(a[i]==b[j])
return true;
if(a[i]>b[j])
j++;
if(a[i]<b[j])
i++;
}
return false;
}
例如N1->N2->N3->N4->N5->N2就是一個有環的鏈表,環的開始結點是N5這裏有一個比較簡單的解法。設置兩個指針p1,p2。每次循環p1向前走一步,p2向前走兩步。直到p2碰到NULL指針或者兩個指針相等結束循環。如果兩個指針相等則說明存在環。
struct link
{
int data;
link* next;
};
bool IsLoop(link* head)
{
link* p1=head, *p2 = head;
if (head ==NULL || head->next ==NULL)
{
return false;
}
do{
p1= p1->next;
p2 = p2->next->next;
} while(p2 && p2->next && p1!=p2);
if(p1 == p2)
return true;
else
return false;
}
2,鏈表反轉 單向鏈表的反轉是一個經常被問到的一個面試題,也是一個非常基礎的問題。比如一個鏈表是這樣的: 1->2->3->4->5 通過反轉後成爲5->4->3->2->1。最容易想到的方法遍歷一遍鏈表,利用一個輔助指針,存儲遍歷過程中當前指針指向的下一個元素,然後將當前節點元素的指針反轉後,利用已經存儲的指針往後面繼續遍歷。源代碼如下:
struct linka {
int data;
linka* next;
};
void reverse(linka*& head)
{
if(head ==NULL)
return;
linka*pre, *cur, *ne;
pre=head;
cur=head->next;
while(cur)
{
ne = cur->next;
cur->next = pre;
pre = cur;
cur = ne;
}
head->next = NULL;
head = pre;
}
還有一種利用遞歸的方法。這種方法的基本思想是在反轉當前節點之前先調用遞歸函數反轉後續節點。源代碼如下。不過這個方法有一個缺點,就是在反轉後的最後一個結點會形成一個環,所以必須將函數的返回的節點的next域置爲NULL。因爲要改變head指針,所以我用了引用。算法的源代碼如下:
linka* reverse(linka* p,linka*& head)
{
if(p == NULL || p->next == NULL)
{
head=p;
return p;
}
else
{
linka* tmp = reverse(p->next,head);
tmp->next = p;
return p;
}
}
3,判斷兩個數組中是否存在相同的數字 給定兩個排好序的數組,怎樣高效得判斷這兩個數組中存在相同的數字?
這個問題首先想到的是一個O(nlogn)的算法。就是任意挑選一個數組,遍歷這個數組的所有元素,遍歷過程中,在另一個數組中對第一個數組中的每個元素進行binary search。用C++實現代碼如下:
bool findcommon(int a[],int size1,int b[],int size2)
{
int i;
for(i=0;i<size1;i++)
{
int start=0,end=size2-1,mid;
while(start<=end)
{
mid=(start+end)/2;
if(a[i]==b[mid])
return true;
else if (a[i]<b[mid])
end=mid-1;
else
start=mid+1;
}
}
return false;
}
後來發現有一個 O(n)算法。因爲兩個數組都是排好序的。所以只要一次遍歷就行了。首先設兩個下標,分別初始化爲兩個數組的起始地址,依次向前推進。推進的規則是比較兩個數組中的數字,小的那個數組的下標向前推進一步,直到任何一個數組的下標到達數組末尾時,如果這時還沒碰到相同的數字,說明數組中沒有相同的數字。
bool findcommon2(int a[], int size1, int b[], int size2)
{
int i=0,j=0;
while(i<size1 && j<size2)
{
if(a[i]==b[j])
return true;
if(a[i]>b[j])
j++;
if(a[i]<b[j])
i++;
}
return false;
}
4,最大子序列 問題:
給定一整數序列A1, A2,... An (可能有負數),求A1~An的一個子序列Ai~Aj,使得Ai到Aj的和最大
例如:
整數序列-2, 11, -4, 13, -5, 2, -5, -3, 12, -9的最大子序列的和爲21。
對於這個問題,最簡單也是最容易想到的那就是窮舉所有子序列的方法。利用三重循環,依次求出所有子序列的和然後取最大的那個。當然算法複雜度會達到O(n^3)。顯然這種方法不是最優的,下面給出一個算法複雜度爲O(n)的線性算法實現,算法的來源於Programming Pearls一書。 在給出線性算法之前,先來看一個對窮舉算法進行優化的算法,它的算法複雜度爲O(n^2)。其實這個算法只是對對窮舉算法稍微做了一些修改:其實子序列的和我們並不需要每次都重新計算一遍。假設Sum(i, j)是A[i] ... A[j]的和,那麼Sum(i, j+1) = Sum(i, j) + A[j+1]。利用這一個遞推,我們就可以得到下面這個算法:
int max_sub(int a[],int size)
{
int i,j,v,max=a[0];
for(i=0;i<size;i++)
{
v=0;
for(j=i;j<size;j++)
{
v=v+a[j];//Sum(i, j+1) = Sum(i, j) + A[j+1]
if(v>max)
max=v;
}
}
return max;
}
那怎樣才能達到線性複雜度呢?這裏運用動態規劃的思想。先看一下源代碼實現:
int max_sub2(int a[], int size)
{
int i,max=0,temp_sum=0;
for(i=0;i<size;i++)
{
temp_sum+=a[i];
if(temp_sum>max)
max=temp_sum;
else if(temp_sum<0)
temp_sum=0;
}
return max;
}
在這一遍掃描數組當中,從左到右記錄當前子序列的和temp_sum,若這個和不斷增加,那麼最大子序列的和max也不斷增加(不斷更新max)。如果往前掃描中遇到負數,那麼當前子序列的和將會減小。此時temp_sum 將會小於max,當然max也就不更新。如果temp_sum降到0時,說明前面已經掃描的那一段就可以拋棄了,這時將temp_sum置爲0。然後,temp_sum將從後面開始將這個子段進行分析,若有比當前max大的子段,繼續更新max。這樣一趟掃描結果也就出來了。
5, 找出單向鏈表的中間結點 這道題和解判斷鏈表是否存在環,我用的是非常類似的方法,只不過結束循環的條件和函數返回值不一樣罷了。設置兩個指針p1,p2。每次循環p1向前走一步,p2向前走兩步。當p2到達鏈表的末尾時,p1指向的時鏈表的中間。
link* mid(link* head)
{
link* p1,*p2;
p1=p2=head;
if(head==NULL || head->next==NULL)
return head;
do {
p1=p1->next;
p2=p2->next->next;
} while(p2 && p2->next);
return p1;
}
冒泡排序算法具體代碼
#include <iostream.h>
void BubbleSort(int* pData,int Count)
{
int iTemp;
for(int i=1;i<Count;i++)
{
f or(int j=Count-1;j>=i;j--)
{
if(pData[j]<pData[j-1])
{
iTemp = pData[j-1];
pData[j-1] = pData[j];
pData[j] = iTemp;
}
}
}
}
f or(int j=Count-1;j>=i;j--)
{
if(pData[j]<pData[j-1])
{
iTemp = pData[j-1];
pData[j-1] = pData[j];
pData[j] = iTemp;
}
}
}
}