1. 程式人生 > >我所理解的要點和難點——劍指offer簡要筆記

我所理解的要點和難點——劍指offer簡要筆記

1、Singleton模式。設計一個類,只能生成該類的一個例項

考慮多執行緒環境,加同步鎖。

2、替換空格。把字串中每個空格換成“%20”

從字串的後面開始複製和替換,準備兩個指標,P1指向原始字串末尾,P2指向替換之後的字串末尾。

3、從尾到頭列印連結串列

方法1:棧  方法2:遞迴

4、重建二叉樹。已知前序遍歷和中序遍歷

前序遍歷第一個節點是根結點,中序遍歷根結點左邊為左子樹的結點,右邊為右子樹的結點;

通過遞迴構建左右子樹

BinaryTreeNode * ConstructCore(int*startPreorder,int* endPreorder,int* startInorder,int* endInorder);

5、兩個棧實現佇列。(兩個佇列實現棧)

template<typename T> voidCQueue<T>::appendTail(const T& element){

stack.push(element);

}

Template<typename T> TCQueue<T>::deleteHead(){

//略

}

6、旋轉陣列的最小數字

常規演算法時間複雜度O(n);

利用二分查詢法並結合旋轉陣列的特性,將時間複雜度降為O(logn);

用兩個指標分別指向陣列第一個元素和最後一個元素,和中間元素進行比較。

7、斐波那契數列

動態規劃實現,時間複雜度為O(n);

通過生僻數學公式實現,時間複雜度為O(logn)。

8、位運算。輸入一個整數,輸出該數二進位制表示中1的個數。

方法:把一個整數減去1,再和原整數做與運算,會把該整數最右邊一個1變成0。那麼一個整數的二進位制表示中有多少個1,就可以進行多少次操作。

與(&)、或(|)、異或(^)、左移(<<)、右移(>>)

左移n位時,最左邊的n位將被丟棄,同時在最右邊補上n個0;

右移n位時,最右邊的n位將被丟棄;處理左邊是,如果數字是一個無符號數值,則用0填補,如果數字是一個有符號數值,則用數字的符號位填補。

把整數右移一位和把整數除以2在數學上是等價的,但是除法的效率比移位運算要低得多。

正數邊界:1、0x7FFFFFFF

負數邊界:0x80000000、0xFFFFFFFF

9、3種錯誤處理方式

返回值:windows中很多API返回值為0代表API呼叫成功,而非零值定義了不同的意義,用來判斷出錯的原因。優點是和系統API一致;缺點是不能方便的使用計算結果。

全域性變數:能方便使用計算結果;缺點是使用者可能忘記檢查全域性變數。

異常:為不同出錯原因定義不同的異常型別,邏輯清晰;缺點是很多語言比如C語言不支援異常,而且丟擲異常時,程式的執行會打亂正常順序,對效能有很大影響。

10、數值的整數次方

double Power(double base,int exponent)

考慮的問題:如果base等於0,exponent小於0,返回0,通過全域性變數bool g_InvalidInput標識出錯;

           判斷base等於0,應該定義函式equal(base,0)不能直接base == 0,因為計算機在表示小數時都有誤差,只能判斷絕對值是不是在一個很小的範圍內(base-0>-0.0000001)&&(base-0<0.0000001)。

11、列印1到最大的n位數

解法1:需要考慮大數問題,於是通過字串模擬數字加法;

解法2:轉換為數字排列的解法,然後將不是以0開始的字串列印。

12、指標被delete後只是釋放了所指記憶體,指標的指向沒有變,所以需要將指標指向NULL

delete pNext;

pNext = NULL;

13、調整陣列順序使奇數位於偶數前面(或負數位於整數前面、或其他……)

考慮擴充套件性,利用函式指標:

void Reorder( int *pData,unsigned intlength,bool ( *func ) ( int ) ){

func(5);

}

bool isEven( int ) {

}

函式呼叫:Reorder( pData , length , isEven );

14、找出最小的K個數

解法1:基於Partition函式來解決,當index = =k-1時,陣列左邊的就是需要找的k個值。時間複雜度為O(n)。

解法2:O(nlogk)的演算法,適合處理海量資料。建立一個大小為k的容器來儲存k個數字,紅黑樹的查詢、刪除和插入操作都在O(logk)時間,在STL中set和multiset都是基於紅黑樹的。

15、連續子陣列的最大和:動態規劃

16、把陣列排成最小的數{ 3,23,321}

通過自定義的比較函式來實現:兩個數字m和n能拼成mn和nm,比較這兩個數的大小來確定m和n的大小。

17、求第n個醜數(只包含因子2、3和5的數)

建立陣列儲存已經找到的醜數,用空間換時間的解法。

18、第一次只出現一次的字元:利用雜湊表,時間複雜度O(n)

19、求陣列中的逆序對

利用歸併排序的方法,時間複雜度為O(nlogn),同時需要一個長度為n的輔助陣列。

20、數字在排序陣列中出現的次數

順序掃描的時間複雜度為O(n)

通過二分查詢第一個出現的需查詢數字和最後一個出現的需查詢數字的下標;

然後計算下標差,就能得到該數出現的次數。時間複雜度為O(logn)。

21、陣列中只出現一次的數字

一個整型陣列除了兩個陣列之外,其他的陣列都出現了兩次,找出這兩個數字。要求時間複雜度為O(n),空間複雜度為O(1)

方法:首先從頭到尾異或,異或結果的二進位制表示中至少有一位為1;

     以第n位是不是1為標準把原陣列分為兩個陣列;

     出這兩個數字。

22、不用加減乘除做加法

while(num2 != 0){

sum = num1^num2;

carry = (num1 & num2);

num1 = sum;

num2 = carry;

}

return num1;

23、c++中,成員變數的初始化順序只與它們在類中的宣告順序有關,而與在建構函式的初始化列表的順序無關。

24、輸入兩個結點,求它們的最低公共祖先

分三種情況:1.該樹是二叉搜尋樹時,樹中從上到下第一個在兩個輸入結點的值之間的結點,就是最低公共祖先;

          2.樹中有指向父結點的指標pParent,則轉換成求兩個連結串列的第一個公共結點;

          3.該樹是普通的樹,則通過輔助記憶體,得到到某一結點的路徑,然後求出這兩條路徑的最後公共結點。

25、陣列中重複的數字

方法1:排序     方法2:雜湊表(需要輔助記憶體)    方法3:比較當前值和下標是否相等

26、字串中第一個不重複的字元

利用雜湊表(256),第一次遍歷統計不同字元個數,第二次遍歷找出第一個值為1的字元。

27、連結串列中環的入口結點

(1)指標P1和P2初始化時指向連結串列的頭結點;(2)通過一快一慢指標,從相遇結點出發,統計環中結點個數nodes,然後P1先在連結串列中移動nodes步;(3)P1和P2再同時移動,直到相遇,相遇結點就是環的入口結點。

28、刪除連結串列中重複的結點

考慮頭結點可能與後面的結點重複,因此刪除函式應該宣告為:

void deleteDuplication(ListNode** pHead)而不是voiddeleteDuplication(ListNode* pHead)

29、二叉樹中序遍歷的下一個結點(有左右子結點的指標和有一個指向父結點的指標)

如果結點有右子樹,則它的下一個結點是它右子樹中的最左子結點;

如果沒有右子樹,而且結點是它父結點的左子結點,那麼下一個結點就是父結點;

如果沒有右子樹,而且是它父結點的右子結點,則一直向上遍歷,直到一個是它父結點的左子結點。如果這樣的結點存在,那麼這個結點的父結點就是要找的下一個結點。

30、二叉樹列印成多行

利用queue進行層序遍歷;設一個變量表示當前層還沒列印的結點數,另一個變量表示下一層結點的數目。

31、按之字列印二叉樹

利用兩個棧來實現。