【劍指offer】劍指offer 練習筆記

劍指offer練習

寫一個程序要注意:

1. 功能性測試(完成基本功能)

2. 邊界值測試(單獨處理邊界)

3. 負面測試(處理無效數據)

其餘的還有:

1. 變量命名規範化,可讀化 

2. 釋放一個內存時,要記得把引用這塊內存的指針置爲NULL

通知函數的調用者函數出錯有三種方式,比如第16題涉及到如果對0求負數冪,由於不能對0求倒數,應該提示調用出錯:

1. 返回值

2. 全局變量

3. 拋出異常

基本概念:

1. 魯棒性(健壯性)指程序能夠判斷輸入是否合乎規範要求,並對不符合要求的輸入予以合理的處理。

1. 賦值運算符

class CMyString
{
char* m_pData; 
int size; 
}
寫一個CMyString類的賦值運算符。
CMyString& operator = (const CMyString& s)
{
if( this == &s) return * this; 
if(m_pData) 
delete[] m_pDate;  // 或者delete m_pData; 也可以
m_pData = nullptr; 

m_pData = new char[s.size + 1]; 
strcpy(m_pData, s.m_pData); 
size = s.size; 
return * this;  
}
這種寫法在內存不足無法分配時會拋出new char異常,m_pData成爲一個空指針。改進的方法是先分配內存,成功之後再修改自身的數據,否則不修改。
CMyString& operator = (const CMyString& s)
{
    if(this == &s) return this; 
    char* temp = new char[s.size + 1]; 
    if(temp){ // 如果分配成功了
        delete m_pData; 
        m_pData = temp; 
        size = s.size; 
    }
    return *this;
}
另一種方法是使用一個臨時對象,我們儘量不要在構造函數裏面刪除自身數據,而應該統一在析構函數裏完成,通過臨時對象可以完成這一點:
CMyString& operator = (const CMyString& s)
{
    if(this != &s)
    {
        CMyString temp = CMyString(s); 
        // 交換自身的數據指針和臨時對象的數據指針
        char* tp = s.m_pData; 
        s.m_pData = m_pData; 
        m_pData = tp; 
    } // 出了if塊之後,會調用temp的析構,釋放它的內存
    
    return *this; 
}

2. 單例模式

單例模式有懶漢和餓漢兩種,其中餓漢方式是線程安全的,一旦類被加載就會去創建實例,而且只會創建一次,所以在餓漢模式下的單利模式getInstance()裏不需要判斷static對象是否爲空,是必非空的。
而懶漢模式在getInstance裏實例化static對象,會發生線程安全性問題。如果兩個線程先後實例化(都執行構造函數),會出現構造了兩次的問題。懶漢模式的getInstance在多線程環境下需要加鎖,否則就是線程不安全的。
懶漢模式
      | --  沒加鎖的懶漢模式(線程不安全)
      | -- 加鎖的懶漢模式
             | -- 單次判斷(線程安全但效率低)
             | -- 雙判空(線程安全且高效)
2.1 加一個鎖,一次判斷。每次只有一個線程進入臨界區,每次調用getInstance必須排隊。
CMyClass* getInstance()
{
lock(); 
if(m_instance == NULL)
{
m_instance = new CMyClass();
}
unlock(); 
return m_instance; 
}
2.2 加一個鎖,兩次判斷。因爲只在第一次實例化的時候會有多線程問題,所以只需要在if判斷快里加鎖就可以了。
CMyClass* getInstance() 
{
if(m_instance == NULL) 
{
lock(); 
if(m_instance == NULL)  // 第二次判斷是防止排隊的第二個線程在第一個線程已經實例化之後,再次實例化
{
m_instance = new CMyClass(); 
}
unlock(); 
}
}

3. 找數組中重複的數字

問題描述:長度爲n的數組裏,數字大小在 0~ n-1之間,其中某些數字重複,但不知道哪些重複了,也不知道重複了幾次。請找出任意一個重複的數字。
解法:
1. 給數組先排序,O(nlogn),然後以O(n)掃描一下,有相鄰兩個一樣就找到了重複的。
2. 用哈希表,只需要一次遍歷,時間複雜度O(n), 空間複雜度O(n),用來保存一個哈希表。
#include <iostream>
using namespace std;

#define SIZE 10
int hashTable[SIZE] = {0};

int main()
{
int num[SIZE] = {1, 2, 2, 3, 4, 5, 5 ,6, 7};
int repNum = -1;
for (int i = 0; i < SIZE; i++)
{
if (hashTable[num[i]] != 0)
{
repNum = num[i];
break;
}
hashTable[num[i]] ++;
}
cout << repNum;
}
3. 看題,容量爲n的數組,元素內容也在0~n-1之間,說明如果元素無重複的話,應該是每個位置的索引和元素值一樣,也就是說,數字i在排序之後應該出現在第i個位置上。而現在出現了重複,說明有個位置被佔了。我們可以遍歷數組,嘗試把每個元素放到它理應在的位置上(數字i在第i個位置)。如果發現那個位置i被另一個i佔了,說明i就是一個重複的數字。時間複雜度爲O(n)。
#include <iostream>
using namespace std;

#define SIZE 10 

int main()
{
    int n[SIZE] = { 1, 2, 3, 4, 2, 5, 5 ,6, 7 };
#define swap(x, y)\
do {\
int t = x;\
    x = y;\
    y = t;\
}while(0)
    int res = -1; 
    for (int i = 0; i < SIZE; )
    {
        if (n[i] != i)
        {
            if (n[n[i]] == n[i])
            {
                res = n[i]; 
                break; 
            }
            // 把數字放到它應在的位置
            int v = n[i];
            // 注意這裏不能n[n[i]],一旦n[i]改了,結果就不對了
            swap(n[i], n[v]);
        }
    }
    cout << res;
}

4. 二維數組中的查找

問題描述:有一個二維數組,每行都按照從左到右遞增,每一列按照從上到下遞增。寫一個函數,接受一個二維數組和一個整數,判斷二維數組中是否有該整數。

比如有一個二維數組: 

1 2 8 9 

2 4 9 12

4 7 10 13 

6 8 11 15

查找7, 5...

解決方案:從右上角(比如9)開始縮小查找區域,如果右上角的值大於目標值,則該列不可能有目標值,剔除一列,接着判斷左邊一列。如果某列第一個值小於目標值,則目標值有可能在此列,且不可能在此行,剔除此行(因爲更左邊更不可能有了,而右邊的已經剔除了),規模立馬縮小了。接着就是重複這個步驟,知道找到目標值,或者列到底,或者行到頭,表示沒有這個值。也就是說,每次我們只會去那右上角和目標值比較,不會去遍歷整個表,或者某行某列。

#include <iostream>
using namespace std;
#define SIZE 4 
// 以(i, j)爲右上角的矩形
bool find(int n[][SIZE], int num, int i, int j)
{
    if (i > SIZE || j < 0) // 遞歸終點
        return false; 
    if (n[i][j] > num)
        return find(n, num, i, j - 1);
    else if (n[i][j] == num)
        return true;
    else
        return find(n, num, i + 1, j); 
}

int main()
{
    int n[][SIZE] = { 
        {1, 2, 8, 9}, 
        {2, 4, 9, 12},
        {4, 7, 10, 13},
        {6, 8, 11, 15},
    };
    if (find(n, 14, 0, 3))
    {
        cout << "find number " << endl; 
    }
    else
    {
        cout << "didnt find number" << endl; 
    }
}

5. 替換空格

問題描述:實現一個函數,把字符串中的每個空格替換成"%20",例如,輸入“We are happy.” ,則輸出"We%20are%20happy."。比如給定一個string buffer,容量足夠大,替換空格。
解決方案:我們可以先遍歷一邊字符O(n),統計字符串中空格的個數,以得到需要的字符串長度,新長度=原來長度+空格數x2。然後從新長度的末尾開始修改原buffer,從後往前複製是一個技巧,可以減少移動的次數,寫法也很方便。
#include <iostream>
using namespace std;
#define SIZE 1024 // buffer size 
int main()
{
    char strbuf[SIZE] = "We are happy."; 
    //cout << sizeof(strbuf); // 1024
    int originalLen = strlen(strbuf) + 1; // 13 + 1, 包含結尾0
    int newlen = originalLen;
    for (int i = 0; strbuf[i] != 0; i++)
    {
        if (strbuf[i] == ' ')
            newlen += 2;
    }
    if (newlen > SIZE)
        return 0; 
    int pre = originalLen - 1; 
    int beh = newlen - 1; 
    while (pre >= 0)
    {
        if (strbuf[pre] == ' ')
        {// 替換
            strbuf[beh--] = '0';
            strbuf[beh--] = '2';
            strbuf[beh--] = '%';
        }
        else
        {// 直接覆蓋
            strbuf[beh--] = strbuf[pre];
        }
        --pre; 
    }


    cout << strbuf;
    return 0;
}

6. 從尾到頭打印鏈表

問題描述:鏈表是面試中常考的內容,鏈表的各種操作都要能默寫出來,並且要保證robust。要從尾到頭打印鏈表,也就是先打印本節點的下一個節點,再打印本節點。很簡單的遞歸。
解決方案:
void PrintListReversingly(ListNode* root)
{
    if(root == NULL) return ;
    PrintListReversingly(root->m_pNext); 
    cout << root->m_pData << endl; 
}
遞歸都可以通過棧來改寫成非遞歸:
void PrintListReversingly(ListNode* root)
{
    if(root == NULL) return ; 
    stack<ListNode*> nodes; 
    while(root)
    {
        nodes.push(root); 
        root = root->next; 
    }
    while(!nodes.empty())
    {
        ListNode* p = nodes.top();
        nodes.pop(); 
        cout << p->m_pData; 
    }
}

7. 重建二叉樹

問題描述:二叉樹也是面試中常考的內容,二叉樹的各種操作也需要清楚。典型的平凡二叉樹,結點的結構是: 
struct Node
{
Node* left; 
Node* right; 
int value; 
}
重建二叉樹是指,給定了前序遍歷和中序遍歷結果,構造出一顆二叉樹。並能給出它的後序遍歷結果。比如給定了前序結果1, 2 , 4, 7, 3, 5, 6, 8, 和中序結果4, 7, 2, 1, 5, 3, 8, 6,要求用上面的Node結點結構重建二叉樹,並返回根結點。
解決方案:看書的第63頁的圖。
#include <iostream>
using namespace std;
struct Node
{
    int value; 
    Node* right;
    Node* left; 
};
//
Node* rebuild(int dlr[], int dlr_p, int s,// 前序遍歷和區間, dir_p表示前序遍歷中的子序列索引,s表示中序和前序序列的大小
    int ldr[], int ldr_l, int ldr_r)    // 中序遍歷和區間, ldr_l,ldr_r分別表示中序遍歷中子樹的範圍
{
    // 邊界
    if (dlr_p < 0 || dlr_p >= s) return NULL; 
    if (ldr_l > ldr_r) return NULL;
    // 判斷
    Node* node = new Node; 
    node->value  = dlr[dlr_p]; // 前序遍歷的第一個點就是根結點
    // 從中序遍歷中尋找根結點的位置
    int pos = -1;
    for (int i = ldr_l; i <= ldr_r; i++)
    {
        if (ldr[i] == node->value)
        {
            pos = i;
            break; 
        }
    }
    int lsize = pos - ldr_l; // 左子樹寬度
    int rsize = ldr_r - pos; // 右子樹寬度
    node->left = rebuild(dlr, dlr_p + 1, s, ldr, ldr_l, pos - 1);
    node->right = rebuild(dlr, dlr_p + lsize + 1, s, ldr, pos + 1, ldr_r);
    return node;
}
// 後續遍歷
void lrd(Node* root)
{
    if (root == NULL)return; 
    lrd(root->left); 
    lrd(root->right);
    cout << root->value << endl; 
}
int main()
{
    const int size = 8;
    int dlr[size] = {1, 2, 4, 7, 3, 5, 6, 8};
    int ldr[size] = {4, 7, 2, 1, 5, 3, 8, 6};
    Node* root = rebuild(dlr, 0, size, ldr, 0, size - 1);
    lrd(root);
}

8. 二叉樹的下一個節點

問題描述:給定一顆二叉樹和其中一個結點,如何找出中序遍歷的下一個節點?樹的節點中,除了左右指針,還有一個指向父結點的指針。
解決方案:
1. 如果這個節點是父結點的左節點,則下一個節點就是父結點
2. 如果這個節點是父結點的右節點,且有右節點,則下一個結點就是它的右子樹的最左節點
3. 如果這個節點是父結點的右節點,且無右節點,則下一個結點就以它的父結點爲基準的下一個節點,回到問題1.2,直到找到這樣一個節點,滿足1或2
Node* findNext(Node* node) 
{
    if(node == NULL) return NULL; 
    Node* parent = node->parent; 
    if(parent->left == node) return parent; 
    else 
    {// 是右節點
        Node* next = node->right ;
        if(next)
        {//,從右子樹中找最左節點,一直遍歷left,知道->left爲空
            while(next->left)
                next = next->left;
        }
        else 
        {
            next = findNext(node->parent);
        }
        return next; 
    }
}   

9. 用兩個棧實現一個隊列、用兩個隊列實現一個棧

問題描述:這是一個典型的適配器模式,STL中的stack 和queue也是適配器,默認是由deque實現的。棧是一種只能在一端操作的數據結構,而且滿足先進後出;隊列是一種兩端操作的結構,一端進,一端出(是不是很像動物吐舌頭),滿足先進先出。
解決方案:爲了方便,我直接用STL中的stack實現一個queue,queue相當於一個適配器。要實現的接口有:
void push(v_type value) ;
void pop(); 
v_type front();
bool empty();
這三個,我們使用兩個stack來保存狀態,隊列中的元素,要麼在stack1中,要麼在stack2中。stack1用來保存push的對尾數據,stack2用來保存pop的隊首數據。push時,始終往stack1push,無論它是不是空的,pop時:
1. 如果stack2爲空,則把stack1中的所有元素pop出來,依次放入stack2,使得它們在stack2中的順序和在stack1中的恰好相反,這樣,先入隊的酒會在stack2的頂上,然後pop 出來就好了。
2. 如果stack2非空,則直接pop出stack2頂部那個元素,肯定是最早進入隊列的元素。
#include <iostream>
#include <stack>
using namespace std;
// MyQueue適配器
class MyQueue
{
public:
    MyQueue():s1(), s2()
    {
    }
    stack<int> s1; 
    stack<int>s2; 
    void push(int n)
    {
        s1.push(n);
    }
    void pop()
    {
        if (!s2.empty())
        {
            s2.pop(); 
            return;
        }
        while (!s1.empty())
        {
            int n = s1.top(); 
            s2.push(n);
            s1.pop();
        }
        s2.pop();
    }
    int front()
    {
        if (!s2.empty())
        {
            return s2.top();
        }
        while (!s1.empty())
        {
            int n = s1.top();
            s2.push(n);
            s1.pop();
        }
        return s2.top();
    }
    bool empty()
    {
        return s1.empty() && s2.empty(); 
    }
};
int main()
{
    MyQueue q;
    q.push(1);
    q.push(3);
    q.push(2);
    q.push(5);
    q.push(4);
    cout << q.front() << endl; 
    q.pop();
    cout << q.front() << endl; 
    while (!q.empty())
    {
        cout << q.front();
        q.pop();
    }
}
還有一個問題就是用兩個隊列實現一個棧,也是一個適配器,要實現的操作也還是:
void push(v_type value) ;
void pop(); 
v_type top();
bool empty();
這四個,由於棧有後入先出的特點,只能在一端操作。用兩個隊列實現一個棧的中心思想是每次只有一個操縱隊列(每次只有一個隊列非空,書72頁),而且在每次執行pop和top的時候另一個隊列成爲操縱隊列。
void push(v_type value) ;
void pop(); 
v_type front();
bool empty();

#include <iostream>
#include <queue>
using namespace std;


class MyStack
{
public:
    MyStack() :q1(), q2()
    {
    }
    // 每次只會有一個隊列非空
    queue<int> q1;
    queue<int> q2;
    void push(int n)
    {
        // 當前操縱隊列
        queue<int>* cur = &q1;
        if (!q2.empty()) cur = &q2; 
        cur->push(n);
    }
    void pop()
    {
        if (empty()) return;
        queue<int>* cur = &q1; 
        queue<int>* another = &q2;
        if (!q2.empty())
        {
            cur = &q2;
            another = &q1; 
        }
        // 將前面的那些轉移到另一個隊列中
        while (cur->size() > 1)
        {
            int n = cur->front();
            another->push(n);
            cur->pop();
        }
        if(!cur->empty())
            cur->pop();
    }
    int top()
    {
        if (empty()) return 0;
        queue<int>& cur = q1;
        queue<int>& another = q2;
        if (!q2.empty())
        {
            another = q1;
            cur = q2;
        }
        // 將前面的那些轉移到另一個隊列中
        int n ;
        while (!cur.empty())
        {
            n = cur.front();
            another.push(n);
            cur.pop();
        }


        return n; 
    }
    bool empty()
    {
        return q1.empty() && q2.empty();
    }
};


int main()
{
    MyStack s; 
    s.push(1); 
    s.push(2);
    s.push(3);
    s.push(4);
    cout << s.top(); 
    s.pop(); 
    s.push(5);
    while (!s.empty())
    {
        cout << s.top(); 
        s.pop();
    }
}

10. 斐波那契數列

f(n) = > (
0, n = 0
1, n = 1 
f(n -1) +f(n - 2), n > 1
)
問題描述:輸入n,求斐波那契數列的第n項。
解決方案:斐波那契數列的遞歸公式是 ,但是這樣會有很多重複計算的問題(遞歸幾乎都有)。可以通過一個備忘錄來優化。或者直接從f(0)開始算起,逐步遞推到f(n)
#include <iostream>
using namespace std;
int main()
{
    for (;;)
    {
        int n; 
        cin >> n;
        if (n == 1)
        {
            cout << 1 << endl;
            continue;
        }
        int fibMinusOne = 1; 
        int fibMinusTow = 0; 
        int fibN = 0; 
        for (int i = 2; i <= n; i++)
        {
            fibN = fibMinusTow + fibMinusOne;
            fibMinusTow = fibMinusOne;
            fibMinusOne = fibN;
        }
        cout << fibN << endl;
    }


    return 0;
}
10.2 青蛙跳臺階問題
有隻青蛙一次可以跳1級臺階,2級臺階,或3級臺階。問跳上n級臺階有多少種跳法。
問題分析:這是一個斐波那契數列的應用。f(0) = 0 , f(1) = 1, f(2) = 2, f(3) = 3, f(4) = f(3) + f(2) + f(1),對於n階臺階,第一步可以走3階,2階或1階。所以f(n) = f(n - 3) +f(n-2) + f(n-1)。可以用遞歸+備忘錄優化,也可以遞推。略過。

11. 旋轉數組的最小數字

問題描述:把一個數組最開始的若干個元素搬到數組的末尾,稱爲數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如,數組3,4,5,1,2爲1,2,3,4,5的一個旋轉,該旋轉數組的最小值爲1。
解決方案:採用二分法,充分利用旋轉數組的特性,將旋轉數組分爲兩部分。左子序列的第一個值一定大於等於右子序列的最後一個值。採用兩個指針,一個從最左邊開始,一個從最右邊開始。每次檢測 (l+r)/2位置上的值,將其與l和r上的比較,如果大於等於l上的值,一定在左子序列,最小值一定在此值右邊;如果小於右等於r的值,一定在此值左邊。如此就將問題縮小了一半。時間複雜度O(logn)。
#include <iostream>
using namespace std;
// l指針一定在左子序列
// r指針一定在右子序列
int find(int *n, int l, int r)
{
    if (n[l] < n[r]) return n[l]; // 處理 自身有序這種情況,可以直接返回
    if (r - l == 1) return n[r]; // 左右兩指針相遇,只差一個,則最小值是右子序列的第一個數字
    int mid = (l + r) / 2.f;
    if (n[mid] >= n[l]) return find(n, mid, r); 
    if (n[mid] <= n[r]) return find(n, l, mid); 
}
int main()
{
    int n[] = {3, 4, 5, 1, 2};
    cout << find(n, 0, sizeof(n)/ sizeof(int) - 1);
}

12. 矩陣中的路徑

問題描述:設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任何一格開始,每一步可以在矩陣中向左,右,上,下移動一格。如果一條路徑經過了矩陣中的某一格,那麼該路徑不能再次進入該格子。例如,在下面的3x4矩陣中包含一條“bfce”的路徑,但不包含"abfb"的路徑,因爲字符串的第一個字符b在佔據了矩陣的第一行第二個格子之後,就不能再進入這個格子。
a b t g 
c  f c s
j  d e h 
解決方案:採用回溯法。詳見書89頁。很好理解,不過要注意的是對於“不能再進入這個格子”的處理,用一個同樣維度的bool矩陣來標記。
#include <iostream>
using namespace std;
#define SIZE 4
// i, j 是當前點的位置
bool traceback(bool mask[][SIZE], char n[][SIZE], int r, int c, int i, int j, const char* s)
{
    if (i < 0 || i >= r || j < 0 || j >= c)
        return false;
    if (mask[i][j]) return false; // 不能重複訪問一個節點
    if (n[i][j] == s[0])
    {// 找下一部分
        mask[i][j] = true; // 標記這個節點已經被訪問過
        if (strlen(s) == 1) return true; // 已經相等
        if (traceback(mask, n, r, c, i, j - 1, s + 1) // 向左走
            || traceback(mask, n, r, c, i, j + 1, s + 1) // 向右走
            || traceback(mask, n, r, c, i - 1, j, s + 1) // 向上走
            || traceback(mask, n, r, c, i + 1, j, s + 1) // 向下走
            )
            return true;
        else
        {// 沒有此路徑
            mask[i][j] = false; 
            return false;
        }
    }
    else
        return false;
}
bool find(bool mask[][SIZE], char n[][SIZE], int r, int c, const char* s)
{
    for (int i = 0; i < r; i++)
    {
        for (int j = 0; j < c; j++)
        {
            if (n[i][j] == s[0])
                if (traceback(mask, n, r, c, i, j, s))
                    return true; 
        }
    }
    return false;
}
int main()
{
    char n[][SIZE] = {
        {'a', 'b', 't', 'g'},
        {'c', 'f', 'c', 's'},
        {'j', 'd', 'e', 'h' },
    };
    bool mask[][SIZE] = {
        {0},
        { 0 },
        { 0 },
    };
    const char* s = "bfce"; 
    //const char* s2 = "abfb";
    if (find(mask, n, 3, 4, s))
        cout << "the string is found" << endl;
    else
        cout << "can't find string" << endl; 
}

13. 機器人的運動範圍

問題描述:地上有m行n列的方格,一個機器人從座標(0,0)的格子開始移動,每次可以向左、右、上、下移動一格,但不能進入行座標和列座標的位數之和大於k的格子。例如,當k爲18時,機器人能夠進入方格(35,37),因爲3+5+3+7=18。但它不能進入方格(35, 38),因爲3+5+3+8=19,請問機器人能夠到達多少個格子?
解決方案:使用回溯法。
解決方案:
#include <iostream>
using namespace std;
int sum(int i, int j)
{
    int res = 0; 
    while (i > 0)
    {
        res += i % 10;
        i /= 10;
    }
    while (j > 0)
    {
        res += j % 10;
        j /= 10;
    }
    return res; 
}
void find(int m, int n,int i, int j, int k, int& count, bool* visited)
{
    if (i < 0 || i >= m || j < 0 || j >= n) return;
    if (visited[n * i + j]) return;
    if (sum(i, j) > k) return ; 
    if (!visited[n * i + j])
    {
        visited[n * i + j] = true; 
        count += 1;
    }
    find(m, n, i - 1, j, k, count, visited); // 向上走
    find(m, n, i + 1, j, k, count, visited); // 向下走
    find(m, n, i, j - 1, k, count, visited); // 向左走
    find(m, n, i, j + 1, k, count, visited); // 向右走
}
int main()
{
    for (;;)
    {
        int m, n, k; 
        m = 5;
        n = 5;
        k = 3; 
        int count = 0;
if(m <= 0 || n <= 0 || k < 0)
{
cout << count; 
continue; 
}
        bool* visited = new bool[m *n];
        for (int i = 0; i < m*n; i++)
            visited[i] = 0;
        find(m, n, 0, 0, k, count, visited);
        cout << count; 
    }
}

14. 剪繩子

問題描述:一根長度爲n的繩子,把繩子剪成m段(m,n都是整數, n >1且m>1),每段繩子長度爲k[0], k[1],... k[m],請問k[0]xk[1]x ... k[m]可能的最大乘積是多少?例如,當繩子長度是8時,我們把它剪成2,3,3,此時得到最大乘積18.注意,只有n是給定的,m是未給定的。
解決方法:用動態規劃的方法。剪第一刀的時候有 n-1 種可能的選擇,也就是剪出來的繩子的長度可能爲1, 2, ... n-1。有遞歸公式 f(n) = max( f(i) * f(n - 1)), 0 < i < n 注意,第一剪剪下來的這一段也有自己的最優解,而不是簡單的 i 。
#include <iostream>
using namespace std;
int main()
{
    int n = 8;
    int *product = new int[n + 1]; 
    // 邊界
    product[0] = 0; 
    product[1] = 1; 
    product[2] = 2;
    product[3] = 3; 
    int max = 0;
    for (int i = 4; i <= n; i++)
    {
        max = 0;
        for (int j = 0; j <= i/2; j++)
        {
            int p = product[j] * product[i - j];
            if (p > max) max = p;
            product[i] = max;
        }
    }
    max = product[n]; 
    delete[] product; 
    cout << max; 
}
另一種解法是貪婪算法,當n>=5時,儘可能多剪成長度爲3的繩子;當剩下的繩子長度爲4時,把繩子剪成兩段長度爲2的繩子。具體的證明和算法在97頁。

15. 位運算

二進制的位運算有
與 &
或 | 
異或 ^
左移 <<
右移 >>
其中左右移的優先級最低,計算的時候經常要加括號。位運算的優先級總體來說比較低(比比較運算符低)。
15.1 微軟Excel中,用A表示第一列,B表示第二列,。。。Z表示第26列,AA表示第27列,AB表示第28列...要求寫一個函數,輸入一個列號編碼,輸出它是第幾列。
解決方案:是一個進制轉換問題,把編碼從26進制轉換成十進制即可
#include <iostream>
#include <string>
#include <math.h>
using namespace std;
int f(const string s)
{
    int n = s.size(); 
    int res = 0; 
    for (int i = 0; i < s.size(); i++)
    {
        char c = s[i]; 
        int num = c - 'A' + 1; 
        res += num * pow((double) 26, (double)(--n));
    }
    return res; 
}
int main()
{
    for (;;)
    {
        string s; 
        cin >> s; 
        cout << f(s);
    }
}
15.2 統計二進制數中的1的個數
解決方案:
15.2.1 會在輸入數字是負數時造成死循環(最高位是1)。
#include <iostream>
using namespace std; 
int countOne(int n)
{
    int res = 0; 
    while (n > 0)
    {
        res += (n & 1);
        n = n >> 1; 
    }
    return res; 
}
int main()
{
    for (;;)
    {
        int n; 
        cin >> n; 
        cout << countOne(n) << endl;
    }
}
15.2.2 爲了避免死循環,可以不右移輸入的數字n,可以把n與1做與運算,看最低位是不是1,然後把1左移一位得到2,在和n做與運算,就能判斷n的次低位是不是1。。。反覆左移1,每次都可以判斷,無論是不是最高位的符號位。
#include <iostream>
using namespace std; 
int countOne(int n)
{
    int count = 0; 
    unsigned int flag = 1;
    while (flag)
    {
        if ((n & flag) != 0) count++; 
        flag = flag << 1; 
    }
    return count; 
}
int main()
{
    for (;;)
    {
        int n; 
        cin >> n; 
        cout << countOne(n) << endl;
    }
}
15.2.3 通過“把一個整數-1之後在和原來的數按位與,結果等於把整數的二進制表示中最右邊的1變成0”。比如1100, 減一之後是1011, 再和原先的數按位與是1000, 這樣就成功的只剔除了原先的最後一個1,循環往復,直到數爲0即可。具體看書102
#include <iostream>
using namespace std; 
int countOne(int n)
{
    int count = 0; 
    while (n)
    {
        ++count;
        n = (n - 1) & n;
    }
    return count; 
}
int main()
{
    for (;;)
    {
        int n; 
        cin >> n; 
        cout << countOne(n) << endl;
    }
}

16. 數值的整數次方

問題描述:實現double Power(double base, int exponent), 不得使用庫函數,同時不需要考慮大數問題。
解決方案:需要考慮以下問題:
1. 指數是負數
2. 不能對0求負冪,因爲不能求0的倒數,應該報告調用出錯
3. 0的0次方在數學上無意義,輸出0或者1都可以
#include <iostream>
bool g_InvalidInput = false; // 保存錯誤信息
double PowerWithUnsignedExponent(double base, unsigned int exponent) // 計算正數冪
{
    double result = 1.0; 
    for (int i = 1; i <= exponent; ++i)
        result *= base;
    return result; 
}
double Power(double base, int exponent)
{
    g_InvalidInput = false;
    if (base == 0.0 && exponent < 0)
    {// 不能對0求負冪,因爲不能對0求倒數
        g_InvalidInput = true; 
        return 0.0; 
    }
    unsigned int absExponent = (unsigned int)exponent;
    if (exponent < 0)
        absExponent = (unsigned int)(-exponent); 
    double result = PowerWithUnsignedExponent(base, exponent);
    if (exponent < 0)
        // 如果是負數冪,就取倒數
        result = 1.0 / result; 
    return result;
}
int main()
{
}
優化的方案是用下面的數列:
a^n = a^(n/2) * a^(n/2) , n爲偶數
          a^((n-1)/2) * a^((n-1)/2) * a ,  n爲奇數

17. 打印從1到最大的n位數

問題描述:輸入數字n,按順序打印出從1到最大的n爲十進制數字,比如輸入3 ,打印1,2,3,一直到最大的三位數999。
解決方案:由於沒有說n有多大,如果很大的話,即便用Longlong 保存也會超範圍。解決方法是在字符串上模擬數字加法,因爲字符串是沒有長度限制的。
#include <iostream>
using namespace std;
// 給n +1
bool Increment(char* n)
{
    bool isOverflow = false;
    int nTakeOver = 0; // 進位
    int nLength = strlen(n); 
    for (int i = nLength - 1; i >= 0; i--)
    {
        int nSum = n[i] - '0' + nTakeOver; // 加上進位
        if (i == nLength - 1)
            nSum++; 
        if (nSum >= 10) // 需要進位
        {
            if (i == 0) // 如果已經到最高位了,即最後一個數字
                isOverflow = true; 
            else
            {
                nSum -= 10; 
                nTakeOver = 1; 
                n[i] = '0' + nSum; //刷新
            }
        }
        else
        {
            n[i] = '0' + nSum;
            break; 
        }
    }
    return isOverflow;
}
// 輸出數字,注意別輸出補位的零
void PrintNumber(const char* n)
{
    int len = strlen(n); 
    bool isBeginningZero = true; 
    for (int i = 0; i < len; i++)
    {
        if (n[i] == '0' && isBeginningZero)
        {
            continue; 
        }
        isBeginningZero = false;
        cout << n[i];
    }
    cout << '\n';
}
// 
void PrintToMaxOfNDigits(int n)
{
    if (n <= 0)
        return;
    char* number = new char[n + 1]; 
    memset(number, '0', n); 
    number[n] = '\0';// 此時 字符串應該是 0000...000\0, n個0, 結尾一個\0
    while (! Increment(number)) // 輸出,直到位數已滿
    {
        PrintNumber(number);
    }
    delete[] number; 
}
int main()
{
    for (;;)
    {
        int n; 
        cin >> n; 
        PrintToMaxOfNDigits(n);
    }
}
這樣的話還是比較複雜的,在短時間內要寫出來幾乎不可能,可以用遞歸來實現更簡單的方法。如果我們在所有數字前面補0,就會發現,n位左右十進制數其實就是n個從0到9的全排列。也就是說,只要把每一位從0到9全排列一遍,就得到了所有十進制數字。在打印的時候,忽略前導0就好了。
#include <iostream>
using namespace std;
void PrintToMaxOfNDigits(int n)
{
    if (n <= 0) return; 
    char* number = new char[n + 1]; 
    number[n] = '\0'; 
    for (int i = 0; i <= 9; i++)
    {
        number[0] = i + '0';
        PrintToMaxOfNDigitsRecursively(number, n, 0);
    }
}
// 輸出數字,注意別輸出補位的零
void PrintNumber(const char* n)
{
    int len = strlen(n);
    bool isBeginningZero = true;
    for (int i = 0; i < len; i++)
    {
        if (n[i] == '0' && isBeginningZero)
        {
            continue;
        }
        isBeginningZero = false;
        cout << n[i];
    }
    cout << '\n';
}
void PrintToMaxOfNDigitsRecursively(char* number, int length, int index)
{
    if (index == length - 1)
    {
        PrintNumber(number); 
    }
    for (int i = 0; i < 10; i++)
    {
        number[index + 1] = i + '0';
        PrintToMaxOfNDigitsRecursively(number, length, index + 1); 
    }
}
int main()
{
    for (;;)
    {
        int n; 
        cin >> n; 
        PrintToMaxOfNDigits(n);
    }
}

18. 刪除鏈表的結點

問題描述:在O(1)時間內刪除單鏈表的某個結點。給定一個表頭指針和一個結點指針,要求定義一個函數,在O(1)時間內刪除該結點。鏈表的數據結構如下:
struct ListNode
{
int m_pData; 
ListNode* m_pNext; 
}
解決方法:通常我們要刪除單鏈表中一個結點,要從表頭遍歷找到此節點的上一個結點,然後進行一系列指針轉移操作,時間複雜度是O(n)。此題要求時間複雜度O(1),那隻能從此結點指針着手了,因爲訪問這個節點的時間是O(1)。可以用結點的下一個結點覆蓋此結點,然後刪除下一個結點即可。因爲結點結構很簡單,這樣是可行的。
void RemoveNode(ListNode** head, ListNode* node) 
{
    if(*head == NULL || node == NULL) return ; 
    // 如果要刪除的不是尾結點
    if(node->next != NULL) 
    {
        ListNode* next= node->next; 
        node->m_pDate = next->m_pData; 
        node->next = next->next; 
        delete next; 
        next = NULL;
    }
    // 如果只有一個結點,刪除這個節點
    else if(*head == node) 
    {
        delete node; 
        node = NULL;
        *head= NULL:  
    }
    else 
    {// 鏈表有多個節點,且刪除尾結點,則尾結點的上一個節點的next應該是0,
     // 這時候只能老老實實遍歷了
        ListNode* n  = *head; 
        while(n->next != node)
        {
            n = n->next;
        }
        n->next = NULL; 
        delete node; 
        node = NULL; 
    }
}
18.2 刪除已排序鏈表中重複的結點。比較簡單,說一下思路。如果當前結點和下一個結點的值相同,那麼就是重複的。

19. 正則表達式匹配

問題描述:實現一個函數,用來匹配包含'.'和'*'的正則表達式。模式中的字符 . 代表任意一個字符,而 * 代表它前面的字符可以出現任意多次(含0次)。匹配指該字符串的所有字符都匹配整個模式。例如,字符串“aaa”與模式"a.a", "ab*ac*a"匹配,但與"aa.a" . "ab*a"均不匹配。
解決方案:
#include <iostream>
using namespace std;
bool matchCore(char* str, char* pattern)
{
    // 遞歸終點
    if (*str == '\0'  && *pattern == '\0') // 當兩者都走完時,匹配
        return true; 
    if (*str != '\0' && * pattern == '\0') // 當模式已經走完時,字符串還沒走完,不匹配。(題目要求字符串中的所有字符匹配整個模式);翻過來的情況在下面的代碼中判斷,即*str = '\0’返回false。
        return false; 
    if (*(pattern + 1) == '*')
    {// 如果模式的第二個字符是* 或者模式允許任意字符出現任意多次即".*"模式
        if (*str == *pattern || *pattern == '.' && *str != '\0')
        {// 如果str的第一個字符和pattern的第一個匹配,則str跳過一個,或者pattern跳過這個*匹配(即匹配0次),或者兩者都跳過
            return matchCore(str + 1, pattern)
                || matchCore(str, pattern + 2)
                || matchCore(str + 1, pattern + 2);
        }
        else
        { // 如果不匹配,則跳過這個* 匹配(因爲*允許出現0次前面的字符)
            return matchCore(str, pattern + 2); 
        }
    }
    if (*str == *pattern || (*pattern == '.' && *str != '\0'))
        return matchCore(str + 1, pattern + 1);
}
bool match(char* str, char* pattern)
{
    if (str == NULL || pattern == NULL) return false;
    return matchCore(str, pattern);
}

20. 表示數值的字符串

跳過
問題描述:實現一個函數,判斷字符串是否表示數值(包括整數、小數)比如,字符串"+100","5e2", "-123", "3.1416"以及"-1E-16"都表示數值,但"12e", "1a3.14",“1.2.3”,"+-5","12e+5.4"都不是數值。
解決方案:表示數值的字符串遵循模式 A[.[B]][e|EC]或者.B[e|EC], 其中A爲數值的整數部分,B爲小數部分,C爲指數部分,C只能是整數,不會是小數。

21. 調整數組順序使得奇數位於偶數前面

問題描述:輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有奇數位於數組的前半部分,所有偶數位於數組的後半部分。
解決方案:利用快速排序一樣的思想,一個左指針,一個右指針,左指針碰到偶數, 右指針碰到奇數,就交換兩者,知道兩個指針相遇。
#include <iostream>
using namespace std;
int main()
{
    int n[] = {0, 1,2,3,4,5,6,7,8};
    int size = sizeof(n) / sizeof(int);
    int l = 0; 
    int r = size - 1; 
    while (l < r)
    {
        while ((n[r] & 1) == 0) --r; // 位運算符優先級低於比較運算符。位運算優先級一般很低,移位最低
        while ((n[l] & 1) == 1) ++l;
        if (l >= r) break;
        n[r] ^= n[l];
        n[l] ^= n[r]; 
        n[r] ^= n[l];
        l++;
        r--;
    }
    for (int i = 0; i < size; i++)
        cout << n[i] << " " << endl; 
}

22. 獲得鏈表中倒數第k個節點

問題描述:輸入一個鏈表,輸出該鏈表的倒數第N個節點。從1開始計數,鏈尾是倒數第一個節點。
解決方案:兩個指針,一個快指針,一個慢指針,快指針走n-1步,然後一起走,等塊指針走到尾部,慢指針的位置就是倒數第n個結點
++++++------------------------------ 快指針先走N步,到了最後一個加號處
-----------------------------+++++++然後一起走,最後慢指針到了最左邊那個加號處,兩個指針走的構成鏡像
證明:假設鏈長爲L(未知),快指針走到正數第N個結點時(走了N-1步),剩餘L-N個沒走,需要走L-N步。此時兩者一起走,等快指針到了鏈尾,慢指針走了L-N步,到了正數第L-N+1個,對應於倒數第N個(正數倒數的編號和是L+1)。
ListNode * RGetKthNode(ListNode * pHead, unsigned int k) // 函數名前面的R代表反向  
{
    if (k == 0 || pHead == NULL) // 這裏k的計數是從1開始的,若k爲0或鏈表爲空返回NULL  
        return NULL;
    ListNode * pAhead = pHead;
    ListNode * pBehind = pHead;
    while (k > 1 && pAhead != NULL) // 前面的指針先走到正向第k個結點, 走了k-1步
    {
        k--;
        pAhead = pAhead->m_pNext;
    }
    if (k > 1 || pAhead == NULL)     // 結點個數小於k,返回NULL  
        return NULL;
    while (pAhead->m_pNext != NULL)  // 前後兩個指針一起向前走,直到前面的指針指向最後一個結點  
    {
        pBehind = pBehind->m_pNext;
        pAhead = pAhead->m_pNext;
    }
    return pBehind;  // 後面的指針所指結點就是倒數第k個結點  
}

23. 鏈表中環的入口結點

略過,見另一篇有關鏈表的博客

24. 反轉鏈表

略過,同上

25. 合併兩個已排序的鏈表

基本思路是每次從兩個鏈表頭部選小的(如果是遞增鏈表的話),然後把next指針指向這個結點。
略過,同上

26. 樹的子結構

問題描述:輸入兩個二叉樹A和B,判斷B是不是A的子結構。
解決方案:
1. 現在A中查找與B根結點一樣的值,等價於樹的遍歷。
2. 以匹配的根爲根,遞歸的判斷結構是否一致。
分爲兩個函數,一個用來找到那個根,另一個以那個根爲根,遞歸的判斷。
詳見書150頁。

27. 翻轉二叉樹

* 28. 對稱的二叉樹

問題描述:實現一個函數,用來判斷一個二叉樹是不是對稱的(關於根結點對稱),即左右翻轉之後,還是它。
解決方案:我們定義一種遍歷方法,類前序遍歷,只不過現在是先遍歷右子樹,再遍歷左子樹。用兩個指針,一個往左走,按照前序遍歷,先左子樹,再右子樹;一個往右走,先右子樹,再左子樹。
bool isSymmetraical(BinaryNode* pRoot)
{
    return isSymmetraical(pRoot, pRoot);
}
bool isSymmetraical(BinaryNode* pRoot1, BinaryNode* pRoot2) // root1往左走,root2往右走
{
    if(pRoot1 == NULL && pRoot2 == NULL)
        return true; 
    if(pRoot1 == NULL || pRoot2 == NULL) // 有一個不爲空
        return false;
    if(pRoot1->m_Value != pRoot2->m_Value)
        return false; 
    return isSymmetraical(pRoot1->m_left, pRoot2->m_right) 
    &&     isSymmetraical(pRoot1->m_right, pRoot2->m_left);
}

29. 順時針打印矩陣

問題描述:輸入一個矩陣,按照從外向內以順時針打印出每一個數字。如
1 2 3 4 
5 6 7 8 
9 10 11 12
13 14 15 16 
依次打印出1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10
畫圖分析,可以把矩陣看成是好幾圈構成的結構,每次打印外面的一圈,然後回到再裏面一圈的左上角。

30. 包含min函數的棧

問題描述:定義棧的數據結構,在該類型中實現一個能得到棧最小值的min函數。在此棧中,調用min,push,pop的時間都是O(1)。
解決方案:用另一個輔助棧存貯最小值,不能用一個數字保存(因爲如果那個最小值被彈出了,棧的最小是什麼?)。
每次往棧中壓入元素,檢查此元素和輔助棧棧頂元素(最小值)的大小關係,如果比最小值小,則把此值壓入輔助棧,否則(大於或等於),再壓入一個最小值。
從棧中pop數據的時候,輔助棧也pop一個。這樣就可以解決棧中有多個最小值的問題了。每次使用min獲得最小值,一定是輔助棧棧頂元素。

31. 棧的壓入、彈出序列

問題描述:定義兩個序列,一個代表棧的入棧順序,一個代表出棧順序,判斷出棧順序是否能實現。假設壓入的數字都不相等。 

解決方案:
以彈出序列爲基準,參考入棧順序,入棧和出棧:
如果下一個彈出的數字剛好是棧頂數字,則直接彈出;
如果下一個彈出的數字不是棧頂數字,則把壓棧序列中還沒有入棧的序列壓入,直到從中找到了下一個彈出數字,壓入棧;
如果所有數字都壓入棧了,還沒有從中(入棧序列中)找到下一個出棧數字,則該彈出序列不可能是一個彈出序列。

32. 從上到下打印二叉樹(二叉樹層次遍歷/廣度優先遍歷)

依靠隊列實現,略。

33. 二叉搜索樹的後序遍歷序列

問題描述:輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷結果。假設數組內容無重複。
解決方案:二叉搜索樹相比平凡二叉樹的特點就是
1. 中序有序性,即中序遍歷是有序的。
2. 給定了前序遍歷結果或者後序遍歷結果,就可以確定BST的結構,而平凡二叉樹不行。
比如5, 7, 6, 9, 11, 10, 8這個後序遍歷,8一定是BST的根,8左邊那些是左子樹和右子樹,由於二叉樹中左子樹中所有值均<根<右子樹中所有值,所以在這些樹中,很容易發現5,7,6是左子樹,9,11,10是右子樹,這些數字一定是連着的。然後左右子樹又是BST,重複遞歸,知道葉節點。
注意如何判斷假,比如 7, 4, 6, 5這個序列,5是根,從左往右遍歷這個序列,第一個數字7>5,所以7在右子樹,且此樹應該沒有左子樹,那麼接下來的值應該都在右子樹,即7,4,6在右子樹,但我們發現右子樹中有一個節點4小於5,這違背了BST的定義,所以,這個序列不是一個可行的BST後序遍歷序列。
代碼參考書上181.

34. 二叉樹中和爲某一值的路徑

跳過
問題描述:輸入一顆二叉樹和一個整數,打印出二叉樹節點值的和爲輸入整數的所有路徑。必須到達葉節點。

35. 複雜鏈表的複製

跳過

36. 二叉搜索樹轉化爲雙向鏈表

看另一個博客
利用二叉搜索樹的中序有序性。分類:

37. 序列化二叉樹

寫兩個函數,分別實現二叉樹的序列化和反序列化
解決方案:
1. 序列化爲前序和中序結果,然後重建。缺點是不能有重複的節點
2. 採用前序遍歷,因爲前序遍歷的第一個節點是根結點。遇到一個空指針,就保存爲一個特殊字符比如$。嘗試反序列化1,2,4,$,$,$,3,5,$,$,6,$,$時,1時根結點,第二個值2非$,說明1的左子結點是2(如果2是$的話,說明1的左子樹爲空)。
具體看書195

* 38. 字符串的排列

問題描述:輸入一個字符串,打印該字符串的所有排列。
解決方案:不要去嘗試排列組合,統計所有字符在排列。而是從現有字符串開始。交換兩個字符就會獲得一個新的字符串。
#include <cstdio>
void Permutation(char* pStr, char* pBegin);\
void Permutation(char* pStr)
{
    if(pStr == nullptr)
        return;
    Permutation(pStr, pStr);
}
void Permutation(char* pStr, char* pBegin)
{
    if(*pBegin == '\0')
    {
        printf("%s\n", pStr);
    }
    else
    {
        for(char* pCh = pBegin; *pCh != '\0'; ++ pCh)
        {
            char temp = *pCh;
            *pCh = *pBegin;
            *pBegin = temp;
            Permutation(pStr, pBegin + 1); // 處理後面的
            temp = *pCh;    
            *pCh = *pBegin;
            *pBegin = temp;
        }
    }
}
// ====================測試代碼====================
void Test(char* pStr)
{
    if(pStr == nullptr)
        printf("Test for nullptr begins:\n");
    else
        printf("Test for %s begins:\n", pStr);
    Permutation(pStr);
    printf("\n");
}
int main(int argc, char* argv[])
{
    Test(nullptr);
    char string1[] = "";
    Test(string1);
    char string2[] = "a";
    Test(string2);
    char string3[] = "ab";
    Test(string3);
    char string4[] = "abc";
    Test(string4);
    return 0;
}

* 39. 數組中出現次數超過一半的數字

問題描述:數組中有一個數字的出現次數超過了數組長度的一半,找出這個數字。
解決方案:O(n)根據數組的特點。一個次數出現次數超過數組長度的一半,說明它出現的次數比其它數字出現的次數的和還要多。要注意的是,題目已經保證了數組中必定有一個數字出現次數超過數組的一半。下面的算法只在這種情況下有效,
用兩個值,一個保存數字,一個保存次數。當我們遍歷到下一個數字的時候,如果下一個數字和我們之前保存的數字相同,則次數加1; 如果下一個數字和我們之前保存的數字不同,則次數減一,此時如果次數爲0,則保存下一個數字,並把次數設爲1。由於我們要找的數字出現的次數很多,比其他數字出現次數加起來還要多,那麼要找的那個數字,一定會活到最後(最後一次把次數設爲1的那個)。

* 40. 最小的k個數(TOP K問題)

問題描述:輸入n個整數,找出其中最小的k個數。例如,輸入4,5,1,6,2,7,3,8,這8個數字,則最小的4個數字是1,2,3,4
解決方案:
1. 最簡單的就是先拍好,然後選出前k個,複雜度是O(nlogn)
2. 基於partition(快排)的算法, 複雜度爲O(n)
在使用快排過程中,記下來每次排序選擇的
Quicksort相關原理就不多說了,STL中Quicksort算法的實現我在<stl sort源碼剖析>中一文也詳細說明過。所以這裏就只說說怎麼樣用Quicksort來求topk。
其實很簡單,大家都知道Quicksort每次分割後即把比軸小的值和比軸大的值給cut開了。那麼,假如是想求前k小(以下源碼即是),我就只關心比軸小的一邊,繼續分割這一邊,直到如果分割後的元素沒有k個了,就結束循環。我們用源碼說話:
TopK問題即求序列中最大或最小的K個數。這裏以求最小K個數爲例。

快速排序的思想是使用一個基準元素將數組劃分成兩部分,左側都比基準數小,右側都比基準數大。

給定數組array[low…high],一趟快排劃分後的結果有三種:

1)如果基準數左側元素個數Q剛好是K-1,那麼在基準數左側(包含基準數本身),即爲TopK的所有元素。

2)如果基準數左側元素個數Q小於K-1,那麼說明基準數左側的Q個數都是TopK裏的元素,只需要在基準數的右側找出剩下的K-Q個元素即可。問題轉化成了以基準數下標爲起點,高位(high)爲終點的Top(K-Q)。遞歸下去即可。

3)如果基準數左側元素個數Q大於K-1,說明第K個位置,在基準數的左側,需要縮小搜索範圍,在低位(low)至基準數位置重複遞歸即可,最終問題會轉化成上面兩種情況。




























































發佈了84 篇原創文章 · 獲贊 145 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章