C++学习笔记(三)

面试题:替换空格

题目:实现一个函数,把字符串中的每个空格替换成“%20”,例如输入“We are happy.”,则输出“Wr%20are%20happy.”。典型应用:网络编程中,若URL参数含有特殊字符(空格、'#')等会导致无法获取正确参数,这时需要将特殊符号转换成服务器可识别的字符,类似编程语言中的反斜杆。(要求时间复杂度为O(n))。

#include <iostream>

using namespace std;

#define log(message) cout << message << endl;

// length 为字符串数组str的总容量
bool ReplaceBlank(char str[], int length)
{
    if (str == NULL || length < 0)
    {
        log("str==NULL or length<0");
        return false;
    }

    int originalLength = 0, numberOfBlank = 0;
    int i = 0;
    while (str[i] != '\0')
    {
        originalLength++;
        if (str[i] == ' ')
        {
            numberOfBlank++;
        }
        i++;
    }

    int newLength = originalLength + numberOfBlank * 2;
    if (newLength > length)
    {
        log("The length isn't enough");
        return false;
    }

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

    return true;
}

int main()
{
    char test[100] = "We are happy.";
    if (ReplaceBlank(test, 100))
    {
        cout << test << endl;
    }
    return 0;
}

面试题:链表增删节点

题目:给出单向链表结点定义如下:

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

往链表的末尾增加一个结点:

bool AddToTail(ListNode** pHead, int value)
{
    if(pHead == NULL)
    {
        log("pHead is NULL");
        return false;
    }
    
    ListNode* pNew = new ListNode();
    pNew->m_nValue = value;
    pNew->m_pNext = NULL;
    
    if(*pHead == NULL)
    {
        *pHead = pNew;
    }
    else
    {
        ListNode* pNode = *pHead;
        while(pNode->m_pNext != NULL)
            pNode = pNode->m_pNext;
        pNode->m_pNext = pNew;
    }
    return true;
}

       上面代码中,我们特别注意函数的第一个参数pHead是一个指向指针的指针。当我们往一个空链表中插入一个结点时,新插入的结点就是链表的头指针,由于此时会改动头指针,因此必须把pHead参数设成指向指针的指针,否则出了这个函数pHead仍然是一个空指针

       形参其实是在把实参传递给子函数的时候定义了一个新的变量,但是这个变量的值和实参相等。所以在子函数里无论怎么修改都不会影响到调用它的函数的实参的值。在用指针做参数的时候,因为传递的不是值本身,而是它所对应的地址,我们改变值的时候是改变那个地址里的值,用法为*p = newValue。上例中若参数pHead为一重指针,一开始若是空链表时我们修改的是形参指针指向的地址,而不是实参指针指向的地址

void Test(int a)    // void AddToTail(ListNode* pHead, int value)
{
    a = newValue;    // pHead = pNew;
}

void Test(int* a)    // void AddToTail(ListNode** pHead, int value)
{
    *a = newValue;    // pHead->m_pNext = pNew;
}

C++学习笔记(三)_第1张图片

从链表中删除第一个含有某值的结点:

bool RemoveNode(ListNode** pHead, int value)
{
    if(pHead == NULL || *pHead == NULL)
    {
        log("pHead is NULL or *pHead is NULL");
        return false;
    }
    
    ListNode* pToBeDeleted = NULL;
    if((*pHead)->m_nValue == value)
    {
        pToBeDeleted = *pHead;
        *pHead = (*pHead)->m_pNext;
    }
    else
    {
        ListNode* pNode = *pHead;
        while(pNode->m_pNext != NULL && pNode-> m_pNext->m_nValue != value)
            pNode = pNode->m_pNext;
        if(pNode->m_pNext != NULL && pNode->m_pNext->m_nValue == value)
        {
            pToBeDeleted = pNode->m_pNext;
            pNode->m_pNext = pNode->m_pNext->m_pNext;
        }
    }
    
    if(pToBeleted != NULL)
    {
        delete pToBeDeleted;
        pToBeDeleted = NULL;
    }
    return true;
}

面试题:从尾到头输出链表

题目:输入一个链表的头结点,从尾到头反过来打印每个结点的值。

链表结点定义如下:

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

代码实现如下:

void PrintListReversingly_Iteratively(ListNode* pHead)
{
    if (pHead == NULL)
    {
        log("The List is empty.");
        return;
    }

    stack<ListNode*> nodes;
    ListNode* visit = pHead;
    while (visit != NULL)
    {
        nodes.push(visit);
        visit = visit->m_pNext;
    }

    while (!nodes.empty())
    {
        cout << nodes.top()->m_nKey << endl;
        nodes.pop();
    }
}

面试题:在O(1)时间删除链表结点

题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。(注意给出结点指针,而不是结点的值)

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

代码实现如下:

void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)
{
    if (pListHead == NULL || *pListHead == NULL || pToBeDeleted == NULL)
    {
        log("NULL ERROR");
        return;
    }

    if (pToBeDeleted->m_pNext != NULL)    // 要删除的结点不是尾结点
    {
        ListNode* pNext = pToBeDeleted->m_pNext;
        pToBeDeleted->m_nValue = pNext->m_nValue;
        pToBeDeleted->m_pNext = pNext->m_pNext;
        delete pNext;
        pNext = NULL;
    }
    else if (*pListHead == pToBeDeleted)    // 链表中只有一个结点
    {
        delete pToBeDeleted;
        pToBeDeleted = NULL;
        *pListHead = NULL;
    }
    else    // 链表中有多个结点,删除尾结点
    {
        ListNode* pNode = *pListHead;
        while (pNode->m_pNext != pToBeDeleted)
            pNode = pNode->m_pNext;
        pNode->m_pNext = NULL;
        delete pToBeDeleted;
        pToBeDeleted = NULL;
    }
}

int main()
{
    ListNode *head = new ListNode();
    head->m_nValue = 1;
    head->m_pNext = NULL;
    ListNode *v = head;
    ListNode *d = NULL;
    for (int i = 2; i < 6; i++)
    {
        ListNode *np = new ListNode();
        np->m_nValue = i;
        np->m_pNext = NULL;
        v->m_pNext = np;
        v = np;
        if (i == 5)
            d = np;
    }
    DeleteNode(&head, d);
    for (ListNode *p = head; p != NULL; p = p->m_pNext)
    {
        cout << p->m_nValue << endl;
    }
    
    return 0;
}

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

题目:输入一个链表,输出该链表中倒数第k个结点。

ListNode* FindKthToTail(ListNode* pListHead, int k)
{
    if(pListHead == NULL || k <= 0)
    {
        log("链表为空 or K值小于等于零");
        return NULL;
    }
    ListNode *pAhead = pListHead;
    ListNode *pBhead = NULL;
    for(int i = 0; i < k - 1; i++)
    {
        if(pAhead->m_pNext != NULL)
            pAhead = pAhead->m_pNext;
        else
        {
            log("链表长度小于k");
            return NULL;
        }
    }
    pBhead = pListHead;
    while(pAhead->m_pNext != NULL)
    {
        pAhead = pAhead->m_pNext;
        pBhead = pBhead->m_pNext;
    }
    return pBhead;
}

面试题:反转链表

ListNode* ReverseList(ListNode* pHead)
{
    if(pHead == NULL)
    {
        log("The List is empty.");
        return NULL;
    }
    ListNode* pReversedHead = NULL;
    ListNode* pNode = pHead;
    ListNode* pPrev = NULL;
    while(pNode != NULL)
    {
        ListNode* pNext = pNode->m_pNext;
        if(pNext == NULL)
            pReversedHead = pNode;
        pNode->m_pNext = pPrev;
        pPrev = pNode;
        pNode = pNext;
    }
    return pReversedHead;
}

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

题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。

ListNode* Merge(ListNode *pHead1, ListNode *pHead2)    // 不用递归的话,就用辅助空间O(m+n)
{
    if (pHead1 == NULL)
        return pHead2;
    if (pHead2 == NULL)
        return pHead1;
    ListNode *pMergedHead = NULL;
    if (pHead1->value < pHead2->value)
    {
        pMergedHead = pHead1;
        pMergedHead->next = Merge(pHead1->next, pHead2);
    }
    else
    {
        pMergedHead = pHead2;
        pMergedHead->next = Merge(pHead1, pHead2->next);
    }
    return pMergedHead;
}

不用递归的话,就用辅助空间O(m+n):

ListNode* Merge(ListNode *pHead1, ListNode *pHead2)
{
    if (pHead1 == NULL)
        return pHead2;
    if (pHead2 == NULL)
        return pHead1;
    ListNode *mHead = NULL;
    ListNode *v1 = pHead1, *v2 = pHead2;
    if (pHead1->value <= pHead2->value)
    {
        mHead = pHead1;
        v1 = pHead1->next;
    }
    else
    {
        mHead = pHead2;
        v2 = pHead2->next;
    }
    mHead->next = NULL;
    ListNode *p = mHead;
    while (v1 != NULL&&v2 != NULL)
    {
        if (v1->value < v2->value)
        {
            p->next = v1;
            v1 = v1->next;
        }
        else
        {
            p->next = v2;
            v2 = v2->next;
        }
        p = p->next;
        p->next = NULL;
    }
    while (v1 != NULL)
    {
        p->next = v1;
        v1 = v1->next;
        p = p->next;
        p->next = NULL;
    }
    while (v2 != NULL)
    {
        p->next = v2;
        v2 = v2->next;
        p = p->next;
        p->next = NULL;
    }
    return mHead;
}

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

题目:求两个单向链表的第一个公共结点,且时间复杂度为O(m+n)。

思路:由于单向链表的特点,每一个结点都仅有一个后继结点,故从第一个公共结点开始,连个链表的内容开始相同,如下图:

C++学习笔记(三)_第2张图片

代码1,需要O(m+n)辅助空间。(具体思路可以参考原书)

ListNode* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2)
{
    if (pHead1 == NULL || pHead2 == NULL)
        return NULL;
    stack<ListNode*> nodes1, nodes2;
    ListNode *visit = pHead1;
    while (visit != NULL)
    {
        nodes1.push(visit);
        visit = visit->next;
    }
    visit = pHead2;
    while (visit != NULL)
    {
        nodes2.push(visit);
        visit = visit->next;
    }
    ListNode *finder = NULL;
    while ((!nodes1.empty()) && (!nodes2.empty()))
    {
        if (nodes1.top() == nodes2.top())
        {
            finder = nodes1.top();
            nodes1.pop();
            nodes2.pop();
        }
        else
        {
            break;
        }
    }
    return finder;
}

代码2,时间复杂度同样是O(m+n),无需辅助空间。(具体思路可以参考原书)

unsigned int GetListLength(ListNode *pHead)
{
    unsigned int nLength = 0;
    ListNode *pNode = pHead;
    while (pNode != NULL)
    {
        ++nLength;
        pNode = pNode->next;
    }
    return nLength;
}

ListNode* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2)
{
    if (pHead1 == NULL || pHead2 == NULL)
        return NULL;
    unsigned int nLength1 = GetListLength(pHead1);
    unsigned int nLength2 = GetListLength(pHead2);

    int nLengthDif = nLength1 - nLength2;
    ListNode *pListHeadLong = pHead1;
    ListNode *pListHeadShort = pHead2;
    if (nLength2 > nLength1)
    {
        pListHeadLong = pHead2;
        pListHeadShort = pHead1;
        nLengthDif = nLength2 - nLength1;
    }

    // 现在长链表上走几步,再同时在两个链表上遍历
    for (int i = 0; i < nLengthDif; i++)
        pListHeadLong = pListHeadLong->next;
    while ((pListHeadLong != NULL) && (pListHeadShort != NULL) && (pListHeadLong != pListHeadShort))
    {
        pListHeadLong = pListHeadLong->next;
        pListHeadShort = pListHeadShort->next;
    }

    // 得到第一个公共结点
    ListNode *pFirstCommonNode = pListHeadLong;
    return pFirstCommonNode;
}

问题变形:求二叉树中两个叶节点的最低公共祖先(假设树结点里面的指针由子结点指向父结点)。


你可能感兴趣的:(面试题,链表操作)