剑指offer——新增面试题

剑指offer——新增面试题

1. 数组

面试题51:数组中重复的数字(数组元素在0~n-1范围内)

bool duplicate(int numbers[],int length,int* duplication)
{
    if (numbers == NULL || length <= 0)
        return false;
    for (int i=0;i<length;++i)
        if (numbers[i] < 0 || numbers[i] > length-1)
            return false;
    for (int i=0;i<length;++i)
    {
        while (numbers[i] != i)
        {
            if (numbers[i] == numbers[numbers[i]])
            {
                *duplication == numbers[i];
                return true;
            }
            int temp = numbers[i];
            numbers[i] = numbers[temp];
            numbers[temp] = temp;
        }
    }
    return false;
}//时间复杂度O(n),空间复杂度O(1)

面试题52:构建乘积数组(数组A[0,1,…,n-1]—>数组B,B[i]=A的元素相乘除了A[i])

/*令C[i]=A[0]*A[1]...A[i-1],D[i]=A[i+1]*A[i+2]...A[n-1],B[i]=C[i]*D[i]
    C[i]=C[i-1]*A[i-1],D[i]=D[i+1]*A[i+1]*/
void multiply(const vector<double>& array1,vector<double>& array2)
{
    int length1 = array1.size();
    int length2 = array2.size();
    if (length1 == length2 && length2>1)
    {
        array2[0] = 1;
        for (int i=0; i<length; ++i)
        {
            array2[i] = array2[i-1] * array1[i-1];//C[i]
        }
        double temp = 1;
        for (int i=length1-2;i>=0;--i)
        {
            temp *= array1[i+1];//D[i]
            array2[i] *=temp;
        }
    }
}//O(n)

2. 字符串

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

/*非确定有限状态机*/
bool match(char* str,char* pattern)
{
    if (str == NULL || pattern == NULL)
        return false;
    return matchCore(str,pattern);
}  
bool matchCore(char* str, char* pattern)
{
    if (*str == '\0' && *pattern == '\0')
        return true;
    if (*str != '\0' && *pattern == '\0')
        return false;
    if (*(pattern+1) == '*')
    {
        if (*pattern == *str || (*pattern == '.' && *str != '\0'))
            return matchCore(str+1,pattern+2) ||
                   matchCore(str+1,pattern) ||
                   matchCore(str,pattern+2);
        else
            return matchCore(str,pattern+2);    
    }
    if (*str == *pattern || (*pattern =='.' && *str != '\0'))
        return macthCore)str+1,pattern+1);
    return false;
}  

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

/*表示数值的字符串遵循以下模式:[sign]整数部分[.[小数部分]][e|E[sign]指数部分]*/
bool IsNumberic(char* string)
{
    if (string == NULL)
        return false;
    if (*string == '+' || *string == '-')
        ++string;
    if (string == '\0')
        return false;
    bool numberic = true;
    scanDigits(&string);
    if (*string != '\0')
    {
        if (*string == '.')
        {
            ++string;
            scaDigits(&string);
            if (*string == 'e' || *string == 'E')
                numeric = isEcponential(&string);
        }
        else if (*string == 'e' || *string == 'E')
            numberic = isEcponential(&string);
        else
            numberic = false;
    }
    return numberic && *string == '\0'
}
void scanDigits(char** string)
{
    while (**string != '\0' && **string >= '0' && **string <= '9')
        ++(*string);
}
bool isExponential(char** string)
{
    if (**string != 'e' && **string != 'E')
        return false;
    ++(*string);
    if (**string == '+' || **string == '-')
        ++(*string);
    if (**string == '\0')
        return false;
    scanDigits(string);
    return (**string == '\0') ? true:false;
}

面试题55:字符流中第一个不重复的字符

class CharStatics
{
private:
    int occurrence[256];
    int index;
public:
    CharStatics():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 FirstAppera()
    {
        char ch = '\0';
        int minIndex = numeric_limits<int>::max();
        for (int i=0;i<256;++i)
        {
            if (occurrence[i] >= 0 && occurrence[i] < minIndex)
            {
                ch = (char)i;
                minIndex = occurrence[i];
            }
        }
        return ch;
    }
}

3. 链表

面试题56:链表中环的入口结点

/*1.定义快慢指针,找到环中任意一个结点;
  2.计算环的长度length3.在定义快慢指针,先让快指针走length步,在让慢指针走。直到两个指针相等时,即为入口节点;
  复杂度为O(n);*/
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
    ListNode *meetNode = meetingNode(pHead);
    if (meetNode == NULL)
        return NULL;
    int loopLength = 1;
    ListNode *pNode1 = meetNode->next;
    while (pNode1 != meetNode)
    {
        ++loopLength;
        pNode1 = pNode1->next;
    }
    pNode1 = pHead;
    ListNode *pNode2 = pHead;
    for (int i = 0; i<loopLength; i++)
        pNode1 = pNode1->next;
    while (pNode1 != pNode2)
    {
        pNode1 = pNode1->next;
        pNode2 = pNode2->next;
    }
    return pNode1;

}
ListNode* meetingNode(ListNode *pHead)
{
    if (pHead == NULL)
        return NULL;

    ListNode *pSlow = pHead->next;
    if (pSlow == NULL)
        return NULL;
    ListNode *pFast = pSlow->next;
    while (pFast != NULL && pSlow != NULL)
    {
        if (pFast == pSlow)
            return pFast;
        pSlow = pSlow->next;
        pFast = pFast->next;
        if (pFast != NULL)
            pFast = pFast->next;
    }
    return NULL;
}

面试题57:删除排序链表中重复的结点

void deleteDuplication(ListNode** pHead)//头结点也可能因重复被删除
{   
    if (pHead == NULL || *pHead == NULL)
        return;
    ListNode* pPreNode = NULL;
    ListNode* pNode = *pHead;
    while (pNode != NULL)
    {
        ListNode* pNext = pNode->m_pNext;
        bool needDelete = false;
        if (pNext != NULL && pNode->m_nValue == pNext->m_nValue)
            needDelete = true;
        if (!needDelete)
        {
            pPreNode = pNode;
            pNode = pNode->m_pNext;
        }
        else
        {
            int value = pNode->m_nValue;
            ListNode* pToBeDel = pNode;
            while (pToBeDel != NULL && pToBeDel->m_nValue = value)
            {
                pNext = pToBeDel->m_pNext;
                delete pToBeDel;
                pToBeDel = NULL;
                pToBeDel = pNext;
            }
            if (pPreNode == NULL)
                *pHead = pNext;
            else
                pPreNode->m_pNext = pNext;
            pNode = pNext;
        }
    }
}

4.树

面试题58:二叉树的下一个结点

/*有两种情况1.存在右孩子,那么下一个节点就是右孩子的左孩子(的左孩子的左孩子......)
2.不存在右节点,下一个就是是其父节点且满足该节点是其父节点的左孩子;
3.既没有右结点也不是其父节点的左孩子,则向上遍历根节点,找到一个结点是其父节点的左孩子的结点*/
BinaryTreeNode* GetNext(BinaryTreeNode* pNode)
{
    if (pNode == NULL)
        return NULL;
    BinaryTreeNode* pNext = NULL;
    if (pNode->m_pRight != NULL)
    {
        BinaryTreeNode* pRight = pNode->m_pRight;
        while (pRight->m_pLeft != NULL)
            pRight = pRight->m_pLeft;
        pNext = pRight;
    }
    else if (pNode->m_pParent != NULL)
    {
        BinaryTreeNode* pCurrent = pNode;
        BinaryTreeNode* pParent = pNode->m_pParent;
        while (pParent != NULL && pCurrent == pParent->m_pParent)
        {
            pCurrent = pParent;
            pParent = pParent->m_pParent;
        }
        pNext = pParent;
    }
    return pNext;
}  

面试题59:对称的二叉树

//前序遍历序列(根左右)与对称前序遍历序列(根右左)相同
bool isSymmetrical(TreeNode* pRoot)
{
    return isSymmetrical(pRoot, pRoot);
}
bool isSymmetrical(TreeNode *pRoot1, TreeNode *pRoot2)
{
    if (pRoot1 == NULL && pRoot2 == NULL)
        return true;
    if (pRoot1 == NULL || pRoot2 == NULL)
        return false;
    if (pRoot1->val != pRoot2->val)
        return false;
    return isSymmetrical(pRoot1->left, pRoot2->right)
        && isSymmetrical(pRoot1->right, pRoot2->left);
}

面试题60:把二叉树打印成多行

vector<vector<int> > Print(TreeNode* pRoot)
{
    vector<vector<int> > result;
    if (pRoot == NULL)
        return result;

    std::queue<TreeNode*> nodes;
    nodes.push(pRoot);
    int nextLevel = 0;//下一层结点数
    int toBePrinted = 1;//当前层未打印的结点数
    vector<int> oneRow;
    while (!nodes.empty())
    {
        TreeNode* pNode = nodes.front();
        oneRow.push_back(pNode->val);

        if (pNode->left != NULL)
        {
            nodes.push(pNode->left);
            ++nextLevel;
        }
        if (pNode->right != NULL)
        {
            nodes.push(pNode->right);
            ++nextLevel;
        }

        nodes.pop();
        --toBePrinted;
        if (toBePrinted == 0)
        {
            result.push_back(oneRow);
            oneRow.clear();
            toBePrinted = nextLevel;
            nextLevel = 0;
        }
    }
    return result;
}

面试题61:按之字形顺序打印二叉树

/*借助两个辅助栈;
    在打印某一行节点时,把下一层的子节点保存到相应的栈里。
    如果当前打印的是奇数层(一,三层等),则先保存左子结点再保存右子结点到第一个栈里;
    如果当前打印的是偶数层(二,四层等),则先保存右子结点再保存左子结点到第二个栈里;*/
vector<vector<int> > Print(TreeNode* pRoot) 
{
    vector<vector<int> > result;
    if (pRoot == NULL)
        return result;

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

    levels[current].push(pRoot);
    vector<int> oneRow;
    while (!levels[0].empty() || !levels[1].empty())
    {
        TreeNode *pNode = levels[current].top();
        levels[current].pop();
        oneRow.push_back(pNode->val);

        if (current == 0)
        {
            if (pNode->left != NULL)
                levels[next].push(pNode->left);
            if (pNode->right != NULL)
                levels[next].push(pNode->right);
        }
        else
        {
            if (pNode->right != NULL)
                levels[next].push(pNode->right);
            if (pNode->left != NULL)
                levels[next].push(pNode->left);
        }
        if (levels[current].empty())
        {
            result.push_back(oneRow);
            oneRow.clear();
            current = 1 - current;
            next = 1 - next;
        }
    }
    return result;
}

面试题62:序列化二叉树(请实现两个函数,分别用来序列化和反序列化二叉树)

/*1. 对于序列化:使用前序遍历,递归的将二叉树的值转化为字符,并且在每次二叉树的结点不为空时,在转化val所得的字符之后添加一个','作为分割。对于空节点则以 '#' 代替。
  2. 对于反序列化:按照前序顺序,递归的使用字符串中的字符创建一个二叉树(特别注意:在递归时,递归函数的参数一定要是char ** ,这样才能保证每次递归后指向字符串的指针会随着递归的进行而移动!!!)*/
void Serialize(BinaryTreeNode* pRoot,ostream& stream)
{
    if (pRoot == NULL)
    {
        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_pValue = number;
        (*pRoot)->m_pLeft = NULL;
        (*pRoot)->m_pRight = NULL;
        Deserialize(&((*pRoot)->m_pLeft),stream);
        Deserialize(&((*pRoot)->m_pRight),stream);
    }
}

面试题63:二叉搜索树的第k个结点

//中序遍历的结果就是有序序列,第K个元素就是K-1存储的节点指针
TreeNode* KthNode(TreeNode* pRoot, unsigned int k)
{
    if (pRoot == NULL || k <= 0) return NULL;
    vector<TreeNode*> vec;
    Inorder(pRoot, vec);
    if (k>vec.size())
        return NULL;
    return vec[k - 1];
}
//中序遍历,将节点依次压入vector中
void Inorder(TreeNode* pRoot, vector<TreeNode*>& vec)
{
    if (pRoot == NULL) return;
    Inorder(pRoot->left, vec);
    vec.push_back(pRoot);
    Inorder(pRoot->right, vec);
}

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

查找插入删除
数组O(n)O(1)
有序数组O(logn)O(n)
链表O(n)O(1)
有序链表O(n)O(n)
二叉树(一般情况)O(logn)O(logn)
二叉树(最坏情况)O(n)O(n)
平衡树O(logn)O(logn)
哈希表O(1)O(1)
/*插入的复杂度:O(logn), 得到中位数的复杂度:O(1)
  核心思路:
    1.维护一个大顶堆,一个小顶堆,且保证两点:
        1)小顶堆里的全大于大顶堆里的;
        2)2个堆个数的差值小于等于1
    2.当insert的数字个数为奇数时:使小顶堆个数比大顶堆多1;
        当insert的数字个数为偶数时,使大顶堆个数跟小顶堆个数一样。
    3.那么当总数字个数为奇数时,中位数就是小顶堆堆头;
        当总数字个数为偶数时,中卫数就是 2个堆堆头平均数 */
class Solution_63 
{
public:
    void Insert(int num)
    {
        count += 1;
        // 元素个数是偶数时,将小顶堆堆顶放入大顶堆
        if (count % 2 == 0) {
            big_heap.push(num);
            small_heap.push(big_heap.top());
            big_heap.pop();
        }
        else {
            small_heap.push(num);
            big_heap.push(small_heap.top());
            small_heap.pop();
        }
    }
    double GetMedian()
    {
        if (count & 0x1) {
            return big_heap.top();
        }
        else {
            return double((small_heap.top() + big_heap.top()) / 2.0);
        }
    }
private:
    int count = 0;
    priority_queue<int, vector<int>, less<int>> big_heap;  
    priority_queue<int, vector<int>, greater<int>> small_heap;
};

5.栈和队列

面试题65:滑动窗口的最大值

vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
    vector<int> vec;
    if (num.size() <= 0 || num.size()<size || size <= 0)
        return vec; //处理特殊情况
    deque<int> dq;
    //处理前size个数据,因为这个时候不需要输出最大值;
    for (unsigned int i = 0;i<size;i++)
    {
        //假如当前的元素比队列队尾的元素大,说明之前加入的这些元素不可能是最大值了。因为当前的这个数字比之前加入队列的更晚
        while (!dq.empty() && num[i] >= num[dq.back()])
            dq.pop_back(); //弹出比当前小的元素下标
        dq.push_back(i); //队尾压入当前下标
    }
    //处理size往后的元素,这时候需要输出滑动窗口的最大值
    for (unsigned int i = size;i<num.size();i++)
    {
        vec.push_back(num[dq.front()]);
        while (!dq.empty() && num[i] >= num[dq.back()])
            dq.pop_back();
        if (!dq.empty() && dq.front() <= (int)(i - size))
            dq.pop_front(); //删除队头元素
        dq.push_back(i); //将当前下标压入队尾,因为可能在未来是最大值
    }
    vec.push_back(num[dq.front()]); //最后还要压入一次
    return vec;
}

6.回溯法

面试题66:矩阵中的路径(判断在一个矩阵中是否存在一条包含某字符串所有字符的路径)

bool hasPath(char* matrix,int rows,int cols,char* str)
{
    if (matrix == NULL || rows<1 || cols <1 || str==NULL)
        return false;
    bool* visited = new bool[rows*cols];
    memset(visited,0,rows*cols);
    int pathLength = 0;
    for (int row=s;row<rows;++row)
    {
        for (int col = 0;col<cols;++col)
        {
            if (hasPathCore(matrix,rows,cols,row,col,str,pathLength,visited))
                return true;
        }
    }
    delete[] visited;
    return false;
}  
bool hasPathCore(char* matrix,int rows,int cols,int row,int col,char* str,int pathLength,bool* visited)
{
    if (str[pathLength] == '\0')
        return true;
    bool hasPath = false;
    if (row>=0 && row<rows && col>=0 && col<cols && matrix[row*cols +col] == str[pathLength] && !visited[row*cols+col])
    {
        ++pathLength;
        visited[row*cols+col] = true;
        haspath = hasPathCore(matrix,rows,cols,row,col-1,str,pathLength,visited) 
          || hasPathCore(matrix,rows,cols,row-1,col,str,pathLength,visited)
          || hasPathCore(matrix,rows,cols,row,col+1,str,pathLength,visited)
          || hasPathCore(matrix,rows,cols,row+1,col,str,pathLength,visited);
        if (!hasPath)
        {
            --pathLength;
            visited[row*cols+col] = false;
        }
    }
    return hasPath;
}  

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

/*地上有一个m行和n列的方格。一个机器人从坐标0, 0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35, 37),因为3 + 5 + 3 + 7 = 18。但是,它不能进入方格(35, 38),因为3 + 5 + 3 + 8 = 19。请问该机器人能够达到多少个格子?*/
int movingCount(int threshold, int rows, int cols)
{
    bool* flag = new bool[rows*cols];
    for (int i = 0;i<rows*cols;i++)
        flag[i] = false;
    int count = moving(threshold, rows, cols, 0, 0, flag);
    delete[] flag;
    return count;
}
//计算最大移动位置
int moving(int threshold, int rows, int cols, int i, int j, bool* flag)
{
    int count = 0;
    if (check(threshold, rows, cols, i, j, flag)) {
        flag[i*cols + j] = true;
        //标记访问过,这个标志flag不需要回溯,因为只要被访问过即可。
        //因为如果能访问,访问过会加1.不能访问,也会标记下访问过。
        count = 1 + moving(threshold, rows, cols, i - 1, j, flag)
            + moving(threshold, rows, cols, i, j - 1, flag)
            + moving(threshold, rows, cols, i + 1, j, flag)
            + moving(threshold, rows, cols, i, j + 1, flag);
    }
    return count;
}
//检查当前位置是否可以访问
bool check(int threshold, int rows, int cols, int i, int j, bool* flag)
{
    if (i >= 0 && i<rows && j >= 0 && j<cols
                    && getSum(i) + getSum(j) <= threshold
                    && flag[i*cols + j] == false)
        return true;
    return false;
}
//计算位置的数值
int getSum(int number)
{
    int sum = 0;
    while (number>0)
    {
        sum += number % 10;
        number /= 10;
    }
    return sum;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值