《劍指offer》習題解答(C/C++)


1.二維數組中的查找

/*
    題目:在一個二維數組中,沒一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。
    請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
*/
#include<stdio.h>
#include<string.h>

//    從右上角開始比較

bool Find(int *matrix, int rows, int columns, int number) {
    bool found = false;
    if (matrix != NULL && rows > 0 && columns > 0) {
        int row = 0;
        int column = columns - 1;
        while (row < rows&&column >= 0) {
            if (matrix[row*columns + column] == number) {
                found = true;
                break;
            }
            else if (matrix[row*columns + column ]> number)
                column--;
            else
                row++;
        }
    }
    return found;
}

//    從左下角開始比較
bool Find_2(int *arr, int rows, int columns, int number) {
    bool find = false;
    if (arr != NULL&&rows > 0 && columns > 0) {
        int row = rows - 1;
        int column = 0;
        while (row >=0 && column <= columns - 1) {
            if (arr[row*columns + column] == number) {
                find = true;
                break;
            }
            else if (arr[row*columns+column] < number)
                column++;
            else
                row--;
        }
    }
    return find;
}

int main() {
    int arr [4][4]= {
        {1, 2, 8, 9},
        {2,4 ,9 ,12},
        {4, 7, 10, 13},
        {6, 8, 11, 15}
    };
    Find_2(*arr, 4, 4, 7);
    return 0;
}

 2.字符串

  C/C++中的每個字符串都以’\0’結尾。為了節省空間,C/C++經常把常量字符串放到一個單獨的內存區域。當幾個指針賦值給相同的常量字符串時,它們實際會指向相同的地址空間。例如:

#include<stdio.h>
int main() {
    char str1[] = "Hello World";
    char str2[] = "Hello world";

    char *str3 = "Hello world";
    char *str4 = "Hello world";
    if (str1 == str2)
        printf("str1 and str2 are same. \n");
    else
        printf("str1 and str2 are not same.\n");
    if (str3 == str4)
        printf("str3 and str4 are same.\n");
    else
        printf("str3 and str4 are not same.\n");
    return 0;
}

輸出如下:

str1 and str2 are not same.
str3 and str4 are same.

  

  題目:請實現一個函數,把字符串中的每個空格替換成”%20”。例如輸入“We are happy.”,則輸出為”We%20are%20%20happy.”。實現代碼如下:

#include<stdio.h>
//    lenght 為字符數組string的總容量
void ReplaceSpace(char string[], int length) {
    if (string == NULL&&length <= 0)
        return;
    //    originalLenght為字符串string的實際長度
    int originalLenght = 0;
    int numOfBlank = 0;
    int i = 0;
    while (string[i] != '\0') {
        originalLenght++;
        if (string[i] == ' ')
            numOfBlank++;
        i++;
    }
    int newLenght = originalLenght + numOfBlank * 2;
    if (newLenght <= originalLenght)
        return;
    
    int indexOfOriginal = originalLenght;
    int indexOfNew = newLenght;
    while (indexOfOriginal >= 0 && indexOfNew > indexOfOriginal) {
        if (string[indexOfOriginal] == ' ') {
            string[indexOfNew--] = '0';
            string[indexOfNew--] = '2';
            string[indexOfNew--] = '%';
        }
        else
            string[indexOfNew--] = string[indexOfOriginal];
        indexOfOriginal--;
    }
}

int main() {
    char string[] = "We are happy.";
    int lenght = sizeof(string) / sizeof(char);
    ReplaceSpace(string, lenght);
    int j = 0;
    while (string[j] != '\0') {
        printf("%c", string[j]);
        j++;
    }
printf("\n");
    return 0;
}

輸出結果:

We%20are%20happy.

  

  相關題目:有兩個排序的數組A1和A2,內存在A1末尾有足夠的空余空間容納A2。請實現一個函數,把A2中的所有數字插入到A1中並且所有的數字是排序的。實現代碼如下:

#include<stdio.h>
// len1為數組arr1的有效數字的長度
void unionArray(int arr1[], int len1, int arr2[], int len2) {
    //    arr1_all是啊讓人整個數組的空間長度
    int arr1_all = len1 + len2;
    if (len2 ==0)
        return;
    int index_1 = len1-1;
    int index_2 = arr1_all-1;
    int index_3 = len2-1;
    while (index_2 > index_1) {
        if (arr1[index_1] > arr2[index_3]) {
            arr1[index_2--] = arr1[index_1];
            index_1--;
        }
        else
        {
            arr1[index_2--] = arr2[index_3];
            index_3--;
        }

    }
    
}

int main() {
    int arr1[9] = { 1,2,3,4 };
    int arr2[5] = { 5,6,7,8,9 };
    unionArray(arr1, 4, arr2, 5);
    int len = sizeof(arr1) / sizeof(int);
    int i = 0;
    while (i < len) {
        printf("%d", arr1[i]);
        ++i;
    }
    printf("\n");
    return 0;
}

輸出結果:

123456789

 3.鏈表

//    單向鏈表的節點定義
struct ListNode
{
    int m_nValue;
    ListNode *m_pNext;
};

//    往鏈表的末尾添加一個節點
void AddToTail(ListNode** pHead, int value) {
    ListNode*    pNew = new ListNode();    //    定義一個新節點指針並為它分配內存
    pNew->m_nValue = value;
    pNew->m_pNext = NULL;

    //    如果鏈表為空,則新添加的節點為頭節點,使頭指針pHead指向該節點
    if (pHead == NULL) {
        pHead = &pNew;
    }
    else {
        ListNode* pNode = *pHead;
        while (pNode->m_pNext != NULL)
            pNode = pNode->m_pNext;
        pNode->m_pNext = pNew;

    }    
}
//    刪除節點函數
void RemoveNode(ListNode** pHead, int value) {
    if (pHead == NULL || *pHead == NULL) {
        return;
    }
    ListNode *pToBeDeleted = NULL;
    if ((*pHead)->m_nValue == value) {
        pToBeDeleted = *pHead;
        *pHead = (*pHead)->m_pNext;
    }
    else {
        ListNode* pNode = *pHead;
        while (pNode->m_pNext != NULL&&pNode->m_pNext->m_nValue!=value) 
            pNode = pNode->m_pNext;
        if (pNode->m_pNext != NULL&&pNode->m_pNext->m_nValue == value) {
            pToBeDeleted = pNode->m_pNext;
            pNode->m_pNext = pNode->m_pNext->m_pNext;
        }
        if (pToBeDeleted != NULL) {
            delete pToBeDeleted;
            pToBeDeleted = NULL;
        }    
    }
}

題目:輸入一個鏈表的頭節點,從尾到頭打印出每個節點的值。

//    題目:輸入一個鏈表的頭節點,從尾到頭打印出每個節點的值。
//    方法一:從頭到尾遍歷鏈表,把節點依次放進一個棧中,當遍歷完整個鏈表后,再從棧頂開始輸出節點的值
void PrintListReversingly_Interatively(ListNode *pHead) {
    std::stack<ListNode*> nodes;
    
    ListNode *pNode = pHead;
    while (pNode != NULL) {
        nodes.push(pNode);
        pNode = pNode->m_pNext;
    }
    while (!nodes.empty()) {
        pNode = nodes.top();
        printf("%d\n", pNode->m_nValue);
        nodes.pop();
    }
}

//    方法二:利用遞歸。每次訪問一個節點時,先輸出它后面的節點
void PrintListReversingly_Recursively(ListNode* pHead) {
    if (pHead != NULL) {
        if (pHead->m_pNext != NULL) {
            PrintListReversingly_Recursively(pHead->m_pNext);
        }
        printf("%d\n", pHead->m_nValue);
    }
}

void haha(ListNode** pHead) {
    while ((*pHead)->m_pNext != NULL)
    {
        printf("%d\n", (*pHead)->m_nValue);
        (*pHead) = (*pHead)->m_pNext;
    }

}

 4.樹

  題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果都不含重復的數字。

#include<stdio.h>
#include<iostream>

//    二叉樹結點定義
struct  BinaryTreeNode
{
    int        m_nvalue;
    BinaryTreeNode*        m_pLeft;
    BinaryTreeNode*        m_pRight;
};

//    函數聲明
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder,
    int* startInorder, int* endInorder);

//    用遞歸的方法構建左右子樹和根節點

BinaryTreeNode* Construct(int *preorder, int *inorder, int length) {
    if (preorder == NULL || inorder == NULL || length <= 0) 
    {
        return NULL;
    }
    return ConstructCore(preorder,preorder+length-1,inorder,inorder+length-1);
}
BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, 
                                int* startInorder, int* endInorder) 
{
    //    前序遍歷第一個數字即為根節點的值
    int rootValue = startPreorder[0];
    BinaryTreeNode* root = new BinaryTreeNode();
    root->m_nvalue = rootValue;
    root->m_pLeft = NULL;
    root->m_pRight = NULL;
    if (startPreorder == endPreorder)
    {
        if (startInorder == endInorder&&*startPreorder == *startPreorder)
            return root;
        else
            throw std::exception("Invalid input");
    }
    //    在中序遍歷中找到根節點的值
    int* rootInorder = startPreorder;
    while (*rootInorder != rootValue && rootInorder <= endInorder)
    {
        ++rootInorder;
    }
    if (*rootInorder != rootValue&&rootInorder == endInorder)
        throw std::exception("Invalid input");

    int leftLength = rootInorder - startInorder;
    int* leftPreorderEnd = startInorder + leftLength;
    if (leftLength > 0) {
        //    構建左子樹
        root->m_pLeft = ConstructCore(startPreorder + 1, endPreorder, 
            startInorder, leftPreorderEnd - 1);
    }
    if (leftLength < endInorder - startInorder)
    {
        //    構建右子樹
        root->m_pRight = ConstructCore(leftPreorderEnd+1,endPreorder,
            rootInorder+1,endInorder);
    }
}

5.棧和隊列

  題目:用兩個棧實現一個隊列。隊列的聲明如下,請實現它的兩個函數appendTail和deleteHead,分別完成在隊列尾部插入節點和在隊列頭部刪除節點的功能。

//    隊列的聲明
template <typename T> class CQueue
{
public:
    CQueue(void);
    ~CQueue(void);

    // 在隊列末尾添加一個結點
    void appendTail(const T& node);

    // 刪除隊列的頭結點
    T deleteHead();

private:
    stack<T> stack1;
    stack<T> stack2;
};

template <typename T> CQueue<T>::CQueue(void)
{
}

template <typename T> CQueue<T>::~CQueue(void)
{
}

  接下來定義兩個函數:

//    往第一個棧中插入元素
template<typename T> void CQueue<T>::appendTail(const T& element)
{
    stack1.push(element);
}

//    刪除最先插入的元素
template<typename T> T CQueue<T>::deleteHead()
{
    //    如果第二個棧為空,把棧一的元素依次彈出插入棧二
    if (stack2.size() <= 0)
    {
        while (stack1.size()>0)
        {
            T& data = stack1.top();
            stack1.pop();
            stack2.push(data);
        }
    }

    if (stack2.size() == 0)
        throw new exception("queue is empty");

    //    依次彈出並返回棧二棧頂元素
    T head = stack2.top();
    stack2.pop();

    return head;
}

 6.查找和排序

  題目:把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。

//    利用二分查找法

#include<stdio.h>
#include<exception>
int MinInOrder(int* numbers, int index1, int index2);

int Min(int* numbers, int length)
{
    if (numbers == NULL || length <= 0)
        throw new std::exception("Invalid parameters");

    int index1 = 0;
    int index2 = length - 1;
    int indexMid = index1;
    while (numbers[index1] >= numbers[index2])
    {
        // 如果index1和index2指向相鄰的兩個數,
        // 則index1指向第一個遞增子數組的最后一個數字,
        // index2指向第二個子數組的第一個數字,也就是數組中的最小數字
        if (index2 - index1 == 1)
        {
            indexMid = index2;
            break;
        }

        // 如果下標為index1、index2和indexMid指向的三個數字相等,
        // 則只能順序查找
        indexMid = (index1 + index2) / 2;
        if (numbers[index1] == numbers[index2] && numbers[indexMid] == numbers[index1])
            return MinInOrder(numbers, index1, index2);

        // 縮小查找范圍
        if (numbers[indexMid] >= numbers[index1])
            index1 = indexMid;
        else if (numbers[indexMid] <= numbers[index2])
            index2 = indexMid;
    }

    return numbers[indexMid];
}

int MinInOrder(int* numbers, int index1, int index2)
{
    int result = numbers[index1];
    for (int i = index1 + 1; i <= index2; ++i)
    {
        if (result > numbers[i])
            result = numbers[i];
    }

    return result;
}

// ====================測試代碼====================
void Test(int* numbers, int length, int expected)
{
    int result = 0;
    try
    {
        result = Min(numbers, length);

        for (int i = 0; i < length; ++i)
            printf("%d ", numbers[i]);

        if (result == expected)
            printf("\tpassed\n");
        else
            printf("\tfailed\n");
    }
    catch (...)
    {
        if (numbers == NULL)
            printf("Test passed.\n");
        else
            printf("Test failed.\n");
    }
}

int main()
{
    // 典型輸入,單調升序的數組的一個旋轉
    int array1[] = { 3, 4, 5, 1, 2 };
    Test(array1, sizeof(array1) / sizeof(int), 1);

    // 有重復數字,並且重復的數字剛好的最小的數字
    int array2[] = { 3, 4, 5, 1, 1, 2 };
    Test(array2, sizeof(array2) / sizeof(int), 1);

    // 有重復數字,但重復的數字不是第一個數字和最后一個數字
    int array3[] = { 3, 4, 5, 1, 2, 2 };
    Test(array3, sizeof(array3) / sizeof(int), 1);

    // 有重復的數字,並且重復的數字剛好是第一個數字和最后一個數字
    int array4[] = { 1, 0, 1, 1, 1 };
    Test(array4, sizeof(array4) / sizeof(int), 0);

    // 單調升序數組,旋轉0個元素,也就是單調升序數組本身
    int array5[] = { 1, 2, 3, 4, 5 };
    Test(array5, sizeof(array5) / sizeof(int), 1);

    // 數組中只有一個數字
    int array6[] = { 2 };
    Test(array6, sizeof(array6) / sizeof(int), 2);

    // 輸入NULL
    Test(NULL, 0, 0);

    return 0;
}

7.遞歸和循環

  遞歸雖然有簡潔的優點,但是缺點也是顯著的。由於遞歸式函數調用自身,而函數的調用是由空間和時間消耗的,所以遞歸的效率不如循環。

  題目一:寫一個函數,輸入n,求斐波那契(Fibonacci)數列的第n項。斐波那契數列定義如下:

  F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2

  解答代碼:

#include<stdio.h>
#include <cassert>

// ====================方法1:遞歸====================
//    時間復雜度以n的指數方式遞增

long long Fibonacci_Solution1(unsigned int n)
{
    if (n <= 0)
        return 0;

    if (n == 1)
        return 1;

    return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2);
}

// ====================方法2:循環====================
//    時間復雜度為O(n)

long long Fibonacci_Solution2(unsigned n)
{
    int result[2] = { 0, 1 };
    if (n < 2)
        return result[n];

    long long  fibNMinusOne = 1;
    long long  fibNMinusTwo = 0;
    long long  fibN = 0;
    for (unsigned int i = 2; i <= n; ++i)
    {
        fibN = fibNMinusOne + fibNMinusTwo;

        fibNMinusTwo = fibNMinusOne;
        fibNMinusOne = fibN;
    }

    return fibN;
}

// ====================方法3:基於矩陣乘法====================
//    時間復雜度為O(logn)

struct Matrix2By2
{
    Matrix2By2
    (
        long long m00 = 0,
        long long m01 = 0,
        long long m10 = 0,
        long long m11 = 0
    )
        :m_00(m00), m_01(m01), m_10(m10), m_11(m11)
    {
    }

    long long m_00;
    long long m_01;
    long long m_10;
    long long m_11;
};

Matrix2By2 MatrixMultiply
(
    const Matrix2By2& matrix1,
    const Matrix2By2& matrix2
)
{
    return Matrix2By2(
        matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10,
        matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11,
        matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10,
        matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11);
}

Matrix2By2 MatrixPower(unsigned int n)
{
    assert(n > 0);

    Matrix2By2 matrix;
    if (n == 1)
    {
        matrix = Matrix2By2(1, 1, 1, 0);
    }
    else if (n % 2 == 0)
    {
        matrix = MatrixPower(n / 2);
        matrix = MatrixMultiply(matrix, matrix);
    }
    else if (n % 2 == 1)
    {
        matrix = MatrixPower((n - 1) / 2);
        matrix = MatrixMultiply(matrix, matrix);
        matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0));
    }

    return matrix;
}

long long Fibonacci_Solution3(unsigned int n)
{
    int result[2] = { 0, 1 };
    if (n < 2)
        return result[n];

    Matrix2By2 PowerNMinus2 = MatrixPower(n - 1);
    return PowerNMinus2.m_00;
}

// ====================測試代碼====================
void Test(int n, int expected)
{
    if (Fibonacci_Solution1(n) == expected)
        printf("Test for %d in solution1 passed.\n", n);
    else
        printf("Test for %d in solution1 failed.\n", n);

    if (Fibonacci_Solution2(n) == expected)
        printf("Test for %d in solution2 passed.\n", n);
    else
        printf("Test for %d in solution2 failed.\n", n);

    if (Fibonacci_Solution3(n) == expected)
        printf("Test for %d in solution3 passed.\n", n);
    else
        printf("Test for %d in solution3 failed.\n", n);
}

int main()
{
    Test(0, 0);
    Test(1, 1);
    Test(2, 1);
    Test(3, 2);
    Test(4, 3);
    Test(5, 5);
    Test(6, 8);
    Test(7, 13);
    Test(8, 21);
    Test(9, 34);
    Test(10, 55);

    Test(40, 102334155);

    return 0;
}

  題目二:一只青蛙一次可以跳上1級台階,也可以跳上2級.求該青蛙跳上一個n級台階有多少種跳法。

  其實這道題的解答就是求上面的斐波那契數列。

8.位計算

  位運算是把數字用二進制表示之后,對每一位上0或者1的運算。

  題目:請實現一個函數,輸入一個整數,輸出該數二進制表示中1的個數。例如把9表示成二進制是1001,有2位是1.因此如果輸入為9,輸出為2.

/*考慮到有負數的情況,如果只是對輸入進行簡單的右移,那么負數的高位會自動的添加1則可能會進入死循環*/

/*常規解法:
    不進行右移,先把輸入與標志位進行與判斷最低位是不是1,再把標志位進行左移判斷次低位是不是1
  依次類推直到最高位,即可計算出1的個數
*/

int NumberOf1(int n){
    int count=0;
    unsigned int flag=1;
    while(flag){
        if(n&flag)
            count++;
        flag<<1;
    }
    return count;

}

/*進階解法:
    可進行論證把一個數減去1,再和原來數做與運算,會把該整數最右邊一個1變成0。例如1100,減去1后為1011,兩數與后為1000。
*/
int NumberOf2(int n){
    int count=0;
    while(n){
        ++count;
        n=(n-1)&n;
    }
    return count;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM