過年在家沒怎麼發博客,現在3月份在學校又被老闆逼着做他的控制理論。時間正是金錢,能擠出一點就是一點,下面是我自己遇到的比較好的算法題,有的是leetcode上的題目,有的是一些公司的面試題目。
一.查詢已排序的兩個數組的中位數
這也是leetcode的第二題,看似很簡單(如果不考慮時間因素的話確實很容易),但是考慮效率的話,這不就是隨便看看就能出來的。我一看到這道題,就想起我之前看到的歸併排序的合併方法,將已經排序的兩個數組合併爲一個,則中間那個數就是中位數,不過這個算法的時間效率是O(N+M)。下面給出我寫的合併方法,用的是java:
public class Solution {
public double findMedianSortedArrays(int A[], int B[]) {
//我這裏採用了歸併排序的merge方法 但是時間爲O(N+M);還有logN的算法
int i=0;
int j=0;
int pos=0;
int[] ss=new int[A.length+B.length];
while(i<A.length && j<B.length)
{
if(A[i]<B[j])
ss[pos++]=A[i++];
else
ss[pos++]=B[j++];
}
while(i<A.length)
{
ss[pos++]=A[i++];
}
while(j<B.length)
{
ss[pos++]=B[j++];
}
if((A.length+B.length)%2==0)
return (double)(ss[(A.length+B.length-1)/2]+ss[(A.length+B.length+1)/2])/2;
else
return (double)ss[(A.length+B.length-1)/2];
}
}
其實它有更好的算法,時間複雜度爲O(log[(N+M)/2])。即先取數組A和B的中位數k,進行比較,如下三種情況:k1爲數組A的中位數,k2爲數組B的中位數
且k=k1+k2
1.A[k1]>B[k2]:則數組B中前一半的數肯定不是我們要找的中位數
2.A[k1]<B[k2]:則數組A中前一半的數肯定不是我們要找的中位數
3.A[k1]=B[k2]:則A[k1]或者B[k2]就是我們要找的中位數
如果是上面情況的1或者2,則我們可以剔除A或B中的一半的數。這裏設爲情況2,在從新得到的數組中取中位數(已經剔除了一個數組的一半,則中位數爲k-k1),如果k/2大於數組的長度,則取該數組的最大值m,另一個數取k-k1-m。
遞歸直到一個數組爲0或者判段的兩個數相等,或者中位數取到了0,程序如下
C++版:
class Solution {
public:
double findMedianSortedArrays(int A[], int m, int B[], int n) {
int len = m+n;
if(len%2==0) {
return (rec(A, m, B, n, len/2) + rec(A, m, B, n, len/2+1))/2.0;
} else {
return rec(A,m,B,n,len/2+1);
}
}
double rec(int A[], int m, int B[], int n, int k) {
if(m<=0) return B[k-1];
if(m>n) return rec(B,n, A, m, k);
if(k<=1) return min(A[0], B[0]);
int pa = min(k/2, m);
int pb = k-pa;
if( A[pa-1]<B[pb-1] ) {
return rec(A+pa, m-pa, B, n, k-pa);
} else {
return rec(A, m, B+pb, n-pb, k-pb);
}
}
};
Java版:看上去的話用C++對數組處理更方便
public class Solution {
public double findMedianSortedArrays(int A[], int B[]) {
int len=A.length+B.length;
if(len%2==0)
return (rec(A,0,A.length-1,B,0,B.length-1,len/2)+rec(A,0,A.length-1,B,0,B.length-1,len/2+1))/2;
else
return rec(A,0,A.length-1,B,0,B.length-1,len/2+1); //這裏是k個個數,不是座標
}
//sa和sb分別代表需要數組A的開始和結束位置,需要保證小數組在前,這裏的k代表第k個小的元素
public double rec(int[] A,int sa,int ea,int[] B,int sb,int eb,int k)
{
int alen=ea-sa+1;
int blen=eb-sb+1;
if(alen>blen) //保證長度小的數組在前
return rec(B,sb,eb, A,sa,ea,k);
if(alen==0)
return B[sb+k-1];
if(k<=1)
return A[sa]<B[sb]?A[sa]:B[sb];
int pa=(alen<k/2)?alen:k/2;
int pb=k-pa;
if(A[sa+pa-1]<B[sb+pb-1])
return rec(A,sa+pa,ea,B,sb,eb,k-pa);
else
return rec(A,sa,ea,B,sb+pb,eb,k-pb);
}
}
這個問題引申爲對已排序的兩個數組,求它們的第k個小的元素(數組升序排列)。
二.質因數分解
質因數分解,給定一個整數,求出該數的所有質因數,如90=2*3*3*5;這個題目我很早就寫過。即從2開始,用n不斷相除,判斷是否有餘數,如果沒有餘數,用剛纔相除的數繼續除2,如果有餘數,則讓2+1,3作除數。一直循環直到被除數<除數。
雖然這個程序簡單,我還是把它列了出來,java版
public static void main(String[] args) {
int num=1092; //被除數
int div=2; //質因數
System.out.print(num+"=");
while(div<=num)
{
if(num%div==0)
{
num=num/div;
System.out.print(div);
if(div<num)
System.out.print("*");
}
else
div++;
}
}
三.求二叉樹的高度
求二叉樹的深度,當只有根節點的時候,二叉樹的深度爲1。看到這題,這裏的節點是不保存高度信息的,不像AVL樹。而且它就是一個普通的二叉樹。既然求高度,那對於某一個節點,必然要求它的左節點的高度和右節點的高度,然後比較它們,取較大的值再加1就是該節點的高度。想到這裏,應該就是採用遞歸的方法,也有非遞歸方法。java程序如下:
//求二叉樹的高度,根節點的高度爲1
public static int rec(Node node)
{
if(node==null)
return 0;
int rh=0;
int lh=0;
rh=rec(node.right);
lh=rec(node.left);
return (rh>lh)?rh+1:lh+1;
}
</pre><p>C++非遞歸算法 求高度:</p><p></p><pre class="cpp" name="code">int BiTreeDepthHierarchy(BiThrTree T) //非遞歸類層次遍歷求二叉樹深度
{
int depth=0,hp,tp,lc; //hp爲已訪問的結點數,tp歷史入隊的結點總數,lc爲每層最後一個結點標記
LinkQueue Q; BiThrNode *p;
if(T)
{
p=T;
hp=0;
tp=1;
lc=1;
InitQueue(Q);
EnQueue(Q,p);
while(!QueueEmpty(Q))
{
DeQueue(Q,p);
hp++; //hp爲已訪問的結點數
if(p->lchild)
{
EnQueue(Q,p->lchild);
tp++; //tp記錄歷史入隊的結點總數
}
if(p->rchild)
{
EnQueue(Q,p->rchild);
tp++;
}
if(hp==lc) //當hp=lc時,表明本層結點均已訪問完
{
depth++;
lc=tp; //lc=tp,更新下層的末結點標記
}
}
}
return depth;
}
三.求一個小寫字母串的最長不重複子串
如:字符串abcafegdcdf的最長不重複子字符串爲:bcafegd,相同長度取第一個。
一般像遇到字符串的題目,一般的做法時間肯定不是O(N^2),要麼是O(N),或者O(logN)或者 O(N*logN)。一般線性算法是最優的,所以要向這方面考慮。
這個問題:要設立一個int[]數組,數組座標代表27個字母,用如str.charAt()-‘a',數組的值代表該字母的座標。另設立一個pos代表沒遇到重複字母前的位置。對字符串循環遍歷,對每一個字符判斷,如果它每出現過,就設定它的值;如果它出現過,則判斷i到pos間的長度是否大於子串,如果大於就重新賦值。一次遍歷下來能求出不重複的子字符串。這一題的關鍵是設定一個pos代表位置,並且每個設定的int[]數組裏面存放的是每個字符的座標。java代碼如下:
//求給定字符串中最長不重複字符子串,因爲題目給的字符串只有小寫字母
public static String nomulStr(String str)
{
int[] con=new int[127]; //
String sub="";
int pos=-1;
for(int i=0;i<127;i++)
con[i]=-1; //因爲值要存放座標,因此不能使用默認的0,改爲-1
//foreach無法賦值
for(int i=0;i<str.length();i++)
{
if(con[str.charAt(i)]>pos)
pos=con[str.charAt(i)];
int max=i-pos;
if(max>sub.length())
sub=str.substring(pos+1,i+1);
con[str.charAt(i)]=i; //更新重複字母的座標
}
return sub;
}