剑指Offer66题之每日6题 - 第三天

原题链接:

第一题:调整数组顺序使奇数位于偶数前面

题目:

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变

解析:

如果这题没有加粗的这个条件,这是一个原版微软笔试题,我也来说下做法,设置两个指针headtail,分别指向首尾,head向后移动,直到找到第一个偶数,tail向前移动,直到找到第一个奇数,交换这两个数,重复上述过程,直到headtail相等为止;

时间复杂度: O(n) ;
空间复杂度: O(1)

class Solution {
public:
    void reArray(vector<int> &array) {
        int head = 0, tail = array.size() - 1;
        while (head < tail) {
            for (; head < tail && array[head] % 2; head++);
            for (; head < tail && !(array[tail] % 2); tail--);
            if (head < tail)
                swap(array[head++], array[tail--]);
        }
    }
};

现在我们再来考虑加粗的条件,首先肯定是最直白的算法,统计一下奇数的数量,然后开一个辅助数组,扫描原数组,发现奇数就往辅助数组的前面放,发现偶数就往辅助数组“后面”放,最后另原数组等于辅助数组就行了。

时间复杂度: O(n)
空间复杂度: O(n)

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int cnt = 0;
        for (int i = 0; i < (int)array.size(); cnt += array[i++] % 2);
        vector<int> arr(array);
        for (int i = 0, j = cnt, id = 0; id < (int)array.size(); id++)
            array[id] % 2 ? arr[i++] = array[id] : arr[j++] = array[id];
        array.swap(arr);
    }
};

要解决这个特殊的排序算法就要用稳定的排序算法,sort函数是不稳定排序,但是我们可以把sort函数改装成稳定排序,通过增加一个下标,然后利用结构体的二级排序可以搞定。

时间复杂度: O(nlogn)
空间复杂度: O(2n) .

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<pair<int, int> > arr;
        for (int i = 0; i < (int)array.size(); i++)
            arr.emplace_back(array[i], i);
        sort(arr.begin(), arr.end(), cmp);
        for (int i = 0; i < (int)array.size(); array[i] = arr[i].first, ++i);
    }

    static bool cmp(const pair<int, int> &A, const pair<int, int> &B)
    {
        if (A.first % 2 && !(B.first % 2))
            return true;
        return A.second < B.second;
    }
};

然而,STL中内置了稳定排序算法stable_sort

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        stable_sort(array.begin(), array.end(), cmp);
    }

    static bool cmp(const int &A, const int &B)
    {
        return (A % 2 && !(B % 2));
    }
};

既然稳定排序可以解决这个问题,那么仿冒泡排序,和仿插入排序这两种排序算法也是可以搞定这个问题的。

时间复杂度: O(n2)
空间复杂度: O(1)

下面依次是仿冒泡排序和仿插入排序的代码:

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        for (int i = 0; i < (int)array.size(); i++)
            for (int j = 0; j < (int)array.size() - i - 1; j++)
                if (!(array[j] % 2) && array[j + 1] % 2)
                    swap(array[j], array[j + 1]);
    }
};
class Solution {
public:
    void reOrderArray(vector<int> &array) {
        for (int i = 1; i < (int)array.size(); i++)
            if (array[i] % 2)
                for (int j = i - 1; j >= 0 && !(array[j] % 2); 
                     swap(array[j], array[j + 1]), j--);
    }
};

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

题目:

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

解析:

前后指针法,让第一个指针先向前走k-1步,然后第二个指针和第一个指针同时向后走,直到第二个指针后面没有结点,第二个指针指向的就是第k个结点。

不过要注意,如果链表的结点个数少于k,就要返回空指针。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        auto pre = pListHead, ret = pre;
        for (int i = 0; i < k - 1 && pre != nullptr; ++i, pre = pre->next);
        if (pre == nullptr)
            return nullptr;
        for (; pre->next != nullptr; ret = ret->next, pre = pre->next);
        return ret;
    }
};

第三题:反转链表

题目:

输入一个链表,反转链表后,输出链表的所有元素。

解析:

有两种做法;

第一种做法是递归做法,递归做法代码都比较短,比较好理解;

想法就是把头结点后面的链表都反转了,然后把头结点接在最后就好了,递归做法就是你是怎么想的,代码就怎么写。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if (pHead == nullptr || pHead->next == nullptr)
            return pHead;
        auto pre = ReverseList(pHead->next);
        pHead->next->next = pHead;
        pHead->next = nullptr;
        return pre;
    }
};

第二种做法就是非递归做法,利用链表的头插法会得到反序的链表这一性质来做。

具体做法是:提取头结点,然后遍历链表,取出的每一个结点,用头插法插入头结点。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if (pHead == nullptr)
            return nullptr;
        auto ret = pHead, pre = ret->next;
        ret->next = nullptr;
        for (auto tmp = ret; pre != nullptr;
             tmp = pre->next, pre->next = ret, ret = pre, pre = tmp);

        return ret;
    }
};

第四题:合并两个排序的链表

题目:

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

解析:

同上题一样,也是有递归做法和非递归做法。

递归做法:两个链表中,哪个头结点的值小就返回哪个头结点,然后合并剩下的链表,最后令头结点的值较小的那个头结点的next指向合并后的剩余链表,一样的,怎么想的,代码就怎么写。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if (pHead1 == nullptr)
            return pHead2;
        if (pHead2 == nullptr)
            return pHead1;
        return pHead1->val > pHead2->val ? (pHead2->next = Merge(pHead1, pHead2->next), pHead2) : 
                                            (pHead1->next = Merge(pHead1->next, pHead2), pHead1);
    }
};

第五题:树的子结构

题目:

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。

解析:

这题我只想到递归解法,递归其实解决问题的时候是很好思考的,代码也很好写。

首先,判断B是不是A的子结构,然后再判断B是不是A的左子树的子结构,B是不是A的右子树的子结构;

编写isTheSameTree函数时要注意下面这两个判断条件:

  • pRoot2 == nullptr
  • pRoot1 == nullptr && pRoot2 != nullptr

两个条件都不满足保证了两个指针都不为空,在草稿纸上画一下就清楚了。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/

class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if (pRoot1 == nullptr || pRoot2 == nullptr)
            return false;
        return isTheSameTree(pRoot1, pRoot2) || 
            HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
    }
    bool isTheSameTree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if (pRoot2 == nullptr)
            return true;
        if (pRoot1 == nullptr && pRoot2 != nullptr)
            return false;
        return pRoot1->val == pRoot2->val && 
            isTheSameTree(pRoot1->left, pRoot2->left) && isTheSameTree(pRoot1->right, pRoot2->right);
    }
};

第六题:二叉树的镜像

题目:

操作给定的二叉树,将其变换为源二叉树的镜像。

解析:

还是递归求解,把根节点的左右子树都转化成镜像二叉树,然后再交换左右指针就行了。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};*/

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if (pRoot == nullptr)
            return ;
        Mirror(pRoot->left);
        Mirror(pRoot->right);
        TreeNode *tmp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = tmp;
    }
};
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值