剑指offer 刷题记录(11~20题)

接上一篇博客,突然觉得把所有的题都放在一起会显得特别臃肿,所以感觉还是分下来比较好。于是决定一篇只写是个题。那么,继续上一篇博客的刷题之旅!

11.输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

常规解法:最直观的思路就是不断的左移,判断n的从右到左的位是不是有1。共循环整数的位数的次数,例如32位整数则循环32次,代码如下:

//运行时间:3ms 占用内存:376k
class Solution {
public:
     int  NumberOf1(int n) {
        int count = 0;
        unsigned int flag = 1;
        while (flag) {
            if (n & flag)
                count++;
            flag = flag << 1;
        }
        return count;
     }
};

最巧妙的解法:如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。有几个1则只需循环几次。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。代码如下:

//运行时间:3ms 占用内存:460k
class Solution {
public:
     int  NumberOf1(int n) {
         int count = 0;
         
         while(n) {
             ++count;
             n = (n-1) & n;
         }
         return count;
     }
};

12.给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0。

主要的就是特殊情况的处理。

运行时间:3ms 占用内存:504k
class Solution {
public:
    double Power(double base, int exponent) {
        if (base == 0.0) {
            return 0.0;
        }
        double result = 1;// 前置结果设为1.0,即当exponent=0 的时候,就是这个结果
        int e = exponent > 0 ? exponent : -exponent;// 获取指数的绝对值
        for(int i = 1 ; i <= e; i ++) {// 根据指数大小,循环累乘
            result *= base;
        }
        return exponent > 0 ? result : 1 / result;// 根据指数正负,返回结果
    }
};

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

首先是一种偷懒的做法,即利用一个新的数组去做:

//运行时间:5ms 占用内存:480k
class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> result;//创建一个新的数组
        int length=array.size();//所输入数组的大小
        
        for(int i=0;i<length;i++) {
            if(array[i]%2==1)
                result.push_back(array[i]);//先将奇数存入数组
        }
        for(int i=0;i<length;i++) {
            if(array[i]%2==0)
                result.push_back(array[i]);//再将偶数放入数组
        }
        array=result;
    }
};

另一种做法,类似于冒泡排序的思想:

//运行时间:4ms 占用内存:460k
for (int i = 0; i < array.size();i++) {
    for (int j = array.size() - 1; j>i;j--) {
        if (array[j] % 2 == 1 && array[j - 1]%2 == 0) {//前偶后奇交换
            int temp=array[j];
            array[j]=array[j-1];
            array[j-1]=temp;
        }
    }
}        

多提一嘴,offer书上的要求是不用顺序要求,因此书上的做法是这样:

int length = array.size()-1;

if (array.empty())
return;

int* pBegin = &array[0];
int* pEnd = &array[length];

while (pBegin < pEnd)
{
    // 向后移动pBegin,直到它指向偶数
    while (pBegin < pEnd && (*pBegin & 0x1) != 0)
        pBegin++;

    // 向前移动pEnd,直到它指向奇数
    while (pBegin < pEnd && (*pEnd & 0x1) == 0)
        pEnd--;

    if (pBegin < pEnd)
    {
        int temp = *pBegin;
        *pBegin = *pEnd;
        *pEnd = temp;
    }
}
/*答案错误:您提交的程序没有通过所有的测试用例
case通过率为0.00%

用例:
[1,2,3,4,5,6,7]

对应输出应该为:

[1,3,5,7,2,4,6]

你的输出为:

[1,7,3,5,4,6,2]
*/

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

运行时间:6ms 占用内存:472k
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == nullptr || k == 0)
            return nullptr;

        ListNode *pAhead = pListHead;
        ListNode *pBehind = nullptr;

        for(unsigned int i = 0; i < k - 1; ++ i)
        {
            if(pAhead->next != nullptr)
                pAhead = pAhead->next;
            else
            {
                return nullptr;
            }
        }

        pBehind = pListHead;
        while(pAhead->next != nullptr)
        {
            pAhead = pAhead->next;
            pBehind = pBehind->next;
        }

        return pBehind;
    }
};

15.输入一个链表,反转链表后,输出新链表的表头。

//运行时间:3ms 占用内存:460k
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode* pReversedHead = nullptr;
        ListNode* pNode = pHead;
        ListNode* pPrev = nullptr;
        while(pNode != nullptr) {
            ListNode* pNext = pNode->next;

            if(pNext == nullptr)
                pReversedHead = pNode;

            pNode->next = pPrev;

            pPrev = pNode;
            pNode = pNext;
        }
        return pReversedHead;
    }
};

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

//运行时间:3ms 占用内存:488k
/*
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;
        else if(pHead2 == nullptr)
            return pHead1;

        ListNode* pMergedHead = nullptr;

        if(pHead1->val < pHead2->val) {
            pMergedHead = pHead1;
            pMergedHead->next = Merge(pHead1->next, pHead2);
        }
        else {
            pMergedHead = pHead2;
            pMergedHead->next = Merge(pHead1, pHead2->next);
        }
        return pMergedHead;
    }
};

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

这题也还是得用递归来做:

//运行时间:3ms 占用内存:476k
/*
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)
    {
        bool result = false;
        if(pRoot1 != nullptr && pRoot2 != nullptr) {//当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
            if(pRoot1->val == pRoot2->val)//以这个根节点为为起点判断是否包含Tree2
                result = DoesTree1HaveTree2(pRoot1, pRoot2);
            if(!result)//如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
                result = HasSubtree(pRoot1->left, pRoot2);
            if(!result)//如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
                result = HasSubtree(pRoot1->right, pRoot2);
        }
        return result;//返回结果
    }
    
    bool DoesTree1HaveTree2(TreeNode* pRoot1, TreeNode* pRoot2) {
    if(pRoot2 == nullptr)//如果Tree2已经遍历完了都能对应的上,返回true
        return true;
    if(pRoot1 == nullptr)//如果Tree2还没有遍历完,Tree1却遍历完了。返回false
        return false;
    if(pRoot1->val != pRoot2->val) //如果其中有一个点没有对应上,返回false
        return false;
    //如果根节点对应的上,那么就分别去子节点里面匹配(递归思想)
    return DoesTree1HaveTree2(pRoot1->left, pRoot2->left) &&
        DoesTree1HaveTree2(pRoot1->right, pRoot2->right);
    }
};

值得一提的是,offer书上的val是double类型。因此在判断是否相等时不能简单的直接用==,而是要通过做差来判断:

bool Equal(double num1, double num2){
    if((num1 - num2 > -0.0000001) && (num1 - num2 < 0.0000001))
        return true;
    else
        return false;
}

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

先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子节点,当交换完所有的非叶子结点的左右子结点之后,就得到了树的镜像。在这里插入图片描述
递归方法代码如下:

//运行时间:5ms 占用内存:400k
/*
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;
        //交换左右节点
        TreeNode *pTemp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = pTemp;
        //递归实现
        if(pRoot->left)
            Mirror(pRoot->left);
        if(pRoot->right)
            Mirror(pRoot->right);
    }
};

基于循环的方法如下:

//运行时间:3ms 占用内存:476k
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot == nullptr)
            return;

        std::stack<TreeNode*> stackTreeNode;
        stackTreeNode.push(pRoot);

        while(stackTreeNode.size() > 0) {
            TreeNode *pRoot = stackTreeNode.top();
            stackTreeNode.pop();

            TreeNode *pTemp = pRoot->left;
            pRoot->left = pRoot->right;
            pRoot->right = pTemp;

            if(pRoot->left)
                stackTreeNode.push(pRoot->left);

            if(pRoot->right)
                stackTreeNode.push(pRoot->right);
        }
    }
};

19.输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

这题还是有点难的,主要就是循环套循环,还有边界条件的各种坑点。主要思路是:利用左上和右下的坐标定位出一次要旋转打印的数据,一次旋转打印结束后,往对角分别前进和后退一个单位。通过对top与bottom,left与right的比较来限定for循环范围。

//运行时间:4ms 占用内存:376k
class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        int rows = matrix.size();
        int columns = matrix[0].size();
        vector<int> result;
        //若为空则直接返回
        if (rows == 0 || columns == 0)
            return result;
        //记录上下左右界限
        int left = 0, top = 0, right = columns - 1, bottom = rows - 1;
        while (left <= right && top <= bottom)
        {
            for (int i = left; i <= right; ++i) //从左到右打印一行
                result.push_back(matrix[top][i]);
            for (int i = top + 1; i <= bottom; ++i)//从上到下打印一列
                result.push_back(matrix[i][right]);
            if (top != bottom) //从右到左打印一行
                for (int i = right - 1; i >= left; --i)
                    result.push_back(matrix[bottom][i]);
            if (left != right) //从下到上打印一行
                for (int i = bottom - 1; i > top; --i)
                    result.push_back(matrix[i][left]);

            left++,top++,right--,bottom--;
        }
        return result;
    }
};

20.定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

//运行时间:5ms 占用内存:480k
class Solution {
public:
    void push(int value) {
        m_data.push(value);//把新元素添加到辅助栈
        
        if(m_min.empty())//当新元素比之前的最小元素小时,把新元素插入辅助栈里;
            m_min.push(value);
        if(m_min.top()>value)//否则把之前的最小元素重复插入辅助栈里
            m_min.push(value);
    }
    void pop() {
        if(m_min.top()==m_data.top())
            m_min.pop();
        m_data.pop();
    }
    int top() {
        return m_data.top();
    }
    int min() {
        return m_min.top();
    }
private:
   stack<int> m_data;//数据栈,存放栈的所有元素
   stack<int> m_min;// 辅助栈,存放栈的最小元素
};
发布了36 篇原创文章 · 获赞 8 · 访问量 1571

猜你喜欢

转载自blog.csdn.net/weixin_43619346/article/details/103667409