《剑指offer》所有面试题及其参考代码

点击上方蓝字关注我,我们一起学编程
有任何疑问或者想看的内容,欢迎私信

微信搜索《编程笔记本》(codingbook2020),获取更多干活。

剑指 offer 面试题目录

文章目录

面试题 1 :赋值运算符函数

题目:如下为类型 CMyString 的声明,请为该类型添加赋值运算符函数。

class CMyString
{
public:
    CMyString(char *pData = nullptr);
    CMyString(const CMyString& str);
    ~CMyString();

private:
    char *m_pData;
}

CMyString& CMyString::operator=(const CMyString& str)
{
    if (&str != this) {
        CMyString strTmp(str);
        swap(strTmp.m_pData, m_pData);
    }

    return *this;
}

面试题 2 :实现 Singleton 模式

题目:设计一个类,我们只能生成该类的一个实例。


class Singleton
{
protected:
    Singleton() {}
private:
    static Singleton* p;
public:
    static Singleton* initance() {
        return p;
    }
};

singleton* Singleton::p = new Singleton;

面试题 3 :数组中重复的数字

题目一:找出数组中重复的数字
在一个长度为 n 的数组里的所有数字都在 0~n-1 的范围内。数组中某写数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了多少次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2,3,1,0,2,5,3} ,那么对应的输出是重复数字 2 或者 3 。


/*
方法:值与下标
时间复杂度:O(n)
空间复杂度:O(1)
*/

bool duplicate(vector<int> numbers, int *duplication)
{
    int len = numbers.size();

    if (len <= 0) {
        return false;
    }

    for (int i = 0; i < len; ++i) {
        while (numbers[i] != i) {
            if (numbers[i] == numbers[numbers[i]]) {
                *duplication = numbers[i];
                return true;
            }

            swap(numbers[i], numbers[numbers[i]])
        }
    }

    return false;
}

题目二:不修改数组找出重复的数字
在一个长度为 n+1 的数组里的所有数字都在 1~n 的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入数组。例如,如果输入长度为 7 的数组 {2,3,1,0,2,5,3} ,那么对应的输出是重复数字 2 或者 3 。


/*
方法:元素数量与值
时间复杂度:O(nlogn)
空间复杂度:O(1)
*/

int getDuplication(const vector<int>& numbers)
{
    int len = numbers.size();

    if (len <= 0) {
        return -1;
    }

    int start = 1;
    int end = len - 1;
    int middle;

    while (end >= start) {
        middle = start + ((end - start) >> 1);
        int count = countRange(numbers, start, middle);

        if (end == start) {
            if (count > 1) {
                return start;
            } else {
                return -1;
            }
        }

        if (count > middle - start + 1) {
            end = middle;
        } else {
            start = middle + 1;
        }
    }
}

int countRange(const vector<int>& numbers, int start, int end) {
    int len = numbers.size();

    if (len <= 0) {
        return 0;
    }

    int count = 0;
    for (int i = 0; i < len; ++i) {
        if (numbers[i] >= start && numbers[i] <= end) {
            ++count;
        }
    }

    return count;
}

面试题 4 :二维数组中的查找

题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。


/*
方法:确定缩小范围
时间复杂度:O(r+c)
空间复杂度:O(1)
*/

bool Find(const vector<vector<int>>& matrix, int number)
{
    bool find = false;
    int rows = matrix.size();
    int cols = matrix[0].size();

    if (rows > 0 && cols > 0) {
        int r = 0;
        int c = cols - 1;

        while (r < rows && c >= 0) {
            if (matrix[r][c] < number) {
                ++r;
            } else if (matrix[r][c] > number) {
                --c;
            } else {
                find = true;
                break;
            }
        }

        return find;
    }
}

面试题 5 :替换空格

题目:请实现一个函数,把字符串中的每个空格替换成 “%20” 。例如,输入 “We are happy.” ,则输出 “We%20are%20happy.” 。


/*
方法:双指针
时间复杂度:O(n)
空间复杂度:O(1)
*/

void ReplaceBlank(char string[], int length)
{
    if (string == nullptr || length <= 0) {
        return;
    }

    int originalLength = 0;    // string的实际长度
    int numberOfBlank = 0;
    int i = 0;

    while (string[i++] != '\0') {
        ++originalLength;

        if (string[i] == ' ') {
            ++numberOfBlank;
        }
    }

    int newLength = originalLength + numberOfBlank * 2;    // 替换空格后string的长度

    if (newLength > length) {
        return;
    }

    int indexOfOriginal = originalLength;
    int indexOfNew = newLength;

    while (indexOfOriginal >= 0 && indexOfNew > indexOfOriginal) {
        if (string[indexOfOriginal] == ' ') {
            string[indexOfNew--] = '0';
            string[indexOfNew--] = '2';
            string[indexOfNew--] = '%';
        } else {
            string[indexOfNew--] = string[indexOfOriginal];
        }

        --indexOfOriginal;
    }
}

面试题 6 :从尾到头打印链表

题目:输入一个链表的头节点,从尾到头反过来打印每个节点的值,链表节点定义如下:

struct ListNode 
{
    int m_nKey;
    ListNode *m_pNext;
}

/*
方法:迭代、递归
时间复杂度:O(n)、O(n)
空间复杂度:O(n)、O(1)+递归
*/

// 迭代方法
void PrintListReversely_Iteratively(ListNode *pHead)
{
    stack<int> st;
    ListNode *p = pHead;

    while (p != nullptr) {
        st.push(p->m_nKey);
        p = p->m_pNext;
    }

    while (!st.empty()) {
        cout << st.top() << endl;
        st.pop();
    }
}

// 递归方法
void PrintListReversely_Recursively(ListNode *pHead) 
{
    if (pHead != nullptr) {
        if (pHead->m_pNext != nullptr) {
            PrintListReversely_Recursively(pHead->m_pNext);
        }
        cout << pHead->m_nKey << endl;
    }
}

面试题 7 :重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,重建该二叉树。假设输入的前序遍历和中序遍历的结果都不含重复的数字。例如,输入前序遍历序列 {1,2,4,7,3,5,6,8} 和中序遍历序列 {4,7,2,1,5,3,8,6} ,则重建如图所示(略)的二叉树并输出它的头节点。二叉树的定义如下:

struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
}

TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
    if (pre.size() <= 0) {
        return nullptr;
    }
        
    int rootVal = pre[0];
    TreeNode* root = new TreeNode;
    root->val = rootVal;
    root->left = root->right = nullptr;
    
    if (pre.size() == 1) {
        return root;
    }
        
    int idx_rootVal = 0;
    while (vin[idx_rootVal] != rootVal) {
        ++idx_rootVal;
    }
        
    vector<int> left_pre(pre.begin() + 1, pre.begin() + 1 + idx_rootVal);
    vector<int> left_vin(vin.begin(), vin.begin() + idx_rootVal);
    vector<int> right_pre(pre.begin() + 1 + idx_rootVal, pre.end());
    vector<int> right_vin(vin.begin() + idx_rootVal + 1, vin.end());
        
    root->left = reConstructBinaryTree(left_pre, left_vin);
    root->right = reConstructBinaryTree(right_pre, right_vin);
        
    return root;
}

面试题 8 :二叉树的下一个节点

题目:给定一颗二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?树中的节点除了有两个分别指向左、右子节点的指针,还有一个指向父节点的指针。


TreeNode* getNext(TreeNode *pNode)
{
    if (pNode == nullptr) {
        return nullptr;
    }

    TreeNode *pNext;

    if (pNode->right != nullptr) {
        TreeNode *pRight = pNode->right;
        while (pRight->left != nullptr) {
            pRight = pRight->left;
        }
        pNext = pRight;
    } else if (pNode->parent != nullptr) {    // 一直找到是其父节点的左子节点
        TreeNode *pCurrent = pNode;
        TreeNode *pParent = pNode->parent;
        while (pParent != nullptr && pCurrent == pParent->right) {
            pCurrent = pParent;
            pParent = pParent->parent;
        }
        pNext = pParent;
    }

    return pNext;
}

面试题 9 :用两个栈实现队列

题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入节点和在队列头部删除节点的功能。

template<typename T> class CQueue
{
public:
    CQueue();
    ~CQueue();

    void appendTail(const T& node);
    T deleteHead();

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

template<typename T> void CQueue<T>::appendTail(const T& element)
{
    stack1.push(element);
}

template<typename T> T CQueue::deleteHead()
{
    if (stack2.empty()) {
        while (!stack1.empty()) {
            stack2.push(stack1.top());
            stack1.pop();
        }
    }
    
    T head = stack2.top();
    stack2.pop();

    return head;
}

面试题 10 :斐波那契数列

题目一:求斐波那契数列的第 n 项
写一个函数,输入 n ,求斐波那契数列的第 n 项。斐波那契数列的定义如下:

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

/*
方法:自下而上
*/

long long Fibonacci(unsigned n)
{
    if (n == 0 || n == 1) {
        return n;
    }

    long long numberN_1 = 0;
    long long numberN_2 = 1;
    long long res = 0;

    for (int i = 2; i <= n; ++i) {
        res = numberN_1 + numberN_2;
        numberN_1 = numberN_2;
        numberN_2 = res;
    }

    return res;
}

题目二:青蛙跳台阶问题
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。


/*
方法:自下而上
*/

long long jumpFloor(unsigned n)
{
    if (n == 1 || n == 2) {
        return n;
    }

    long long numberN_1 = 1;
    long long numberN_2 = 2;
    long long res = 0;

    for (int i = 3; i <= n; ++i) {
        res = numberN_1 + numberN_2;
        numberN_1 = numberN_2;
        numberN_2 = res;
    }

    return res;
}

面试题 11 :旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 {3,4,5,1,2} 为 {1,2,3,4,5} 的一个旋转,该数组的最小值为 1 。


/*
方法:二分查找
时间复杂度:O(logn)
空间复杂度:O(1)
*/

int Min(const vector<int>& numbers)
{
    int len = numbers.size();

    if (len <= 0) {
        return -1;
    }

    int s = 0;
    int e = len - 1;
    int m;

    // 未旋转
    if (numbers[s] < numbers[e]) {
        return numbers[s];
    }

    while (e - s > 1) {
        m = s + ((e - s) >> 1);
        
        // 三者相同时,无法缩小范围,初始下标后移一位
        if (numbers[s] == numbers[m] && numbers[m] == numbers[e]) {
            ++s;
        } else if (numbers[s] <= numbers[m]) {
            s = m;
        } else if (numbers[e] >= numbers[m]) {
            e = m;
        }
    }

    return numbers[e];
}

面试题 12 :矩阵中的路径

题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。


/*
方法:回溯法
*/

bool hasPath(const vector<vector<char>>& matrix, const vector<char>& str)
{
    int rows = matrix.size();
    int cols = matrix[0].size();

    if (rows <= 0 || cols <= 0 || str.size() <= 0) {
        return false;
    }

    vector<bool> visited(rows * cols, false);
    int pathLength = 0;

    for (int r = 0; r < rows; ++r) {
        for (int c = 0; c < cols; ++c) {
            // 以 (r,c) 为起点判断路径是否存在
            if (hasPathCore(matrix, r, c, visited, str, pathLength)) {
                return true;
            }
        }
    }

    return false;
}

bool hasPathCore(const vector<vector<char>>& matrix, int r, int c, vector<bool>>& visited, const vector<char>& str, int pathLength) {
    if (pathLength == str.size()) {
        return true;
    }

    int rows = matrix.size();
    int cols = matrix[0].size();

    bool hasPath = false;

    if (r >= 0 && r < rows && c >= 0 && c < cols && matrix[r][c] == str[pathLength] && !visted[r * cols + c]) {
        ++pathLength;
        visted[r * cols + c] = true;
    }

    hasPath = hasPath(matrix, r, c - 1, visited, str, pathLength)\
              || hasPath(matrix, r, c + 1, visited, str, pathLength)\
              || hasPath(matrix, r - 1, c, visited, str, pathLength)\
              || hasPath(matrix, r + 1, c, visited, str, pathLength);

    // 回溯
    if (!hasPath) {
        --pathLength;
        visted[r * cols + c] = false;
    }

    return hasPath;
}

面试题 13 :机器人的运动范围

题目:地上有一个 m 行 n 列的方格。一个机器人从坐标 (0,0) 的格子开始移动,他每次可以向左、右、上、下移动一格,但不能进入行坐标和列坐标之和大于 k 的格子。例如,当 k 为 18 时,机器人能够进入方格 (35,37) ,因为 3+5+3+7=18 。但它不能进入 (35,38) ,因为 3+5+3+8=19 。请问该机器人能够到达多少个格子?


/*
方法:回溯法
*/

int movingCount(int rows, int cols, int threshold)
{
    if (threshold < 0 || rows <= 0 || cols <= 0) {
        return 0;
    }

    vector<bool> visited(rows * cols, false);

    return movingCountCore(rows, cols, 0, 0, visited, threshold);
}

int movingCountCore(int rows, int cols, int r, int c, const vector<bool>& visited, int threshold) {
    int count = 0;

    if (check(rows, cols, r, c, visited, threshold)) {
        visited[r * cols + c] = true;

        count = 1 + movingCountCore(rows, cols, r - 1, c, visited, threshold)\
                  + movingCountCore(rows, cols, r + 1, c, visited, threshold)\
                  + movingCountCore(rows, cols, r, c - 1, visited, threshold)\
                  + movingCountCore(rows, cols, r, c + 1, visited, threshold);
    }

    return count;
}

// 判断 (r,c) 格子能否进入
bool check(int rows, int cols, int r, int c, vector<bool>& visited, int threshold) {
    if (r >= 0 && r < rows && c >= 0 && c < cols && !visited[r * cols + c] && getDigitSum(r) + getDigitSum(c) <= threshold) {
        return true;
    } else {
        return false;
    }
}

// 获取数位之和
int getDigitSum(int num) {
    int sum = 0;

    while (num) {
        sum += num % 10;
        num /= 10;
    }

    return sum;
}

面试题 14 :剪绳子

题目:给你一根长度为 n 的绳子,请把绳子剪成 m 段(m、n 都是整数且均大于 1),每段绳子的长度记为 k[0],k[1],…,k[m] 。请问 k[0]*k[1]*…*k[m] 可能的最大乘积是多少?例如,当绳子的长度是 8 时,我们把它剪成长度分别为 2,3,3 的三段,此时得到的最大乘积是 18 .


/*
方法:动态规划
*/

int maxProductAfterCutting_1(int length)
{
    if (length <= 1) {
        return 0;
    } else if (length <= 3) {
        return length - 1;
    }

    int max = 0;
    int product;
    // products[i] 表示将长度为 i 的绳子剪切后所得的最大乘积
    vector<int> products(length + 1);

    products[0] = 0;
    products[1] = 1;
    products[2] = 2;
    products[3] = 3;

    for (int i = 4; i <= length; ++i) {
        max = 0;

        for (int j = 1; j <= i / 2; ++j) {
            product = products[j] * products[i - j];
            if (max < product) {
                max = product;
            }
        }

        products[i] = max;
    }

    max = products[length];

    return max;
}

/*
方法:贪婪算法
*/

int maxProductAfterCutting_2(int length)
{
    if (length <= 1) {
        return 0;
    } else if (length <= 3) {
        return length - 1;
    }

    // 尽可能多的剪成 3 米的段
    int timesOfThree = length / 3;

    // 最后 4 米剪成 2 米和 2 米
    if (length - timesOfThree * 3 == 1) {
        --timesOfThree;
    }

    int timesOfTwo = (length - timesOfThree * 3) / 2;

    return static_cast<int>(pow(3, timesOfThree) * pow(2, timesOfTwo));
}

面试题 15 :二进制中 1 的个数

题目:请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制的 1001 ,有 2 位是 1 。因此,如果输入 9 ,则函数输出 2 。


/*
方法:位运算
*/

int numberOfOne(int number)
{
    int count = 0;

    while (number) {
        ++count;
        number &= number - 1;
    }

    return count;
}

面试题 16 :数值的整数次方

题目:实现函数 double Power(double base, int exponent),求 base 的 exponent 次方。不得使用库函数,同时不需要考虑大数问题。


double Power(double base, int exponent)
{
    // 遇非法输入时输出零,必要时可使用全局布尔变量区分正常零和异常零
    if (equalWithZero(base) && exponent <= 0) {
        return 0.0;
    }
    if (!equalWithZero(base) && exponent == 0) {
        return 0.0;
    }

    if (exponent < 0) {
        return 1.0 / PowerAbs(base, 0 - exponent);
    } else {
        return PowerAbs(base, exponent);
    }

// 计算正数次幂    
double PowerAbs(double base, int exponent) {
    if (exponent == 1) {
        return base;
    }
        
    double tmp = PowerAbs(base, exponent >> 1);
    double res = tmp * tmp;
    if (exponent & 1) {
        return base * res;
    } else {
        return res;
    }
}

// 判断一个 double 是否为零
bool equalWithZero(double num) {
    if (num > -0.00001 && num < 0.00001) {
        return true;
    } else {
        return false;
    }
}

面试题 17 :打印从 1 到最大的 n 位数

题目:输入数字 n ,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3 ,则打印出 1、2、3 一直到最大的三位数 999 。


/*
方法:全排列的递归
*/
void printOneToMaxNDigits(int n)
{
    if (n <= 0) {
        return;
    }

    vector<char> number(n, '0');

    for (int i = 0; i < 10; ++i) {
        number[0] += i;
        printOneToMaxNDigitsRecursively(number, n, 0);
    }
}

void printOneToMaxNDigitsRecursively(vector<char>& number, int n, int idx) {
    if (idx == n) {
        printNumber(number);
        return;
    }
    
    for (int i = 0; i < 10; ++i) {
        number[idx + 1] += i;
        printOneToMaxNDigitsRecursively(number, n, idx + 1);
    }
}

void printNumber(const vector<char>& number) {
    int len = number.size();
    bool left_trim = true;

    for (int i = 0; i < len; ++i) {
        if (left_trim && number[i] != '0') {
            left_trim = false;
        }

        if (!left_trim) {
            cout << number[i];
        }
    }

    cout << "\t";
}

面试题 18 :删除链表的节点

题目一:在 O(1) 时间内删除链表节点
给定单向链表的头指针和一个节点指针,定义一个函数在 O(1) 时间内删除该节点。链表节点与函数的定义如下:

struct ListNode
{
    int m_nValue;
    ListNode *m_pNext;
};

void DeleteNode(ListNode **pListHead, ListNode *pToBeDelete);

/*
方法:等价
*/

// 要在常数时间内删除单链表节点,可将其下一节点复制至本节点,等价地删除下一节点接口
void DeleteNode(ListNode **pListHead, ListNode *pToBeDelete)
{
    if (pListHead == nullptr || pToBeDelete == nullptr) {
        return;
    }

    // 要删除的节点不是尾节点
    if (pToBeDelete->m_pNext != nullptr) {
        ListNode *pNext = pToBeDelete->m_pNext;
        pToBeDelete->m_nValue = pNext->m_nValue;
        pToBeDelete->m_pNext = pNext->m_pNext;

        delete pNext;
        pNext = nullptr;
    }
    // 链表只有一个节点,删除头节点(也是尾节点)
    else if (*pListHead == pToBeDelete) {
        delete pToBeDelete;
        pToBeDelete = nullptr;
        *pListHead = nullptr;
    }
    // 链表中有多个节点,删除尾节点
    else {
        ListNode *pNode = *pListHead;
        while (pNode->m_pNext != pToBeDelete) {
             pNode->m_pNext = pNode;   
        }
        pNode->m_pNext = nullptr;
        delete pToBeDelete;
        pToBeDelete = nullptr;
    }
}

题目二:删除链表中重复的节点
在一个排序的链表中,如何删除重复的节点?例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5 。


void deleteDuplication(ListNode **pHead)
{
    if (pHead == nullptr || *pHead == nullptr) {
            return;
    }
        
    ListNode *pPreNode = *nullptr;
    ListNode *pCurNode = *pHead;
        
    while (pCurNode != nullptr) {
        ListNode *pNext = pCurNode->next;
        bool needDelete = false;
            
        if (pNext != nullptr && pCurNode->val == pNext->val) {
            needDelete = true;
        }
            
        if (!needDelete) {
            pPreNode = pCurNode;
            pCurNode = pNext;
        } else {
            int deleteValue = pCurNode->val;
            ListNode *pToBeDelete = pCurNode;
                
            while (pToBeDelete != nullptr && pToBeDelete->val == deleteValue) {
                pNext = pToBeDelete->next;
                delete pToBeDelete;
                pToBeDelete = nullptr;
                pToBeDelete = next;
            }
                
            if (pPreNode == nullptr) {
                *pHead = pNext;
             } else {
                pPreNode->next = pNext;
            }
                
            pCurNode = pNext;
        }
    }
}

面试题 19 :正则表达式匹配

题目:请实现一个函数用来匹配包含 ‘.’ 和 ‘*’ 的正则表达式。模式中的字符 ‘.’ 表示任意一个字符,而 ‘*’ 表示它前面的字符可以出现任意次(含 0 次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 “aaa” 与模式 “a.a” 和 “abaca” 匹配。但与 “aa.a” 和 “ab*a” 均不匹配。


bool match(char *str, char *pattern)
{
    if (str == nullptr || pattern == nullptr) {
        return false;
    }

    return matchCore(str, pattern);
}

bool matchCore(char *str, char *pattren) {
    if (*str == '\0' && *pattren == '\0') {
        return true;
    }
    if (*str != '\0' && *pattren == '\0') {
        return false;
    }

    if (*(pattren + 1) == '*') {
        if (*str == *pattren || (*pattren == '.' && *str != '\0')) {
            //     当前字符匹配,忽略 '*'               当前字符匹配,不忽略 '*'        忽略模式串当前字符和 '*'
            return matchCore(str + 1, pattren + 2) || matchCore(str + 1, pattren) || matchCore(str, pattren + 2);
        } else {
            //     当前字符不匹配,忽略模式串当前字符和 '*'
            return matchCore(str, pattren + 2);
        }
    }

    if (*str == *pattren || (*pattren == '.' && *str != '\0')) {
        return matchCore(str + 1, pattren + 1);
    }

    return false;
}

面试题 20 :表示数值的字符串

题目:请实现一个函数,用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 “+100”、“5e2”、"-123"、“3.1416” 及 “-1E-16” 都表示数值,但 “12e”、“1a3.14”、“1.2.3”、“±5” 及 “12e+5.4” 都不是。


bool isNumeric(const char* str)
{
    if (str == nullptr) {
        return false;
    }
    if (*str == '+' || *str == '-') {
        ++str;
    }
    if (*str == '\0') {
        return false;
    }

    bool integer = false;
    bool dot = false;
    bool exponent = false;

    while (*str != '\0') {
        if (*str >= '0' && *str <= '9') {
            integer = true;
            ++str;
        } else if (*str == '.') {
            if (dot || exponent) {
                return false;
            }
            dot = true;
            ++str;
        } else if (*str == 'e' || *str == 'E') {
            if (!integer || exponent) {
                return false;
            }
            exponent = true;
            ++str;
            if (*str == '+' || *str == '-') {
                ++str;
            }
            if (*str == '\0') {
                return false;
            }
        } else {
            return false;
        }
    }

    return true;
}

面试题 21 :调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。


/*
方法:双指针
*/

void RecorderOddEven(vector<int>& data)
{
    if (data.size() <= 0) {
        return;
    }

    int start = 0;
    int end = data.size() - 1;

    while (start < end) {
        if ((data[start] & 0x1) == 0 && (data[end] & 0x1) == 1) {
            swap(data[start], data[end]);
            ++start;
            --end;
        } else {
            if ((data[start] & 0x1) == 1) {
                ++start;
            }
            if ((data[end] & 0x1) == 0) {
                --end;
            }
        }
    }
}

面试题 22 :链表中倒数第 k 个节点

题目:输入一个链表,输出该链表中倒数第 k 个节点。为了符合大多数人的习惯,本题从 1 开始计数,即链表的尾节点是倒数第 1 个节点。例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1,2,3,4,5,6 。这个链表的倒数第 3 个节点是值为 4 的节点。链表节点定义如下:

struct ListNode
{
    int m_nValue;
    ListNode *m_pNext;
}

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
    ListNode* pAhead = pListHead;

    if (k == 0) {
        return nullptr;
    }

    while (k--) {
        if (!pAhead) {
            return nullptr;
        }
        pAhead = pAhead->m_pNext;
    }
        
    while (pAhead) {
        pAhead = pAhead->m_pNext;
        pListHead = pListHead->m_pNext;
    }
        
    return pListHead;
}

面试题 23 :链表中环的入口节点

题目:如果一个链表中包含环,如何找出环的入口节点?


/*
方法:快慢指针
*/

ListNode* EntryNodeOfLoop(ListNode* pHead)
{
    // 快慢指针在环中相遇的位置
    ListNode *meetingNode = MeetingNode(pHead);
    if (meetingNode == nullptr) {
        return nullptr;
    }
        
    // 计算环中有多少节点
    int numOfNodesInLoop = 1;
    ListNode *pNode1 = meetingNode;
    while (pNode1->next != meetingNode) {
        ++numOfNodesInLoop;
        pNode1 = pNode1->next;
    }
        
    // pNode1 先走环中节点数的步数
    pNode1 = pHead;
    while (numOfNodesInLoop--) {
        pNode1 = pNode1->next;
    }
        
    // pNode1、pNode2 一起走,相遇的节点即为环的入口
    ListNode *pNode2 = pHead;
    while (pNode1 != pNode2) {
        pNode1 = pNode1->next;
        pNode2 = pNode2->next;
    }
        
    return pNode1;
}
    
ListNode* MeetingNode(ListNode *pHead) {
    ListNode *pFast = pHead;
    ListNode *pSlow = pHead;
        
    while (pFast) {
        if (pFast->next) {
            pFast = pFast->next->next;
        } else {
            return nullptr;
        }
            
        pSlow = pSlow->next;
        
        if (pFast == pSlow) {
            return pFast;
        }
    }
        
    return nullptr;
}

面试题 24 :反转链表

题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。链表节点定义如下:

struct ListNode
{
    int m_nKey;
    ListNode *m_pNext;
}

/*
方法:前驱指针
*/

ListNode* ReverseList(ListNode *pHead)
{
    ListNode *pre = nullptr;
    ListNode *cur = pHead;
    ListNode *tmp = nullptr;

    while (cur) {
        tmp = cur->m_pNext;
        cur->m_pNext = pre;
        pre = cur;
        cur = tmp;
    }

    return pre;
}

面试题 25 :合并两个排序的链表

题目:输入两个递增排序的链表,合并这两个链表并使新链表的节点仍然是递增排序的。链表节点定义如下:

struct ListNode
{
    int m_nKey;
    ListNode *m_pNext;
}

/*
方法:前驱节点
*/

ListNode* Merge(ListNode *pHead1, ListNode *pHead2)
{
    if (pHead1 == nullptr) {
        return pHead2;
    }
    if (pHead2 == nullptr) {
        return pHead1;
    }

    ListNode *fake = new ListNode;
    ListNode *cur = fake;

    while (pHead1 && pHead2) {
        if (pHead1->m_nKey <= pHead2->m_nKey) {
            cur->m_pNext = pHead1;
            cur = pHead1;
            pHead1 = phead1->m_pNext;
        } else {
            cur->m_pNext = pHead2;
            cur = pHead2;
            pHead2 = phead2->m_pNext;
        }
    }

    if (!pHead1) {
        cur->m_pNext = pHead2;
    }
    if (!pHead2) {
        cur->m_pNext = pHead1;
    }

    return fake->next;
}

面试题 26 :树的子结构

题目:输入两棵二叉树 A 和 B ,判断 B 是不是 A 的子结构。二叉树节点的定义如下:

struct BinaryTreeNode
{
    double m_dbVale;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
}

bool HahSubtree(BinaryTreeNode *pRoot1, BinaryTreeNode *pRoot2)
{
    bool hasSubtree = false;

    if (pRoot1 && pRoot2) {
        if (Equal(pRoot1->m_dbValue == pRoot2->m_dbValue)) {
            hasSubtree = DoesTree1HasTree2(pRoot1, pRoot2);
        }
        if (!hasSubtree) {
            hasSubtree = DoesTree1HasTree2(pRoot1->m_pLeft, pRoot2);
        }
        if (!hasSubtree) {
            hasSubtree = DoesTree1HasTree2(pRoot1->m_pRight, pRoot2);
        }
    }

    return hahSubtree;
}

bool Equal(double a, double b) {
    if (a - b >= -0.00001 && a - b <= 0.00001) {
        return true;
    } else {
        return false;
    }
}

bool DoesTree1HasTree2(BinaryTreeNode *pRoot1, BinaryTreeNode *pRoot2) {
    if (pRoot2 == nullptr) {
        return true;
    }
    if (pRoot1 == nullptr) {
        return false;
    }
    if (!Equal(pRoot1->m_dbValue == pRoot2->m_dbValue)) {
        return false;
    }

    return DoesTree1HasTree2(pRoot1->m_pLeft, pRoot2->m_pLeft) && DoesTree1HasTree2(pRoot1->m_pRight, pRoot2->m_pRight);
}

面试题 27 :二叉树的镜像

题目:请完成一个函数,输入一棵二叉树,该函数输出它的镜像。二叉树的节点定义如下:

struct BinaryTreeNode
{
    double m_nVale;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
}

void MirrorRecursively(BinaryTreeNode *pNode)
{
    if (pRoot){
        swap(pRoot->left, pRoot->right);
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
}

面试题 28 :对称的二叉树

题目:请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。


/*
方法:双指针
*/

bool isSymmetrical(BinaryTreeNode *pRoot)
{
    return isSymmetrical(pRoot, pRoot);
}

bool isSymmetrical(BinaryTreeNode *pRoot1, BinaryTreeNode *pRoot2) {
    if (!pRoot1 && !pRoot2) {
        return true;
    }
    if (!pRoot1 || !pRoot2) {
        return false;
    }
    if (pRoot1->m_nValue != pRoot2->m_nValue) {
        return false;
    }

    return isSymmetrical(pRoot1->m_pLeft, pRoot2->m_pRight) && isSymmetrical(pRoot1->m_pRight, pRoot2->m_pLeft);
}

面试题 29 :顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。


 void printMatrix(vector<vector<int> > matrix) {
        int row = matrix.size();
        int col = matrix[0].size();
         
        if (row <= 0 || col <= 0) {
            return;
        }
         
        // 定义变量存储上、下、左、右的范围
        int left = 0, top = 0, right = col - 1, bottom = row - 1;

        while (left <= right && top <= bottom) {
            // 从左到右
            for (int i = left; i <= right; ++i) {
                cout << matrix[top][i] << "\t";
            }
            // 从上到下
            for (int i = top + 1; i <= bottom; ++i) {
                cout << matrix[i][right] << "\t";
            }
            // 从右到左
            if (top != bottom) { 
                for (int i = right - 1; i >= left; --i) {
                    cout << matrix[bottom][i] << "\t";
                }
            }
            // 从下到上
            if (left != right) {
                for (int i = bottom - 1; i > top; --i) {
                    cout << matrix[i][left] << "\t";
                }
            }

            // 调整范围
            left++,top++,right--,bottom--;
        }
    }

面试题 30 :包含 min 函数的栈

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数。在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1) 。


/*
stackData:数据栈
stackMin:辅助栈
*/

template<typename T> void StackWithMin<T>::push(const T& value)
{
    stackData.push(value);
    if (stackMin.empty() || value <= stackMin.top()) {
        stackMin.push(value);
    }
}

template<typename T> void StackWithMin<T>::pop()
{
    if (stackData.top() == stackMin.top()) {
        stackMin.pop();
    }
    stackData.pop();
}

template<typename T> T StackWithMin<T>::min() const
{
    return stackMin.top();
}

面试题 31 :栈的压入、弹出序列

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。


bool IsPopOrder(const vector<int>& pushV, const vector<int>& popV)
{
    stack<int> st;
    int idx = 0;

    for (int i = 0; i < popV.size(); ++i) {
        while (idx < pushV.size() && (st.empty() || st.top() != popV[i])) {
            st.push(pushV[idx++]);
        }
        
        if (st.top() == popV[i]) {
            st.pop();
        } else if (idx == pushV.size()){
            return false;
        }
    }
    
    return true;
}

面试题 32 :从上到下打印二叉树

题目一:不分行从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。二叉树节点的定义如下:

struct BinaryTreeNode
{
    int m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
}

/*
方法:层次遍历的非递归形式
*/

void PrintFromTopToBottom(BinaryTreeNode *pRoot)
{
    queue<BinaryTreeNode*> q;

    if (pRoot) {
        q.push(pRoot);
    }

    BinaryTreeNode *pNode = nullptr;

    while (!q.empty()) {
        pNode = q.front();
        q.pop();

        cout << pNode->m_nValue << "\t";

        if (pNode->m_pLeft) {
            q.push(pNode->m_pLeft);
        }
        if (pNode->m_pRight) {
            q.push(pNode->m_pRight);
        }
    }
}

题目二:分行从上到下打印二叉树
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印一行。


void PrintFromTopToBottom(BinaryTreeNode *pRoot)
{
    queue<BinaryTreeNode*> q;

    if (pRoot) {
        q.push(pRoot);
    }

    int toBePrint = 1;
    int nextLevel = 0;
    BinaryTreeNode *pNode = nullptr;

    while (!q.empty()) {
        pNode = q.front();
        q.pop();
        --toBePrint;

        cout << pNode->m_nValue << "\t";

        if (pNode->m_pLeft) {
            q.push(pNode->m_pLeft);
            ++nextLevel;
        }
        if (pNode->m_pRight) {
            q.push(pNode->m_pRight);
            ++nextLevel;
        }

        if (toBePrint == 0) {
            cout << endl;
            toBePrint = nextLevel;
            nextLevel = 0;
        }
    }
}

题目三:之字形打印二叉树
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二行按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。


void Print(BinaryTreeNode *pRoot)
{
    if (pRoot == nullptr) {
        return;
    }

    stack<BinaryTreeNode*> levels[2];
    int current = 0;
    int next = 1;

    levels[current].push(pRoot);

    while (!levels[0].empty() || !levels[1].empty()) {
        BinaryTreeNode *pNode = levels[current].top();
        levels[current].pop();

        cout << pNode->m_nValue << " ";

        if (current == 0) {
            if (pNode->m_pLeft) {
                levels[next].push(pNode->m_pLeft);
            }
            if (pNode->m_pRight) {
                levels[next].push(pNode->m_pRight);
            }
        } else {
            if (pNode->m_pRight) {
                levels[next].push(pNode->m_pRight);
            }
            if (pNode->m_pLeft) {
                levels[next].push(pNode->m_pLeft);
            }
        }

        if (levels[current].empty()) {
            cout << "\n";
            current = 1 - current;
            next = 1 - next;
        }
    }
}

面试题 33 :二叉搜索树的后序遍历序列

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true ,否则返回 false 。假设输入的数组任意两个数字都互不相同。


bool VerifySquenceOfBST(vector<int> sequence) {
    int len = sequence.size();
    if (len <= 0) {
        return false;
    }
        
    int rootVal = sequence[len - 1];
    int idx;
        
    for (idx = 0; idx < len - 1; ++idx) {
        if (sequence[idx] > rootVal) {
            break;
        }
    }
        
    for (int i = idx; i < len - 1; ++i) {
        if (sequence[i] < rootVal) {
            return false;
        }
    }
        
    vector<int> leftV(sequence.begin(), sequence.begin() + idx);
    vector<int> rightV(sequence.begin() + idx, sequence.end() - 1);
        
    bool left = true, right = true;
        
    if (leftV.size() > 0) {
        left = VerifySquenceOfBST(leftV);
    }
    if (rightV.size() > 0) {
        right = VerifySquenceOfBST(rightV);
    }
    return left && right;
}

面试题 34 :二叉树中和为某一值的路径

题目:输入一个二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径,二叉树的节点定义如下:

struct BinaryTreeNode
{
    int m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
}

void FindPath(BinaryTreeNode* pRoot,int expectNumber)
{
    if (pRoot == nullptr) {
        return;
    }

    vector<int> path;
    int currentSum = 0;
    FindPath(pRoot, expectNumber, path, currentSum);
}

void FindPath(BinaryTreeNode* pRoot,int expectNumber, vector<int>& path, int currentSum) {
    currentSum += pRoot->m_nValue;
    path.push(pRoot->m_nValue);

    bool isLeaf = pRoot->m_pLeft == nullptr && pRoot->m_pRight == nullptr;

    // 路径和为期望值且已到达叶节点
    if (currentSum == expectNumber && isLeaf) {
        for (auto e : path) {
            cout << e << "\t";
        }
        cout << "\n";
    }

    // 未到达叶节点
    if (pRoot->m_pLeft) {
        FindPath(pRoot->m_pLeft, expectNumber, path, currentSum);
    }
    if (pRoot->m_pRight) {
        FindPath(pRoot->m_pRight, expectNumber, path, currentSum);
    }

    // 回溯
    path.pop();
}

面试题 35 :复杂链表的复制

题目:请实现一个函数,用于复制一个复杂链表。在复杂链表中,每个节点除了有一个 m_pNext 指针指向下一个节点,还有一个 m_pSibling 指针指向链表中的任意节点或者 nullptr 。节点的定义如下:

struct ComplexListNode
{
    int m_nValue;
    ComplexListNode *m_pNext;
    ComplexListNode *m_pSibling;
}

RandomListNode* Clone(RandomListNode* pHead)
{
    if (!pHead) {
        return NULL;
    }

    nodeClone(pHead);
    connectRandom(pHead);
    return reconnect(pHead);
}
     
     
//[1]复制结点,插入到原结点后方
void nodeClone(RandomListNode *head)
{
    RandomListNode *pNode = head;
    while (pNode != NULL)
    {
        RandomListNode *pClone = new RandomListNode;
        pClone->m_nValue = pNode->label;
        pClone->m_pNext = pNode->next;
        pNode->next = pClone;
        pNode = pClone->next;
    }
}
 
//[2]还原新结点的random指针
void connectRandom(RandomListNode *head)
{
    RandomListNode *pNode = head;
     
    while (pNode != NULL)
    {
        RandomListNode *pClone = pNode->next;
        if (pNode->random)
        {
            pClone->random = pNode->random->next;
        }
        pNode = pClone->next;
    }
}
 
//[3]拆分
RandomListNode *reconnect(RandomListNode *head)
{
    RandomListNode *pNode = head;
    RandomListNode *result = head->next;
    while (pNode != NULL)
    {
        RandomListNode *pClone = pNode->next;
        pNode->next = pClone->next;
        pNode = pNode->next;
        if (pNode != NULL) {
            pClone->next = pNode->next;
        }
    }
    return result;
}

面试题 36 :二叉搜索树与双向链表

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。二叉树的节点定义如下:

struct BinaryTreeNode
{
    int m_nValue;
    BinaryTreeNode *m_pLeft;
    BinaryTreeNode *m_pRight;
}

TreeNode* Convert(TreeNode* pRootOfTree)
{
    if (pRootOfTree == nullptr) {
        return nullptr;
    }
        
    // 找到最左子节点,也即调整后链表的头节点
    TreeNode *head = pRootOfTree, *pre = nullptr;
    while (head->left != nullptr) {
        head = head->left;
    }

    inorder(pRootOfTree, pre);
        
    return head;
}
    
void inorder(TreeNode* pRootOfTree, TreeNode*& pre) {
    if (pRootOfTree != nullptr) {
        inorder(pRootOfTree->left, pre);
     
        pRootOfTree->left = pre;
        if (pre != nullptr) {
            pre->right = pRootOfTree;
        }
        pre = pRootOfTree;
            
        inorder(pRootOfTree->right, pre);
    }
}

面试题 37 :序列化二叉树

题目:请实现两个函数,分别用来序列化和反序列化二叉树。


void Serialize(BinaryTreeNode *pRoot, ostream& stream)
{
    if (pRoot == nullptr) {
        stream << "$,";
        return;
    }

    stream << pRoot->m_nValue << ",";
    Serialize(pRoot->m_pLeft, stream);
    Serialize(pRoot->m_pRight, stream);
}

void Deserialize(BinaryTreeNode*& pRoot, istream& stream)
{
    int number;

    if (ReadStream(stream, &number)) {
        pRoot = new BinaryTreeNode;
        pRoot->m_nValue = number;
        pRoot->m_pLeft = nullptr;
        pRoot->m_pRight = nullptr;

        Deserialize(pRoot->m_pLeft, stream);
        Deserialize(pRoot->m_pRight, stream);
    }
}

// 每次从流中读出一个数字或者'$'
bool ReadStream(istream& stream, int* number)
{
    if(stream.eof())
        return false;

    char buffer[32];
    buffer[0] = '\0';

    char ch;
    stream >> ch;
    int i = 0;
    while(!stream.eof() && ch != ',')
    {
        buffer[i++] = ch;
        stream >> ch;
    }

    bool isNumeric = false;
    if(i > 0 && buffer[0] != '$')
    {
        *number = atoi(buffer);
        isNumeric = true;
    }

    return isNumeric;
}

面试题 38 :字符串的排列

题目:输入一个字符串,打印出该字符串中字符的所有排列。例如,输入字符串 abc ,则打印出 a,b,c 所能排列出来的所有字符串 abc,acb,bac,bca,cab,cba 。


void Permutation(char *pStr)
{
    if (pStr == nullptr) {
        return;
    }

    Permutation(pStr, pStr);
}

void Permutation(char *pStr, char *pBegin) {
    if (*pBegin == '\0') {
        printf("%s\n", pStr);
        return;
    }

    for (char *pCh = pBegin; *pCh != '\0'; ++pCh) {
        swap(*pCh, *pBegin);

        Permutation(pStr, pBegin + 1);

        swap(*pCh, *pBegin);
    }
}

面试题 39 :数组中出现次数超过一半的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如,输入一个长度为 9 的数组 {1,2,3,2,2,2,5,4,2} 。由于数字 2 在数组中出现了 5 次,超过数组长度的一半,因此输出 2 。


/*
方法:数组特点
特点:无需修改输入数组
时间复杂度:O(n)
*/

int MoreThanHalfNum(vector<int> numbers) {
    int res = numbers[0];
    int count = 1;

    for (int i = 0; i < numbers.size(); ++i) {
        if (count == 0) {
            res = numbers[i];
            count = 1;
        } else if (numbers[i] == res) {
            ++count;
        } else {
            --count;
        }
    }

    count = 0;

    for (int i = 0; i < numbers.size(); ++i) {
        if (numbers[i] == res) {
            ++count;
        }
    }

    if (count * 2 <= numbers.size()) {
        res = 0;
    }

    return res;
}

/*
方法:快速排序变异
特点:需修改输入数组
时间复杂度:O(n)
*/

int MoreThanHalfNum(int *numbers; int length)
{
    int middle = length >> 1;
    int start = 0;
    int end = length - 1;
    int idx = Partition(numbers, length, start, end);

    while (idx != middle) {
        if (idx > middle) {
            end = idx - 1;
            idx = Partition(numbers, length, start, end);
        } else {
            start = idx + 1;
            idx = Partition(numbers, length, start, end);
        }
    }

    int res = numbers[middle];              
    int count = 0;

    for (int i = 0; i < numbers.size(); ++i) {
        if (numbers[i] == res) {
            ++count;
        }
    }

    if (count * 2 <= numbers.size()) {
        res = 0;
    }

    return res;
}

int Partiton(int data[], int length, int start, int end) {
    if (data == nullptr || length <= 0 || start < 0 || end >= length) {
        return -1;
    }

    int idx = start;    // 以 data[idx] 将数据分区
    swap(data[idx], data[end]);

    int small = start - 1;

    for (idx = start; idx < end; ++idx) {
        if (data[idx] < data[end]) {
            ++small;

            if (small != idx) {
                swap(data[idx], data[small]);
            }
        }
    }

    ++small;
    swap(data[small], data[end]);

    return small;
}

面试题 40 :最小的 k 个数

题目:输入 n 个整数,找出其中最小的 k 个数。例如,输入 4,5,1,6,2,7,3,8 这 8 个数字,则最小的 4 个数字是 1,2,3,4 。


/*
方法:快速排序分区函数
特点:需修改原数组
时间复杂度:O(n)
*/

vector<int> GetLeastNumbers(vector<int> numbers, int k)
{
    int len = numbers.size();
    vector<int> res;

    if (len <= 0 || k <= 0 || k > len) {
        return res;
    }

    int start = 0;
    int end = len - 1;
    int idx = Partition(numbers, start, end);

    while (idx != k - 1) {
        if (idx > k - 1) {
            end = idx - 1;
            idx = Partition(numbers, start, end);
        } else {
            start = idx + 1;
            idx = Partition(numbers, start, end);
        }
    }

    for (int i = 0; i < k; ++i) {
        res.push_back(numbers[i]);
    }

    return res;
}

int Partition(vector<int>& numbers, int start, int end) {
    int len = numbers.size();

    if (len <= 0 || start < 0 || end >= len) {
        return -1;
    }

    int idx = start;
    swap(numbers[idx], numbers[end]);

    int small = start - 1;

    for (idx = start; idx < end; ++idx) {
        if (numbers[idx] < numbers[end]) {
            ++small;

            if (idx != small) {
                swap(numbers[idx], numbers[small]);
            }
        }
    }

    ++small;
    swap(numbers[small], numbers[end]);

    return small;
}

/*
方法:使用STL的multiset
特点:适合处理海量数据
时间复杂度:O(nlogk)
*/

typedef multiset<int, greater<int>> intSet;
typedef multiset<int, greater<int>>::iterator intSetIter;

vector<int> GetLeastNumbers(vector<int> numbers, int k)
{
    vector<int> res;
    intSet leastNumbers;
    leastNumbers.clear();

    if (k <= 0 || k > numbers.size()) {
        return res;
    }

    vector<int>::const_iterator iter = numbers.begin();

    for (; iter != numbers.end(); ++iter) {
        if (leastNumbers.size() < k) {
            leastNumbers.insert(*iter);
        } else {
            intSetIter iterGreater = leastNumbers.begin();

            if (*iter < *(leastNumbers.begin())) {
                leastNumbers.erase(iterGreater);
                leastNumbers.insert(*iter);
            }
        }
    }
}

面试题 41 :数据流中的中位数

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。


/*
方法:最大堆和最小堆
时间复杂度:插入 O(logn) ,取中位数 O(1)
*/

template<typename T> class DynamicArray
{
public:
    void Insert(T num)
    {
        if (((min.size() + max.size()) & 1) == 0) {    // 偶数
            if (max.size() > 0 && num < max[0]) {
                max.push_back(num);
                // 向堆中插入一个元素,并且使堆的规则依然成立(less-最大堆,greater-最小堆)
                push_heap(max.begin(), max.end(), less<T>());

                num = max[0];

                // 堆的基础上,弹出堆顶元素
                pop_heap(max.begin(), max.end(), less<T>());
                max.pop_back();
            }

            min.push_back(num);
            push_heap(min.begin(), min.end(), greater<T>());
        } else {    // 奇数
            if (min.size() > 0 && num > min[0]) {
                min.push_back(num);
                push_heap(min.begin(), min.end(), greater<T>());

                num = min[0];

                pop_heap(min.begin(), min.end(), greater<T>());
                min.pop_back();
            }

            max.push_back(num);
            push_heap(max.begin(), max.end(), less<T>());
        }
    }

    T GetMedian()
    {
        int size = min.size() + max.size();

        if (size <= 0) {
            return -1;
        }

        T median = 0;

        if ((size & 1) == 1) {
            median = min[0];
        } else {
            median = max[0] + ((min[0] - max[0]) >> 1);
        }

        return median;
    }

private:
    vector<T> min;    // 最小堆
    vector<T> max;    // 最大堆
}

面试题 42 :连续子数组的做大和

题目:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n) 。


/*
时间复杂度:O(n)
*/


int FindGreatestSumOfSubArray(vector<int> numbers)
{
    int len = numbers.size();

    if (len <= 0) {
        return -1;
    }

    int sum = 0;
    int maxSum = 0 - MAX_INT;

    for (int i = 0; i < len; ++i) {
        if (sum <= 0) {
            sum = numbers[i];
        } else {
            sum += numbers[i];
        }

        maxSum = max(sum, maxSum);
    }

    return maxSum;
}

面试题 43 :1~n 整数中 1 出现的次数

题目:输入一个整数 n ,求 1~n 这 n 个整数的十进制表示中 1 出现的次数。例如,输入 12 ,1~12 这些整数中包含 1 的数字有 1、10、11、12 ,1 一共出现了 5 次。


/*
方法:递归处理最高位
*/

int NumbberOf1Between1AndN(int n)
{
    if (n <= 0) {
        return 0;
    }

    char str[50];
    sprintf(str, "%d", n);

    return NumberOf1(str);
}

int NumberOf1(const char *str) {
    if (str == nullptr || *str == '\0' || *str < '0' || *str > '9') {
        return 0;
    }

    int first = *srt - '0';
    unsigned int length = strlen(str);

    if (length == 1 && first == 0) {
        return 0;
    }
    if (length == 1 && first > 0) {
        return 1;
    }

    // 最高位带来的 1
    int numFirstDigit = 0;

    if (first > 1) {
        numFirstDigit = PowerBase10(length - 1);
    } else if (first == 1) {
        numFirstDigit = atoi(str + 1) + 1;
    }

    // 除最高位外其余位带来的 1(定一位,全排列其余位)
    int numOtherDigits = first * (length - 1) * PowerBase10(length - 2);
    // 去除最高位数字后的数字带来的 1
    int numRecursive = NumberOf1(str + 1);

    return numFirstDigit + numOtherDigits + numRecursive;
}

int PowerBase10(unsigned int n) {
    int res = 1;

    for (unsigned int i = 0; i < n; ++i) {
        res *= 10;
    }

    return res;
}

面试题 44 :数字序列中某一位的数字

题目:数字以 0123456789101112131415… 的格式序列化到一个字符序列中。在这个序列中,第 5 位(从 0 开始)是 5 ,第 13 位是 1 ,第 19 位是 4 ,等等。请写一个函数,求任意第 n 位对应的数字。


int digitAtIndex(int index)
{
    if (index < 0) {
        return -1;
    }

    int digits = 1;

    while (true) {
        // digits 位数的数量
        int numbers = countOfIntegers(digits);

        if (index < numbers * digits) {
            return digitAtIndex(index, digits);
        }

        index -= digits * numbers;
        ++digits;
    }

    return -1;
}

int countOfIntegers(int digits) {
    if (digits == 1) {
        return 10;
    }

    int count = pow(10, digits - 1);

    return count * 9;
}

int digitAtIndex(int index, int digits) {
    int number = pow(10, digits - 1);    // digits位数的第一个数
    number += index / digits;
    int indexOfBit = index % digits;

    for (int i = 1; i < digits - indexOfBit; ++i) {
        number /= 10;
    }

    return number % 10;
}

面试题 45 :把数组排成最小的数

题目:输入一个正整数数组,把数组里的数组拼接起来排成一个数,打印出拼成的最小的数字。例如,输入数组 {3,32,321} ,则打印出这三个数字拍成的最小数字 321323 。


static bool cmp(int a, int b) {
    string sa = to_string(a);
    string sb = to_string(b);
    return sa + sb <= sb + sa;
}

void PrintMinNumber(vector<int> numbers) {
    sort(numbers.begin(), numbers.end(), cmp);
    string res = "";

    for (int i = 0; i < numbers.size(); ++i) {
        res += to_string(numbers[i]);
    }
        
    cout << res << endl;
}

面试题 46 :把数字翻译成字符串

题目:给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 ‘a’ ,1 翻译成 ‘b’ , … ,11 翻译成 ‘l’ , … ,25 翻译成 ‘z’ 。一个数字可能有多个翻译。例如,12258 有 5 种不同的翻译,分别是 “bccfi”、“bwfi”、“bczi”、“mcfi” 和 “mzi” 。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。


int GetTransCount(int number)
{
    if (number < 0) {
        return 0;
    }

    string numStr = to_string(number);
    return GetTransCount(numStr);
}

int GetTransCount(const string& numStr) {
    int length = numStr.size();
    vector<int> counts(length);
    counts[length - 1] = 1;

    for (int i = length - 2; i >= 0; --i) {
        int count = counts[i + 1];

        int digit1 = numStr[i] - '0';
        int digit2 = numStr[i + 1] - '0';
        int converted = digit1 * 10 + digit2;

        if (converted >= 10 && converted <= 25) {
            if (i < length - 2) {
                count += counts[i + 2];
            } else {
                count += 1;
            }
        }

        counts[i] = count;
    }

    count = counts[0];

    return count;
}

面试题 47 :礼物的最大价值

题目:在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格,直到到达棋盘的右下角。给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?


int GetMaxValue(const vector<vector<int>> values)
{
    int rows = values.size();
    int cols = values[0].size();

    if (rows == 0 || cols == 0) {
        return 0;
    }

    vector<int> maxValues(cols);

    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            int left = 0;
            int up = 0;

            if (i > 0) {
                up = maxValue[j];
            }
            if (j > 0) {
                left = maxValue[j - 1];
            }

            maxValue[j] = max(left, up) + values[i][j];
        }
    }

    return maxValue[cols - 1];
}

面试题 48 :最长不含重复字符的子字符串

题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含 ‘a’~‘z’ 的字符。例如,在字符串 “arabcacfr” 中,最长的不包含重复字符的子字符串是 “acfr” ,长度为 4 。


int longestSubstringWithoutDuplication(const string& str)
{
    int curLen = 0;
    int maxLen = 0;

    vector<int> position(26, -1);

    for (int i = 0; i < str.size(); ++i) {
        int preIdx = position[str[i] - 'a'];

        if (preIdx < 0 || i - preIdx > curLen) {
            ++curLen;
        } else {
            if (curLen > maxLen) {
                maxLen = curLen;
            }

            curLen = i - preIdx;
        }

        position[str[i] - 'a'] = i;
    }

    if (curLen > maxLen) {
        maxLen = curLen;
    }

    return maxLen;
}

面试题 49 :丑数

题目:我们把只包含质因子 2、3、5 的数称作丑数。求按从小到大的顺序的第 n 个丑数。例如,6、8 是丑数,但 14 不是,因为它包含因子 7 。习惯上我们把 1 当作第一个丑数。


int GetUglyNumber(int index) {
    if (index <= 0) {
        return 0;
    } else if (index <= 6) {
        return index;
    }
        
    vector<int> res(index);
    res[0] = 1;
    int t2 = 0, t3 = 0, t5 = 0;
        
    for (int i = 1; i < index; ++i) {
        res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
            
        if (res[i] == res[t2] * 2) {
            ++t2;
        }
        if (res[i] == res[t3] * 3) {
            ++t3;
        }
        if (res[i] == res[t5] * 5) {
            ++t5;
        }
    }
        
    return res[index - 1];
}

面试题 50 :第一个只出现一次的字符

题目一:字符串中第一个只出现一次的字符
在字符串中找出第一个只出现第一次的字符。如输入 “abaccdeff” ,则输出 ‘b’ 。


/*
方法:哈希表
时间复杂度:O(1)
空间复杂度:O(1)
*/

char FirstNotRepeatingChar(string str)
{
    int len = str.size();
    char res = '\0';

    if (len <= 0) {
        return res;
    }

    vector<unsigned int> hashTable(256, 0);
    int i = 0;

    while (i < len) {
        ++hashTable[str[i++]];
    }

    i = 0;

    while (i < len) {
        if (hashTable[str[i]] == 1) {
            res = str[i];
            break;
        }
    }

    return res;
}

题目二:字符流中第一个只出现一次的字符
请实现一个函数,用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 “go” 时,第一个只出现一次的字符是 ‘g’ ;当从字符流中读出前 6 个字符 “huawei” 时,第一个只出现一次的字符是 ‘h’ 。


class CharStatistics
{
public:
    CharStatistics() : index(0) {
        for (int i = 0; i < 256; ++i) {
            occurrence[i] = -1;
        }
    }

    void Insert(char ch) {
        if (occurrence[ch] == -1) {
            occurrence[ch] = index;
        } else if (occurrence[ch] >= 0) {
            occurrence[ch] = -2
        }

        ++index;
    }

    char FirstAppearenceOnce() {
        char ch = '\0';
        int minIndex = INT_MAX;

        for (int i = 0; i < 256; ++i) {
            if (occurrence[i] >= 0 && occurrence[i] < minIndex) {
                ch = (char)i;
                minIndex = occurrence[i];
            }
        }

        return ch;
    }

private:
    int occurrence[256];
    int index;
}

面试题 51 :数组中的逆序对

题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,在数组 {7,5,6,4} 中,一共存在 5 个逆序对,分别是 (7,6)、(7,5)、(7,4)、(5,4)、(6,4) 。


/*
方法:归并排序,双指针
时间复杂度:O(nlogn)
空间复杂度:O(n)
*/

int InversePairs(int *data, int length)
{
    if (data == nullptr || length <= 0) {
        return 0;
    }

    int *copy = new int[length];

    for (int i = 0; i < length; ++i) {
        copy[i] = data[i];
    }

    int count = InversePairs(data, copy, o, length - 1);
    delete []copy;

    return count;
}

int InversePairs(int *data, int *copy, int start, int end) {
    if (start == end) {
        copy[start] = data[start];
        return 0;
    }

    int length = start + ((end - start) >> 1);

    int left = InversePairs(data, copy, start, start + length);
    int right = InversePair(data, copy, start + length + 1, end);

    int i = start + length;    // 前半段最后一个数字的下标
    int j = end;               // 后半段最后一个数字的下标
    int indexCopy = end;
    int count = 0;

    while (i >= start && j >= start + length + 1) {
        if (data[i] > copy[j]) {
            copy[indexCopy--] = data[i--];
            count += j - start - length;
        } else {
            copy[indexCopy--] = data[j--];
        }
    }

    while (i >= start) {
        copy[indexCopy--] = data[i--];
    }

    while (j >= start + length + 1) {
        copy[indexCopy--] = data[j--];
    }

    return left + right + count;
}

面试题 52 :两个链表的第一个公共节点

题目:输入两个链表,找出它们的第一个公共节点。


/*
方法:快慢指针
时间复杂度:O(m+n)
空间复杂度:O(1)
*/

ListNide* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2)
{
    unsigned int lenth1 = GetListLength(pHead1);
    unsigned int lenth2 = GetListLength(pHead2);

    if (length1 > length2) {
        for (int i = 0; i < length1 - length2; ++i) {
            pHead1 = pHead1->m_pNext;
        }
    } else {
        for (int i = 0; i < length2 - length1; ++i) {
            pHead2 = pHead2->m_pNext;
        }
    }

    while (pHead1 != nullptr && pHead2 != nullptr && pHead1 != pHead2) {
        pHead1 = pHead1->m_pNext;
        pHead2 = pHead2->m_pNext;
    }

    return pHead1;
}

unsigned int GetListLength(ListNode *pHead) {
    unsigned int length = 0;
    ListNode *pNode = pHead;

    while (pNode != nullptr) {
        ++length;
        pNode = pNode->m_pNext;
    }

    return length;
}

面试题 53 :在排序数组中查找数字

题目一:数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。例如,输入排序数组 {1,2,3,3,3,3,4,5} 和数字 3 ,由于 3 在这个数组中出现了 4 次,因此输出 4 。


int GetNumberOfK(int *data, int length, int k)
{
    int count = 0;

    if (data != nullptr && length > 0) {
        int first = GetFirstK(data, length, k, 0, length - 1);
        int last = GetLastK(data, length, k, 0, length - 1);

        if (first > -1 && last > -1) {
            count = lasr - first + 1;
        }
    }

    return count;
}

int GetFirstK(int *data, int length, int k, int start, int end) {
    if (start > end) {
        return -1;
    }

    int middleIndex = start + ((end - start) >> 1);
    int middleData = data[middleIndex];

    if (middleData == k) {
        if ((middleIndex > 0 && data[middleIndex - 1] != k) || middleIndex == 0) {
            return middleIndex;
        } else {
            end = middleIndex - 1;
        }
    } else if (middleData > k) {
        end = middleIndex - 1;
    } else {
        start = middleIndex + 1;
    }

    return GetFirstK(data, length, k, start, end);
}

int GetLastK(int *data, int length, int k, int start, int end) {
    if (start > end) {
        return -1;
    }

    int middleIndex = start + ((end - start) >> 1);
    int middleData = data[middleIndex];

    if (middleData == k) {
        if ((middleIndex < length - 1 && data[middleIndex + 1] != k) || middleIndex == length - 1) {
            return middleIndex;
        } else {
            end = middleIndex + 1;
        }
    } else if (middleData < k) {
        start = middleIndex + 1;
    } else {
        end = middleIndex - 1;
    }

    return GetLastK(data, length, k, start, end);
}

题目二:0~n-1 中缺失的数字
一个长度为 n-1 的递增排序数组中的所有数字都是唯一的,并且每个数字的都在范围 0~n-1 之内。在范围 0~n-1 内的 n 个数字中有且只有一个数字不在该数组中,请找出这个数字。


int GetMissingNumber(int *numbers, int length)
{
    if (numbers == nullptr || length <= 0) {
        return -1;
    }

    int left = 0;
    int right = length -1;

    while (left <= right) {
        int middle = left + ((right - left) >> 1);

        if (numbers[middle] != middle) {
            if (middle == 0 || numbers[middle - 1] == middle - 1) {
                return middle;
            }

            right = middle - 1;
        } else {
            left = middle + 1;
        }
    }

    if (left == length) {
        return length;
    }
}

题目三:数组中数值和下标相等的元素
假设一个单调递增的数组里的每个元素都是整数并且是唯一的。请编程实现一个函数,找出数组中任意一个数值等于其下标的元素。例如,在数组 {-3,-1,1,3,5} 中,数字 3 和它的下标相同。


int GetNumberSameAsIndex(const int *numbers, int length)
{
    if (numbers == nullptr || length <= 0) {
        return -1;
    }

    int left = 0;
    int right = length -1;

    while (left <= right) {
        int middle = left + ((right - left) >> 1);

        if (numbers[middle] == middle) {
            return middle;
        }
        if (numbers[middle] > middle) {
            right = middle - 1;
        } else {
            left = middle + 1;
        }
    }

    return -1;
}

面试题 54 :二叉搜索树的第 k 大节点

题目:给定一棵二叉搜索树,请找出其中第 k 大的节点。


/*
方法:中序遍历
*/

BinaryTreeNode *KthNode(BinaryTreeNode *pRoot, unsigned int k)
{
    if (pRoot == nullptr || k == 0) {
        return nullptr;
    }

    return KthNode(pRoot, k);
}

BinaryTreeNode *KthNodeCore(BinaryTreeNode *pRoot, unsigned int& k) {
    BinaryTreeNode *res = nullptr;

    if (pRoot->m_pLeft != nullptr) {
        res = KthNodecore(pRoot->m_pLeft, k);
    }

    if (res == nullptr) {
        if (k == 1) {
            res = pRoot;
        }

        --k;
    }

    if (res == nullptr && pRoot->m_pRight != nullptr) {
        res = KthNodecore(pRoot->m_pRight, k);
    }

    return res;
}

面试题 55 :二叉树的深度

题目一:二叉树的深度
输入一棵二叉树的根节点,求该数的深度。从根节点到叶节点依次经过的节点(含根节点、叶节点)形成树的一条路径,最长路径的长度为数的深度。


int TreeDepth(BinaryTreeNode *pRoot)
{
    if (pRoot == nullptr) {
        return 0;
    }

    return 1 + max(pRoot->m_pLeft, pRoot->m_pRight);
}

题目二:平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左、右子树的深度相差不超过 1 ,那么它就是一棵平衡二叉树。


/*
方法:后序遍历
*/

bool isBalanced(BinaryTreeNode *pRoot)
{
    int depth = 0;
    return isBalanced(pRoot, &depth);
}

bool isBalanced(BinaryTreeNode *pRoot, int *pDepth) {
    if (pRoot == nullptr) {
        *pDepth = 0;
        return true;
    }

    int left, right;

    if (isBalanced(pRoot->m_pLeft, &left) && isBalanced(pRoot->m_pRight, &right)) {
        if (abs(left - right) <= 1) {
            *pDepth = 1 + max(left, right);
            return true;
        }
    }

    return false;
}

面试题 56 :数组中数字出现的次数

题目一:数组中只出现一次的两个数字。
一个整型数组里除了两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是 O(n) ,空间复杂度是 O(1)。


/*
方法:位运算
时间复杂度:O(n)
空间复杂度:O(1)
*/

void FindNumsAppearOnce(int data[], int length, int *num1, int *num2)
{
    if (data == nullptr || length < 2) {
        return;
    }

    int resultXOR = 0;

    for (int i = 0; i < length; ++i) {
        resultXOR ^= data[i];
    }

    // 从低位起第一个为 1 的位
    unsigned int indexOf1 = FindFirstBit1(resultXOR);

    for (int i = 0; i < length; ++i) {
        if (isBit1(data[i], indexOf1)) {
            *num1 ^= data[i];
        } else {
            *num2 ^= data[i];
        }
    }
}

unsigned int FindFirstBit1(int num) {
    int indexBit = 0;

    while (((num & 1) == 0) && (indexBit < 8 * sizeof(int))) {
        num >>= 1;
        ++indexBit;
    }

    return indexBit;
}

bool isBit1(int num, unsigned int indexBit) {
    num >>= indexBit;
    return (num & 1);
}

题目二:数组中唯一只出现一次的数字
在一个数组中除了一个数字只出现一次外,其他数字都出现了三次。请找出那个只出现一次的数字。


/*
方法:位运算
时间复杂度:O(n)
空间复杂度:O(1)
*/

int FindNumberAppearOnce(int numbers[], int length)
{
    if (numbers == nullptr || length <= 0) {
        return -1;
    }

    int bitSum[32] = {0};

    for (int i = 0; i < length; ++i) {
        int bitMask = 1;

        for (int j = 31; j >= 0; --j) {
            int bit = numbers[i] & bitMask;

            if (bit != 0) {
                bitSum[j] += 1;
            }

            bitMask <<= 1;
        }
    }

    int res = 0;

    for (int i = 0; i < 32; ++i) {
        res <<= 1;
        res += bitSum[i] % 3;
    }

    return res;
}

面试题 57 :和为 s 的数字

题目一:和为 s 的两个数字
输入一个递增排序的数组和一个数字 s ,在数组中查找两个数字,使得它们的和正好是 s 。如果有多对数字的和等于 s ,则输出任意一对即可。


/*
方法:双指针
时间复杂度:O(n)
空间复杂度:O(1)
*/

bool FindNumbersWithSum(int data[], int length, int sum, int *num1; int *num2)
{
    bool found = false;

    if (length < 1 || num1 == nullptr || num2 == nullptr) {
        return found;
    }

    int head = 0; 
    int tail = length - 1;

    while (tail > head) {
        long long curSum = data[head] + data[tail];

        if (curSum == sum) {
            *num1 = data[head];
            *num2 = data[tail];
            found = true;
            break;
        } else if (curSum > sum) {
            --tail;
        } else {
            ++head;
        }
    }

    return found;
}

题目二:和为 s 的连续正数序列
输入一个正数 s ,打印出所有和为 s 的连续正数序列(至少含有两个数)。例如,如数 15 ,由于 1+2+3+4+5=4+5+6=7+8=15 ,所以打印出 3 个连续序列 1,2,3,4,5、4,5,6、7,8 。


void FindContinueSequence(int sum)
{
    if (sum < 3) {
        return;
    }

    int small = 1;
    int big = 2;
    int middle = sum >> 1;
    int curSum = small + big;

    while (small <= middle) {
        if (curSum == sum) {
            PrintContinueSequence(small, big);
        }

        while (curSum > sum && small <= middle) {
            curSum -= small;
            ++small;

            if (curSum == sum) {
                PrintContinueSequence(small, big);
            }
        }

        ++big;
        curSum += big;
    }
}

void PrintContinueSequence(int small, int big) {
    for (int i = small; i <= big; ++i) {
        printf("%d\t", i);
    }

    printf("\n");
}

面试题 58 :翻转字符串

题目一:翻转单词顺序
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为了简单起见,标点符号和普通字母一样处理。例如,输入字符串 “I am a student.” ,则输出 “student. a am I” 。


char* ReverseSentence(char *pData)
{
    if (pData == nullptr) {
        return nullptr;
    }

    char *pBegin = pData;
    char *pEnd = pData;

    while (*pEnd != '\0') {
        ++pEnd;
    }
    --pEnd;

    // 先将整个句子翻转
    Reverse(pBegin, pEnd);

    // 再翻转每个单词

    pBegin = pEnd = pData;

    while (*pBegin != '\0') {
        if (*pBegin == ' ') {
            ++pBegin;
            ++pEnd;
        } else if (*pEnd == ' ' || *pEnd == '\0') {
            Reverse(pBegin, --pEnd);
            pBegin = ++pEnd;
        } else {
            ++pEnd;
        }
    }

    return pData;
}

void Reverse(char *pBegin, char *pEnd) {
    if (pBegin == nullptr || pEnd == nullptr) {
        return;
    }

    while (pBegin < pEnd) {
        swap(*pBegin, *pEnd);
        ++pBegin;
        --pEnd;
    }
}

题目二:左旋转字符串
字符串的左旋操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串的左旋转操作功能。例如,输入字符串 “abcdefg” 和数字 2 ,该函数将返回左旋转两位得到的结果 “cdefgab” 。


char* LeftRotateString(char *pStr, int n)
{
    if (pStr != nullptr) {
        int length = strlen(pStr);

        if (length > 0 && n > 0 && n < length) {
            char *pFirstStart = pStr;
            char *pFirstEnd = pStr + n - 1;
            char *pSecondStart = pStr + n;
            char *pSecondEnd = pStr + length - 1;

            // 翻转前n个字符
            Reverse(pFirstStart, pFirstEnd);
            // 翻转剩余部分
            Reverse(pSecondStart, pSecondEnd);
            // 再次翻转整个字符串
            Reverse(pFirstStart, pSecondEnd);
        }
    }

    return pStr;
}

void Reverse(char *pBegin, char *pEnd) {
    if (pBegin == nullptr || pEnd == nullptr) {
        return;
    }

    while (pBegin < pEnd) {
        swap(*pBegin, *pEnd);
        ++pBegin;
        --pEnd;
    }
}

面试题 59 :队列的最大值

题目一:滑动窗口的最大值
给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。例如,如果输入数组 {2,3,4,2,6,2,5,1} 及滑动窗口的大小为 3 ,那么一共存在 6 个滑动窗口,它们的最大值分别为 {4,4,6,6,6,5} 。


vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
    vector<int> maxInWindows;

    if (size > 0 && size <= num.size()) {
        deque<int> index;

        for (unsigned int i = 0; i < size; ++i) {
            while (!index.empty() && num[i] >= num[index.back()]) {
                index.pop_back();
            }

            index.push(i);
        }

        for (unsigned int i = size; i < num.size(); ++i) {
            maxInWindows.push_back(num[index.front()]);

            while(!index.empty() && num[i] >= num[index.back()]) {
                index.pop_back();
            }

            if (!index.empty() && index.front() <= (int)(i - size)) {
                index.pop_front();
            }

            index.push_back(i);
        }

        maxInWindows.push_back(num[index.front()]);
    }

    return maxInWindows;
}

题目二:队列的最大值
请定义一个队列,并实现函数 max 得到队列里的最大值,要求函数 max()、push_back() 和 pop_front() 的时间复杂度都是 O(1) 。


template<typename T>
class QueueWithMax
{
public:
    QueueWithMax() : curIdx(0) {}
    
    void push_back(T number) {
        while (!max.empty() && number >= max.back().number) {
            max.pop_back();
        }

        InternalData internalData = {number, curIdx};
        data.push_back(internalData);
        max.push_back(internalData);

        ++curIdx;
    }

    void pop_front() {
        if (max.empty()) {
            throw new exception("queue is empty.");
        }

        if (max.front().index == data.front().index) {
            max.pop_front();
        }

        data.pop_front();
    }

    T max() const {
        if (max.empty()) {
            throw new exception("queue is empty.");
        }

        return max.front().number;
    }

private:
    struct InternalData {
        T number;
        int index;
    };

    deque<InternalData> data;
    deque<InternalData> max;
    int curIdx;
};

面试题 60 :n 个骰子的点数

题目:把 n 个骰子仍在地上,所有骰子朝上一面的点数之和为 s 。输入 n ,打印出 s 的所有可能的值出现的概率。


/*
方法:递归
*/

void PrintProbability(int n)
{
    if (n <= 0) {
        return;
    }

    int maxSum = n * 6;
    // 存放所有可能的结果的概率
    int *pProbabilities = new int[maxSum - n + 1];

    for (int i = n; i <= maxSum; ++i) {
        pProbabilities[i - n] = 0;
    }

    Probability(n, pProbabilities);

    // 基本事件的数量
    int total = pow(6.0, n);

    for (int i = n; i <= maxSum; ++i) {
        double ratio = (double)pProbabilities[i - n] / total;
        printf("%d\t: %e\n", i, ratio);
    }

    delete []pProbabilities; 
}

void Probability(int n, int *Probabilities) {
    // 6:6个骰子
    for (int i = 1; i <= 6; ++i) {
        Probability(n, n, i, Probabilities);
    }
}

void Probability(int original, int current, int sum, int *pProbabilities) {
    if (current == 1) {
        ++pProbabilities[sum - original];
    } else {
        // 6:6种点数
        for (int i = 1; i <= 6; ++i) {
            Probability(original, current - 1, i + sum, pProbabilities);
        }
    }
}

/*
方法:循环
*/

void PrintProbability(int n)
{
    if (n <= 0) {
        return;
    }

    int *pProbabilities[2];
    pProbabilities[0] = new int[n * 6 + 1];
    pProbabilities[1] = new int[n * 6 + 1];

    for (int i = 0; i < n * 6 + 1; ++i) {
        pProbabilities[0][i] = 0;
        pProbabilities[1][i] = 0;
    }

    // 用作交换目的
    int flag = 0;

    for (int i = 1; i <= 6; ++i) {
        pProbabilities[flag][i] = 1;
    }

    for (int k = 2; k <= n; ++k) {
        // k个骰子点数之和不小于k
        for (int i = 0; i < k; ++i) {
            pProbabilities[1 - flag][i] = 0;
        }

        for (int i = k; i <= k * 6; ++i) {
            pProbabilities[1 - flag][i] = 0;

            for (int j = 1; j <= i && j <= 6; ++j) {
                pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];
            }
        }

        flag = 1 - flag;
    }

    double total = pow(6.0, n);

    for (int i = n; i <= n * 6; ++i) {
        double ratio = pProbabilities[flag][i] / total;
        printf("%d\t: %e\n", i, ratio);
    }

    delete []pProbabilities[0];
    delete []pProbabilities[1];
}

面试题 61 :扑克牌中的顺子

题目:从扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这五张牌是不是连续的。2~10 为数字本身,A 为 1,J 为 11 ,Q 为 12 ,K 为 13 ,而大、小王可以看成任意数字。


bool isContinuous(vector<int> numbers)
{
    int len = numbers.size();

    if (len <= 0) {
        return false;
    }

    // 排序
    sort(numbers.begin(), numbers.end());

    int numberOfZero = 0;
    int numberOfGap = 0;

    // 求数组中0的个数
    numberOfZero = numbers.count(0);

    int small = numberOfZero;
    int big = small + 1;

    while (big < length) {
        // 有对子则不可能为顺子
        if (numbers[small] == numbers[big]) {
            return false;
        }

        // 求数组中间隙个数
        numberOfGap += numbers[big] - numbers[small] - 1;
        ++small;
        ++big;
    }

    return numberOfGap < numberOfZero;
}

面试题 62 :圆圈中最后剩下的数字

题目:0,1,2,…,n-1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字。求出这个圆圈里剩下的最后一个数字。


/*
方法:循环链表
*/

int LastRemaining(unsigned int n, unsigned int m)
{
    if (n < 1 || m < 1) {
        return -1;
    }

    list<int> numbers;

    for (int i = 0; i < n; ++i) {
        numbers.push_back(i);
    }

    list<int>::iterator current = numbers.begin();

    while (numbers.size() > 1) {
        for (int i = 0; i < m - 1; ++i) {
            ++current;
            
            // 手动将单链表首尾连接,形成循环链表
            if (current == numbers.end()) {
                current = numbers.begin();
            }
        }

        list<int>::iterator next = current + 1;

        if (next == numbers.end()) {
            next = numbers.begin();
        }

        numbers.erase(current);
        current = next;
    }

    return *current;
}

/*
方法:数学逻辑
*/

int LastRemaining(unsigned int n, unsigned int m)
{
    if (n < 1 || m < 1) {
        return -1;
    }

    int last = 0;

    for (int i = 2; i <= n; ++i) {
        last = (last + m) % i;
    }

    return last; 
}

面试题 63 :股票的最大利润

题目:假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?例如,一只股票在某些时间节点的价格为 {9,11,8,5,7,12,16,14} 。如果我们能在价格为 5 的时候买入并在价格为 16 时卖出,则能收获最大利润 11 。


int MaxProfit(vector<int> numbers)
{
    int len = numbers.size();
    
    if (len <= 1) {
        return 0;
    }

    int min = numbers[0];
    int maxProfit = numbers[1] - min;

    for (int i = 2; i < len; ++i) {
        if (numbers[i - 1] < min) {
            min = numbers[i - 1];
        }
        if (numbers[i] - min > maxProfit) {
            maxProfit = numbers[i] - min;
        }
    }

    return maxProfit;
}

面试题 64 :求 1+2+…+n

题目:求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。


/*
方法:构造函数
*/

unsigned int Sum_Solution(unsigned int n) 
{
    Tmp::Reset();

    Tmp *a = new Tmp[n];
    delete []a;
    a = nullptr;

    return Tmp::GetSum();
}

class Tmp {
public:
    Tmp() {
        ++N;
        Sum += N;
    }

    static void Reset() {
        N = 0;
        Sum = 0;
    }

    static unsigned int GetSum() {
        return Sum;
    }

private:
    static unsigned int N;
    static unsigned inr Sum;
}

/*
方法:虚函数
*/

int Sum_Solution(int n)
{
    A a;
    B b;
    Array[0] = &a;
    Array[1] = &b;

    return Array[1]->Sum(n);
}

class A;
A *Array[2];

class A {
public:
    virtual unsigned int Sum(unsigned int n) {
        return 0;
    }
};

class B : public A {
public:
    virtual unsigned int Sum(unsigned int n) {
        return Array[!!n]->Sum(n - 1) + n;
    }
};

/*
方法:函数指针
*/

unsigned int Sun_Solution(unsigned int n)
{
    static fun f[2] = {Solution_Teminator, Sum_Solution};
    return n + f[!!n](n-1);
}

typedef unsigned int (*fun)(unsigned int);

unsigned int Solution_Teminator(unsigned int n) {
    return 0;
}

/*
方法:模板类
*/

template<unsigned int n>
struct Sun_Solution {
    enum Value {N = Sum_Solution<n - 1>::N + n};
};

template<>
struct Sum_Solution<1> {
    enum Value{N = 1};
};

面试题 46 :把数字翻译成字符串

题目:给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 ‘a’ ,1 翻译成 ‘b’ , … ,11 翻译成 ‘l’ , … ,25 翻译成 ‘z’ 。一个数字可能有多个翻译。例如,12258 有 5 种不同的翻译,分别是 “bccfi”、“bwfi”、“bczi”、“mcfi” 和 “mzi” 。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。


int GetTransCount(int number)
{
    if (number < 0) {
        return 0;
    }

    string numStr = to_string(number);
    return GetTransCount(numStr);
}

int GetTransCount(const string& numStr) {
    int length = numStr.size();
    vector<int> counts(length);
    counts[length - 1] = 1;

    for (int i = length - 2; i >= 0; --i) {
        int count = counts[i + 1];

        int digit1 = numStr[i] - '0';
        int digit2 = numStr[i + 1] - '0';
        int converted = digit1 * 10 + digit2;

        if (converted >= 10 && converted <= 25) {
            if (i < length - 2) {
                count += counts[i + 2];
            } else {
                count += 1;
            }
        }

        counts[i] = count;
    }

    count = counts[0];

    return count;
}

面试题 47 :礼物的最大价值

题目:在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格,直到到达棋盘的右下角。给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?


int GetMaxValue(const vector<vector<int>> values)
{
    int rows = values.size();
    int cols = values[0].size();

    if (rows == 0 || cols == 0) {
        return 0;
    }

    vector<int> maxValues(cols);

    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            int left = 0;
            int up = 0;

            if (i > 0) {
                up = maxValue[j];
            }
            if (j > 0) {
                left = maxValue[j - 1];
            }

            maxValue[j] = max(left, up) + values[i][j];
        }
    }

    return maxValue[cols - 1];
}

面试题 48 :最长不含重复字符的子字符串

题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含 ‘a’~‘z’ 的字符。例如,在字符串 “arabcacfr” 中,最长的不包含重复字符的子字符串是 “acfr” ,长度为 4 。


int longestSubstringWithoutDuplication(const string& str)
{
    int curLen = 0;
    int maxLen = 0;

    vector<int> position(26, -1);

    for (int i = 0; i < str.size(); ++i) {
        int preIdx = position[str[i] - 'a'];

        if (preIdx < 0 || i - preIdx > curLen) {
            ++curLen;
        } else {
            if (curLen > maxLen) {
                maxLen = curLen;
            }

            curLen = i - preIdx;
        }

        position[str[i] - 'a'] = i;
    }

    if (curLen > maxLen) {
        maxLen = curLen;
    }

    return maxLen;
}

面试题 49 :丑数

题目:我们把只包含质因子 2、3、5 的数称作丑数。求按从小到大的顺序的第 n 个丑数。例如,6、8 是丑数,但 14 不是,因为它包含因子 7 。习惯上我们把 1 当作第一个丑数。


int GetUglyNumber(int index) {
    if (index <= 0) {
        return 0;
    } else if (index <= 6) {
        return index;
    }
        
    vector<int> res(index);
    res[0] = 1;
    int t2 = 0, t3 = 0, t5 = 0;
        
    for (int i = 1; i < index; ++i) {
        res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
            
        if (res[i] == res[t2] * 2) {
            ++t2;
        }
        if (res[i] == res[t3] * 3) {
            ++t3;
        }
        if (res[i] == res[t5] * 5) {
            ++t5;
        }
    }
        
    return res[index - 1];
}

面试题 50 :第一个只出现一次的字符

题目一:字符串中第一个只出现一次的字符
在字符串中找出第一个只出现第一次的字符。如输入 “abaccdeff” ,则输出 ‘b’ 。


/*
方法:哈希表
时间复杂度:O(1)
空间复杂度:O(1)
*/

char FirstNotRepeatingChar(string str)
{
    int len = str.size();
    char res = '\0';

    if (len <= 0) {
        return res;
    }

    vector<unsigned int> hashTable(256, 0);
    int i = 0;

    while (i < len) {
        ++hashTable[str[i++]];
    }

    i = 0;

    while (i < len) {
        if (hashTable[str[i]] == 1) {
            res = str[i];
            break;
        }
    }

    return res;
}

题目二:字符流中第一个只出现一次的字符
请实现一个函数,用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 “go” 时,第一个只出现一次的字符是 ‘g’ ;当从字符流中读出前 6 个字符 “huawei” 时,第一个只出现一次的字符是 ‘h’ 。


class CharStatistics
{
public:
    CharStatistics() : index(0) {
        for (int i = 0; i < 256; ++i) {
            occurrence[i] = -1;
        }
    }

    void Insert(char ch) {
        if (occurrence[ch] == -1) {
            occurrence[ch] = index;
        } else if (occurrence[ch] >= 0) {
            occurrence[ch] = -2
        }

        ++index;
    }

    char FirstAppearenceOnce() {
        char ch = '\0';
        int minIndex = INT_MAX;

        for (int i = 0; i < 256; ++i) {
            if (occurrence[i] >= 0 && occurrence[i] < minIndex) {
                ch = (char)i;
                minIndex = occurrence[i];
            }
        }

        return ch;
    }

private:
    int occurrence[256];
    int index;
}

面试题 51 :数组中的逆序对

题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,在数组 {7,5,6,4} 中,一共存在 5 个逆序对,分别是 (7,6)、(7,5)、(7,4)、(5,4)、(6,4) 。


/*
方法:归并排序,双指针
时间复杂度:O(nlogn)
空间复杂度:O(n)
*/

int InversePairs(int *data, int length)
{
    if (data == nullptr || length <= 0) {
        return 0;
    }

    int *copy = new int[length];

    for (int i = 0; i < length; ++i) {
        copy[i] = data[i];
    }

    int count = InversePairs(data, copy, o, length - 1);
    delete []copy;

    return count;
}

int InversePairs(int *data, int *copy, int start, int end) {
    if (start == end) {
        copy[start] = data[start];
        return 0;
    }

    int length = start + ((end - start) >> 1);

    int left = InversePairs(data, copy, start, start + length);
    int right = InversePair(data, copy, start + length + 1, end);

    int i = start + length;    // 前半段最后一个数字的下标
    int j = end;               // 后半段最后一个数字的下标
    int indexCopy = end;
    int count = 0;

    while (i >= start && j >= start + length + 1) {
        if (data[i] > copy[j]) {
            copy[indexCopy--] = data[i--];
            count += j - start - length;
        } else {
            copy[indexCopy--] = data[j--];
        }
    }

    while (i >= start) {
        copy[indexCopy--] = data[i--];
    }

    while (j >= start + length + 1) {
        copy[indexCopy--] = data[j--];
    }

    return left + right + count;
}

面试题 52 :两个链表的第一个公共节点

题目:输入两个链表,找出它们的第一个公共节点。


/*
方法:快慢指针
时间复杂度:O(m+n)
空间复杂度:O(1)
*/

ListNide* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2)
{
    unsigned int lenth1 = GetListLength(pHead1);
    unsigned int lenth2 = GetListLength(pHead2);

    if (length1 > length2) {
        for (int i = 0; i < length1 - length2; ++i) {
            pHead1 = pHead1->m_pNext;
        }
    } else {
        for (int i = 0; i < length2 - length1; ++i) {
            pHead2 = pHead2->m_pNext;
        }
    }

    while (pHead1 != nullptr && pHead2 != nullptr && pHead1 != pHead2) {
        pHead1 = pHead1->m_pNext;
        pHead2 = pHead2->m_pNext;
    }

    return pHead1;
}

unsigned int GetListLength(ListNode *pHead) {
    unsigned int length = 0;
    ListNode *pNode = pHead;

    while (pNode != nullptr) {
        ++length;
        pNode = pNode->m_pNext;
    }

    return length;
}

面试题 53 :在排序数组中查找数字

题目一:数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。例如,输入排序数组 {1,2,3,3,3,3,4,5} 和数字 3 ,由于 3 在这个数组中出现了 4 次,因此输出 4 。


int GetNumberOfK(int *data, int length, int k)
{
    int count = 0;

    if (data != nullptr && length > 0) {
        int first = GetFirstK(data, length, k, 0, length - 1);
        int last = GetLastK(data, length, k, 0, length - 1);

        if (first > -1 && last > -1) {
            count = lasr - first + 1;
        }
    }

    return count;
}

int GetFirstK(int *data, int length, int k, int start, int end) {
    if (start > end) {
        return -1;
    }

    int middleIndex = start + ((end - start) >> 1);
    int middleData = data[middleIndex];

    if (middleData == k) {
        if ((middleIndex > 0 && data[middleIndex - 1] != k) || middleIndex == 0) {
            return middleIndex;
        } else {
            end = middleIndex - 1;
        }
    } else if (middleData > k) {
        end = middleIndex - 1;
    } else {
        start = middleIndex + 1;
    }

    return GetFirstK(data, length, k, start, end);
}

int GetLastK(int *data, int length, int k, int start, int end) {
    if (start > end) {
        return -1;
    }

    int middleIndex = start + ((end - start) >> 1);
    int middleData = data[middleIndex];

    if (middleData == k) {
        if ((middleIndex < length - 1 && data[middleIndex + 1] != k) || middleIndex == length - 1) {
            return middleIndex;
        } else {
            end = middleIndex + 1;
        }
    } else if (middleData < k) {
        start = middleIndex + 1;
    } else {
        end = middleIndex - 1;
    }

    return GetLastK(data, length, k, start, end);
}

题目二:0~n-1 中缺失的数字
一个长度为 n-1 的递增排序数组中的所有数字都是唯一的,并且每个数字的都在范围 0~n-1 之内。在范围 0~n-1 内的 n 个数字中有且只有一个数字不在该数组中,请找出这个数字。


int GetMissingNumber(int *numbers, int length)
{
    if (numbers == nullptr || length <= 0) {
        return -1;
    }

    int left = 0;
    int right = length -1;

    while (left <= right) {
        int middle = left + ((right - left) >> 1);

        if (numbers[middle] != middle) {
            if (middle == 0 || numbers[middle - 1] == middle - 1) {
                return middle;
            }

            right = middle - 1;
        } else {
            left = middle + 1;
        }
    }

    if (left == length) {
        return length;
    }
}

题目三:数组中数值和下标相等的元素
假设一个单调递增的数组里的每个元素都是整数并且是唯一的。请编程实现一个函数,找出数组中任意一个数值等于其下标的元素。例如,在数组 {-3,-1,1,3,5} 中,数字 3 和它的下标相同。


int GetNumberSameAsIndex(const int *numbers, int length)
{
    if (numbers == nullptr || length <= 0) {
        return -1;
    }

    int left = 0;
    int right = length -1;

    while (left <= right) {
        int middle = left + ((right - left) >> 1);

        if (numbers[middle] == middle) {
            return middle;
        }
        if (numbers[middle] > middle) {
            right = middle - 1;
        } else {
            left = middle + 1;
        }
    }

    return -1;
}

面试题 54 :二叉搜索树的第 k 大节点

题目:给定一棵二叉搜索树,请找出其中第 k 大的节点。


/*
方法:中序遍历
*/

BinaryTreeNode *KthNode(BinaryTreeNode *pRoot, unsigned int k)
{
    if (pRoot == nullptr || k == 0) {
        return nullptr;
    }

    return KthNode(pRoot, k);
}

BinaryTreeNode *KthNodeCore(BinaryTreeNode *pRoot, unsigned int& k) {
    BinaryTreeNode *res = nullptr;

    if (pRoot->m_pLeft != nullptr) {
        res = KthNodecore(pRoot->m_pLeft, k);
    }

    if (res == nullptr) {
        if (k == 1) {
            res = pRoot;
        }

        --k;
    }

    if (res == nullptr && pRoot->m_pRight != nullptr) {
        res = KthNodecore(pRoot->m_pRight, k);
    }

    return res;
}

面试题 55 :二叉树的深度

题目一:二叉树的深度
输入一棵二叉树的根节点,求该数的深度。从根节点到叶节点依次经过的节点(含根节点、叶节点)形成树的一条路径,最长路径的长度为数的深度。


int TreeDepth(BinaryTreeNode *pRoot)
{
    if (pRoot == nullptr) {
        return 0;
    }

    return 1 + max(pRoot->m_pLeft, pRoot->m_pRight);
}

题目二:平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左、右子树的深度相差不超过 1 ,那么它就是一棵平衡二叉树。


/*
方法:后序遍历
*/

bool isBalanced(BinaryTreeNode *pRoot)
{
    int depth = 0;
    return isBalanced(pRoot, &depth);
}

bool isBalanced(BinaryTreeNode *pRoot, int *pDepth) {
    if (pRoot == nullptr) {
        *pDepth = 0;
        return true;
    }

    int left, right;

    if (isBalanced(pRoot->m_pLeft, &left) && isBalanced(pRoot->m_pRight, &right)) {
        if (abs(left - right) <= 1) {
            *pDepth = 1 + max(left, right);
            return true;
        }
    }

    return false;
}

面试题 56 :数组中数字出现的次数

题目一:数组中只出现一次的两个数字。
一个整型数组里除了两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是 O(n) ,空间复杂度是 O(1)。


/*
方法:位运算
时间复杂度:O(n)
空间复杂度:O(1)
*/

void FindNumsAppearOnce(int data[], int length, int *num1, int *num2)
{
    if (data == nullptr || length < 2) {
        return;
    }

    int resultXOR = 0;

    for (int i = 0; i < length; ++i) {
        resultXOR ^= data[i];
    }

    // 从低位起第一个为 1 的位
    unsigned int indexOf1 = FindFirstBit1(resultXOR);

    for (int i = 0; i < length; ++i) {
        if (isBit1(data[i], indexOf1)) {
            *num1 ^= data[i];
        } else {
            *num2 ^= data[i];
        }
    }
}

unsigned int FindFirstBit1(int num) {
    int indexBit = 0;

    while (((num & 1) == 0) && (indexBit < 8 * sizeof(int))) {
        num >>= 1;
        ++indexBit;
    }

    return indexBit;
}

bool isBit1(int num, unsigned int indexBit) {
    num >>= indexBit;
    return (num & 1);
}

题目二:数组中唯一只出现一次的数字
在一个数组中除了一个数字只出现一次外,其他数字都出现了三次。请找出那个只出现一次的数字。


/*
方法:位运算
时间复杂度:O(n)
空间复杂度:O(1)
*/

int FindNumberAppearOnce(int numbers[], int length)
{
    if (numbers == nullptr || length <= 0) {
        return -1;
    }

    int bitSum[32] = {0};

    for (int i = 0; i < length; ++i) {
        int bitMask = 1;

        for (int j = 31; j >= 0; --j) {
            int bit = numbers[i] & bitMask;

            if (bit != 0) {
                bitSum[j] += 1;
            }

            bitMask <<= 1;
        }
    }

    int res = 0;

    for (int i = 0; i < 32; ++i) {
        res <<= 1;
        res += bitSum[i] % 3;
    }

    return res;
}

面试题 57 :和为 s 的数字

题目一:和为 s 的两个数字
输入一个递增排序的数组和一个数字 s ,在数组中查找两个数字,使得它们的和正好是 s 。如果有多对数字的和等于 s ,则输出任意一对即可。


/*
方法:双指针
时间复杂度:O(n)
空间复杂度:O(1)
*/

bool FindNumbersWithSum(int data[], int length, int sum, int *num1; int *num2)
{
    bool found = false;

    if (length < 1 || num1 == nullptr || num2 == nullptr) {
        return found;
    }

    int head = 0; 
    int tail = length - 1;

    while (tail > head) {
        long long curSum = data[head] + data[tail];

        if (curSum == sum) {
            *num1 = data[head];
            *num2 = data[tail];
            found = true;
            break;
        } else if (curSum > sum) {
            --tail;
        } else {
            ++head;
        }
    }

    return found;
}

题目二:和为 s 的连续正数序列
输入一个正数 s ,打印出所有和为 s 的连续正数序列(至少含有两个数)。例如,如数 15 ,由于 1+2+3+4+5=4+5+6=7+8=15 ,所以打印出 3 个连续序列 1,2,3,4,5、4,5,6、7,8 。


void FindContinueSequence(int sum)
{
    if (sum < 3) {
        return;
    }

    int small = 1;
    int big = 2;
    int middle = sum >> 1;
    int curSum = small + big;

    while (small <= middle) {
        if (curSum == sum) {
            PrintContinueSequence(small, big);
        }

        while (curSum > sum && small <= middle) {
            curSum -= small;
            ++small;

            if (curSum == sum) {
                PrintContinueSequence(small, big);
            }
        }

        ++big;
        curSum += big;
    }
}

void PrintContinueSequence(int small, int big) {
    for (int i = small; i <= big; ++i) {
        printf("%d\t", i);
    }

    printf("\n");
}

面试题 58 :翻转字符串

题目一:翻转单词顺序
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为了简单起见,标点符号和普通字母一样处理。例如,输入字符串 “I am a student.” ,则输出 “student. a am I” 。


char* ReverseSentence(char *pData)
{
    if (pData == nullptr) {
        return nullptr;
    }

    char *pBegin = pData;
    char *pEnd = pData;

    while (*pEnd != '\0') {
        ++pEnd;
    }
    --pEnd;

    // 先将整个句子翻转
    Reverse(pBegin, pEnd);

    // 再翻转每个单词

    pBegin = pEnd = pData;

    while (*pBegin != '\0') {
        if (*pBegin == ' ') {
            ++pBegin;
            ++pEnd;
        } else if (*pEnd == ' ' || *pEnd == '\0') {
            Reverse(pBegin, --pEnd);
            pBegin = ++pEnd;
        } else {
            ++pEnd;
        }
    }

    return pData;
}

void Reverse(char *pBegin, char *pEnd) {
    if (pBegin == nullptr || pEnd == nullptr) {
        return;
    }

    while (pBegin < pEnd) {
        swap(*pBegin, *pEnd);
        ++pBegin;
        --pEnd;
    }
}

题目二:左旋转字符串
字符串的左旋操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串的左旋转操作功能。例如,输入字符串 “abcdefg” 和数字 2 ,该函数将返回左旋转两位得到的结果 “cdefgab” 。


char* LeftRotateString(char *pStr, int n)
{
    if (pStr != nullptr) {
        int length = strlen(pStr);

        if (length > 0 && n > 0 && n < length) {
            char *pFirstStart = pStr;
            char *pFirstEnd = pStr + n - 1;
            char *pSecondStart = pStr + n;
            char *pSecondEnd = pStr + length - 1;

            // 翻转前n个字符
            Reverse(pFirstStart, pFirstEnd);
            // 翻转剩余部分
            Reverse(pSecondStart, pSecondEnd);
            // 再次翻转整个字符串
            Reverse(pFirstStart, pSecondEnd);
        }
    }

    return pStr;
}

void Reverse(char *pBegin, char *pEnd) {
    if (pBegin == nullptr || pEnd == nullptr) {
        return;
    }

    while (pBegin < pEnd) {
        swap(*pBegin, *pEnd);
        ++pBegin;
        --pEnd;
    }
}

面试题 59 :队列的最大值

题目一:滑动窗口的最大值
给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。例如,如果输入数组 {2,3,4,2,6,2,5,1} 及滑动窗口的大小为 3 ,那么一共存在 6 个滑动窗口,它们的最大值分别为 {4,4,6,6,6,5} 。


vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
    vector<int> maxInWindows;

    if (size > 0 && size <= num.size()) {
        deque<int> index;

        for (unsigned int i = 0; i < size; ++i) {
            while (!index.empty() && num[i] >= num[index.back()]) {
                index.pop_back();
            }

            index.push(i);
        }

        for (unsigned int i = size; i < num.size(); ++i) {
            maxInWindows.push_back(num[index.front()]);

            while(!index.empty() && num[i] >= num[index.back()]) {
                index.pop_back();
            }

            if (!index.empty() && index.front() <= (int)(i - size)) {
                index.pop_front();
            }

            index.push_back(i);
        }

        maxInWindows.push_back(num[index.front()]);
    }

    return maxInWindows;
}

题目二:队列的最大值
请定义一个队列,并实现函数 max 得到队列里的最大值,要求函数 max()、push_back() 和 pop_front() 的时间复杂度都是 O(1) 。


template<typename T>
class QueueWithMax
{
public:
    QueueWithMax() : curIdx(0) {}
    
    void push_back(T number) {
        while (!max.empty() && number >= max.back().number) {
            max.pop_back();
        }

        InternalData internalData = {number, curIdx};
        data.push_back(internalData);
        max.push_back(internalData);

        ++curIdx;
    }

    void pop_front() {
        if (max.empty()) {
            throw new exception("queue is empty.");
        }

        if (max.front().index == data.front().index) {
            max.pop_front();
        }

        data.pop_front();
    }

    T max() const {
        if (max.empty()) {
            throw new exception("queue is empty.");
        }

        return max.front().number;
    }

private:
    struct InternalData {
        T number;
        int index;
    };

    deque<InternalData> data;
    deque<InternalData> max;
    int curIdx;
};

面试题 60 :n 个骰子的点数

题目:把 n 个骰子仍在地上,所有骰子朝上一面的点数之和为 s 。输入 n ,打印出 s 的所有可能的值出现的概率。


/*
方法:递归
*/

void PrintProbability(int n)
{
    if (n <= 0) {
        return;
    }

    int maxSum = n * 6;
    // 存放所有可能的结果的概率
    int *pProbabilities = new int[maxSum - n + 1];

    for (int i = n; i <= maxSum; ++i) {
        pProbabilities[i - n] = 0;
    }

    Probability(n, pProbabilities);

    // 基本事件的数量
    int total = pow(6.0, n);

    for (int i = n; i <= maxSum; ++i) {
        double ratio = (double)pProbabilities[i - n] / total;
        printf("%d\t: %e\n", i, ratio);
    }

    delete []pProbabilities; 
}

void Probability(int n, int *Probabilities) {
    // 6:6个骰子
    for (int i = 1; i <= 6; ++i) {
        Probability(n, n, i, Probabilities);
    }
}

void Probability(int original, int current, int sum, int *pProbabilities) {
    if (current == 1) {
        ++pProbabilities[sum - original];
    } else {
        // 6:6种点数
        for (int i = 1; i <= 6; ++i) {
            Probability(original, current - 1, i + sum, pProbabilities);
        }
    }
}

/*
方法:循环
*/

void PrintProbability(int n)
{
    if (n <= 0) {
        return;
    }

    int *pProbabilities[2];
    pProbabilities[0] = new int[n * 6 + 1];
    pProbabilities[1] = new int[n * 6 + 1];

    for (int i = 0; i < n * 6 + 1; ++i) {
        pProbabilities[0][i] = 0;
        pProbabilities[1][i] = 0;
    }

    // 用作交换目的
    int flag = 0;

    for (int i = 1; i <= 6; ++i) {
        pProbabilities[flag][i] = 1;
    }

    for (int k = 2; k <= n; ++k) {
        // k个骰子点数之和不小于k
        for (int i = 0; i < k; ++i) {
            pProbabilities[1 - flag][i] = 0;
        }

        for (int i = k; i <= k * 6; ++i) {
            pProbabilities[1 - flag][i] = 0;

            for (int j = 1; j <= i && j <= 6; ++j) {
                pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];
            }
        }

        flag = 1 - flag;
    }

    double total = pow(6.0, n);

    for (int i = n; i <= n * 6; ++i) {
        double ratio = pProbabilities[flag][i] / total;
        printf("%d\t: %e\n", i, ratio);
    }

    delete []pProbabilities[0];
    delete []pProbabilities[1];
}

面试题 61 :扑克牌中的顺子

题目:从扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这五张牌是不是连续的。2~10 为数字本身,A 为 1,J 为 11 ,Q 为 12 ,K 为 13 ,而大、小王可以看成任意数字。


bool isContinuous(vector<int> numbers)
{
    int len = numbers.size();

    if (len <= 0) {
        return false;
    }

    // 排序
    sort(numbers.begin(), numbers.end());

    int numberOfZero = 0;
    int numberOfGap = 0;

    // 求数组中0的个数
    numberOfZero = numbers.count(0);

    int small = numberOfZero;
    int big = small + 1;

    while (big < length) {
        // 有对子则不可能为顺子
        if (numbers[small] == numbers[big]) {
            return false;
        }

        // 求数组中间隙个数
        numberOfGap += numbers[big] - numbers[small] - 1;
        ++small;
        ++big;
    }

    return numberOfGap < numberOfZero;
}

面试题 62 :圆圈中最后剩下的数字

题目:0,1,2,…,n-1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字。求出这个圆圈里剩下的最后一个数字。


/*
方法:循环链表
*/

int LastRemaining(unsigned int n, unsigned int m)
{
    if (n < 1 || m < 1) {
        return -1;
    }

    list<int> numbers;

    for (int i = 0; i < n; ++i) {
        numbers.push_back(i);
    }

    list<int>::iterator current = numbers.begin();

    while (numbers.size() > 1) {
        for (int i = 0; i < m - 1; ++i) {
            ++current;
            
            // 手动将单链表首尾连接,形成循环链表
            if (current == numbers.end()) {
                current = numbers.begin();
            }
        }

        list<int>::iterator next = current + 1;

        if (next == numbers.end()) {
            next = numbers.begin();
        }

        numbers.erase(current);
        current = next;
    }

    return *current;
}

/*
方法:数学逻辑
*/

int LastRemaining(unsigned int n, unsigned int m)
{
    if (n < 1 || m < 1) {
        return -1;
    }

    int last = 0;

    for (int i = 2; i <= n; ++i) {
        last = (last + m) % i;
    }

    return last; 
}

面试题 63 :股票的最大利润

题目:假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?例如,一只股票在某些时间节点的价格为 {9,11,8,5,7,12,16,14} 。如果我们能在价格为 5 的时候买入并在价格为 16 时卖出,则能收获最大利润 11 。


int MaxProfit(vector<int> numbers)
{
    int len = numbers.size();
    
    if (len <= 1) {
        return 0;
    }

    int min = numbers[0];
    int maxProfit = numbers[1] - min;

    for (int i = 2; i < len; ++i) {
        if (numbers[i - 1] < min) {
            min = numbers[i - 1];
        }
        if (numbers[i] - min > maxProfit) {
            maxProfit = numbers[i] - min;
        }
    }

    return maxProfit;
}

面试题 64 :求 1+2+…+n

题目:求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。


/*
方法:构造函数
*/

unsigned int Sum_Solution(unsigned int n) 
{
    Tmp::Reset();

    Tmp *a = new Tmp[n];
    delete []a;
    a = nullptr;

    return Tmp::GetSum();
}

class Tmp {
public:
    Tmp() {
        ++N;
        Sum += N;
    }

    static void Reset() {
        N = 0;
        Sum = 0;
    }

    static unsigned int GetSum() {
        return Sum;
    }

private:
    static unsigned int N;
    static unsigned inr Sum;
}

/*
方法:虚函数
*/

int Sum_Solution(int n)
{
    A a;
    B b;
    Array[0] = &a;
    Array[1] = &b;

    return Array[1]->Sum(n);
}

class A;
A *Array[2];

class A {
public:
    virtual unsigned int Sum(unsigned int n) {
        return 0;
    }
};

class B : public A {
public:
    virtual unsigned int Sum(unsigned int n) {
        return Array[!!n]->Sum(n - 1) + n;
    }
};

/*
方法:函数指针
*/

unsigned int Sun_Solution(unsigned int n)
{
    static fun f[2] = {Solution_Teminator, Sum_Solution};
    return n + f[!!n](n-1);
}

typedef unsigned int (*fun)(unsigned int);

unsigned int Solution_Teminator(unsigned int n) {
    return 0;
}

/*
方法:模板类
*/

template<unsigned int n>
struct Sun_Solution {
    enum Value {N = Sum_Solution<n - 1>::N + n};
};

template<>
struct Sum_Solution<1> {
    enum Value{N = 1};
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值