剑指Offer66题之13-18

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/muyiyushan/article/details/88403661

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

题目

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

解析

如果这题没有加粗的这个条件,这是一个原版微软笔试题。设置两个指针head和tail,分别指向首尾,head向后移动,直到找到第一个偶数,tail向前移动,直到找到第一个奇数,交换这两个数,重复上述过程,直到head和tail相等为止;
时间复杂度: O ( n ) O(n) ;
空间复杂度: O ( 1 ) O(1)

class Solution {
public:
    void reArray(vector<int> &array) {
        int head = 0, tail = array.size() - 1;
        while (head < tail) {
        	// head指向偶数
            for (; head < tail && array[head] % 2; head++);
            // tail指向奇数
            for (; head < tail && !(array[tail] % 2); tail--);
            // swap交换两个两个元素内存地址
            if (head < tail)
                swap(array[head++], array[tail--]);
        }
    }
};

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

class Solution {
public:
    void reOrderArray(vector<int> &array) {
    	// cnt:奇数个数
        int cnt = 0;
        // 计算cnt
        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 ( n l o g n ) O(nlogn)
空间复杂度: O ( 2 n ) 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)
    {
        // 如果A是奇数,B是偶数,返回True. (即奇数在前,偶数在后)
        if (A.first % 2 && !(B.first % 2))
            return true;
        // 否则,保持A在前B在后的顺序    
        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));
    }
};

备注

各种排序算法,稳定排序,sort, stable_sort函数

第14题:链表中倒数第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) {
        // pre为前,ret为后
        auto pre = pListHead, ret = pre;
		// pre向前k-1步,指向第k个节点
        for (int i = 0; i < k - 1 && pre != nullptr; ++i, pre = pre->next);
        // 若pre为空指针,则少于k个节点,返回空
        if (pre == nullptr)
            return nullptr;
        // 前后指针同时后移
        for (; pre->next != nullptr; ret = ret->next, pre = pre->next);
        return ret;
    }
};

第15题:反转链表

题目

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

解析

递归方法,将第一个之后的链表翻转,再指向第一个

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

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
    	//如果只有一个或0个节点
        if (pHead == nullptr || pHead->next == nullptr)
            return pHead;
        // 递归处理后面的链表
        auto pre = ReverseList(pHead->next);
        // 翻转的后面的再指向第一个
        pHead->next->next = pHead;
        //第一个指向空指针
        pHead->next = nullptr;
        return pre;
    }
};

第二种做法就是非递归做法,利用链表的头插法会得到反序的链表这一性质来做。
具体做法是:提取头结点,然后遍历链表,取出的每一个结点,用头插法插入头结点。

扫描二维码关注公众号,回复: 5583036 查看本文章
/*
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;
    }
};

第16题

题目:合并两个排序的链表

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

解析

递归做法:两个链表中,令目标头结点指向头结点小的链表,然后合并剩下的链表,最后令目标头结点指向合并链表

/*
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);
    }
};

第17题:树的子结构

题目

输入两棵二叉树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;
        // 判断当前是否与pRoot2相同,或子树是否包含pRoot2
        return isTheSameTree(pRoot1, pRoot2) || 
            HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
    }
    // 判断两个树是否相同
    bool isTheSameTree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        // pRoot2为nullptr,pRoot1可以不为空
        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);
    }
};

备注

子树的意思是只要包含了一个结点,就得包含这个结点下的所有节点.
子结构的意思是包含了一个结点,可以只取左子树或者右子树,或者都不取。
简单而言,与子树不同的是,子结构可以是A树的任意一部分。

第18题:二叉树的镜像

题目

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

解析

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

/*
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;
    }
};

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

猜你喜欢

转载自blog.csdn.net/muyiyushan/article/details/88403661