剑指offer11-20

夙愿已清 提交于 2020-02-07 04:12:26

11. 旋转数组的最小值

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路:由于旋转数组的特性,尾元素肯定是小于首元素的(当不考虑重复值时)。所以,每次找中间值,如果中间值大于首元素,则中元素为前面那段,相反,则为后面那段。截止条件为s,e相差1。

其次,考虑旋转0个的情况,返回的是第一个。在其次,考虑有重复的情况。如{1,0,1,1,1},{1,1,1,0,1}都可以看成{0,1,1,1,1}的旋转,故此时使用循环依次查找。

代码:

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size() == 0) //注意vector的长度并不是array.length
            return 0;
        int length = rotateArray.size();
        int s = 0, e = length - 1;
        int mid = 0, min;
        while(rotateArray[s] >= rotateArray[e])//旋转数组的特性,尾元素肯定是小于首元素的(当不考虑重复值时)
        {
            if(e - s == 1) return rotateArray[e];
            mid = (s + e)/2;
            if(rotateArray[s] == rotateArray[mid] && rotateArray[e] == rotateArray[mid] )//当三个下标的值相等时,只得顺序查找
            {
                min = rotateArray[s];
                for(int i = s; i <= e; i++)
                {
                    if(rotateArray[i] < min) 
                        min = rotateArray[i];
                }
                return min;
            }
            if(rotateArray[mid] <= rotateArray[e]) //如果中间值大于首元素,则中元素为前面那段,相反,则为后面那段
                e = mid;
            else if(rotateArray[mid] >= rotateArray[s]) 
                s = mid;
        }
       return rotateArray[mid];
    }
};

12. 二进制中1 的个数

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

思路:
way1: 首先对负数做一点点特殊操作,可以将最高位的符号位1变成0,也就是n & 0x7FFFFFFF(0x7FFFFFFF 的二进制表示就是除了首位是 0,其余都是1,也就可以表示最大的int整型数。),这样就把负数转化成正数了,唯一差别就是最高位由1变成0,因为少了一个1,所以count加1。之后再按照while循环里处理正数的方法来操作就可以啦!

class Solution {
public:
     int  NumberOf1(int n) {
         int count = 0;
         if( n < 0)
         {
             n = n & 0x7FFFFFFF;
             count ++;
         }
         while(n != 0)
         {
             if(n % 2 == 1)
                 count ++;
             n = n / 2;
         }
         return count;
     }
};

**way2:**除了使用除2操作,把整数右移一位和把整数除以2在数学上是等价的,
在计算机中,除法的效率比移位运算要低得多,在实际变成中应尽可能的利用以为运算代替乘除法。同理,取余操作的效率也是明显低于和1做与运算的,这两个操作都能打到同样的效果,但是与运算的效率更高一些

class Solution {
public:
     int  NumberOf1(int n) {
         int count = 0;
         if( n < 0)
         {
             n = n & 0x7FFFFFFF;
             count ++;
         }
         while(n != 0)
         {
             if(n & 1 == 1)
                 count ++;
             n = n >> 1;
         }
         return count;
     }
};

**way3:**把一个整数减去1在与它本身做与运算,就会把该整数最右边一个1变成0。那么一个整数的二进制有多少个1,就可以进行多少次这样的操作就可以了。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000。

class Solution {
public:
     int  NumberOf1(int n) {
         int count = 0;
         while( n != 0)
         {
             count ++;
             n = n & (n-1);
         }
         return count;
};

13. 求数值中的整数次方

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

思路:方法1,直接求,注意任意数的0次方等于1,负数的次方等于它的倒数(时间复杂度O(n))。方法2,利用平方的性质,an=a(n/2) * a(n/2)或an=a^(n-1/2) * a^(n-1/2)*a(分别为偶数、奇数的情况)(时间复杂度O(logn))

代码一:

class Solution {
public:
    double Power(double base, int exponent) {
        double result = 1;
        if(exponent > 0)
        {
            while(exponent --)
            {
                result *= base;
            }
        }
        else if ( exponent < 0)
        {
            exponent = -exponent;
            while(exponent --)
            {
                result *= base;
            }
            result = 1.0 / result; //幂次方为负数时,其结果为其倒数
        }
        return result;
};

代码2(利用性质):

class Solution {
public:
    double Power(double base, int exponent) {
        if (exponent == 0) return 1;
        int n = exponent > 0 ? exponent : -exponent;
        double result = Power(base, n >> 1);
        result *= result;
        if(n & 1) result *= base;
        if(exponent < 0) result = 1.0 /result; //幂次方为负数时,其结果为其倒数
        return result;
    }
};

14. 调整数组顺序使奇数位于偶数前面

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

思路:(1)新开数组直接模拟,或者用插入排序等方法
代码1:

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> arr;
        for(int i = 0; i < array.size(); i++)
            if((array[i] & 1) == 1)
                  arr.push_back(array[i]);
        for(int i = 0; i < array.size(); i++)
            if((array[i] & 1) == 0)
                  arr.push_back(array[i]);
         for(int i = 0; i < arr.size(); i++)
             array[i] = arr [i];
    }
};

(2)首先,设置前指针指向第一个数,并且只向后移动;
  然后,.设置第二个指针指向最后一个数,并且只向前移动;
  最后,在两个指针相遇之前,第一个指针总是位于第二个指针的前面。如果第一个指针指向的数字是偶数,并且第二个指针指向的数字是奇数,我们就交换这两个数字。

测试不能通过!!!原来快排是不稳定的,而此题目要求相对位置不变!!!
其实,要想保证原有次序,则只能顺次移动或相邻交换。那么依然设置两个指针:
首先,begin从左向右遍历,找到第一个偶数;
然后,from从begin+1开始向后找,直到找到第一个奇数;
接着,将[begin,…,from-1]的元素整体后移一位;
最后将找到的奇数放入begin位置,然后begin++。

代码2:

class Solution {
public:
    void reOrderArray(vector<int> &array) {
            int begin = 0, end, tmp;
            while(begin < array.size())
            {
                //找到第一个偶数
                while(begin < array.size() && (array[begin] & 1) == 1)
                        begin ++;
                end = begin + 1;
                //找到第一个奇数
                while(end < array.size() && (array[end] & 1) == 0)
                        end ++;
                //将[begin,…,end-1]的元素整体后移一位
                if(end < array.size())
                {
                    tmp = array[end];
                    for(int i = end - 1; i >= begin; i--)
                        array[i+1] = array[i];
                    array[begin] = tmp;
                }
                else 
                    break;
            }
    }
};

15. 链表中倒数第k个结点

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

思路:方法1,遍历两次,第一次计算总长度,第二次找length-k+1个即可。(略)
方法2,遍历一次,用两个指针,第一个指针指向第i个,第二个指针指向i+k-1个即可,当第二个指针指向最后一个时,第一个指针指向的是倒数第k个。注意考虑空指针,k为0的情况(k为无符号,无符号-1很大的)

代码:

/*
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 *p1 = pListHead, *p2 = pListHead;
        for(int i = 0; i < k-1; i++)
            if(p1->next != nullptr)
                p1 = p1->next;
           //考虑列表的长度小于k值
            else
                return nullptr;
        while(p1->next != nullptr)
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p2;
    }
};

16. 反转链表

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

思路:遍历时,新链表newhead指向当前遍历结点,newhead->next指向上一个遍历的结点。为了防止断链,需要加临时变量存储当前的next。

代码:

/*
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;
        ListNode *p = nullptr, *pNode = pHead, *r = nullptr;
        while(pNode != nullptr)
        {
            ListNode *s = pNode->next;
            p = pNode;
            p->next = r;
            r = pNode;
            pNode = s;
            // pNode = pNode->next;  //不能省去第一个s赋值直接将pNode->next赋值给pNode。
        }
        return p;
    }
};

17. 合并两个排序链表

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

代码1(递归):

/*
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;
        ListNode *p = nullptr;
        if(pHead1->val <= pHead2->val)
        {
            p = pHead1;
            p->next = Merge(pHead1->next, pHead2);
        }
        else
        {
            p = pHead2;
            p->next = Merge(pHead1, pHead2->next);
        }
        return p;
    }
};

代码2(非递归):

/*
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;
        ListNode *p = nullptr, *r = nullptr;
        while(pHead1 != nullptr && pHead2 != nullptr)
        {
            if(pHead1->val <= pHead2->val)
            {
                if(p == nullptr)  p = r = pHead1;
                else{
                    r->next = pHead1;//有疑问???
                    r = r->next;
                }
                pHead1 = pHead1->next;
            }
            else
            {
                if(p == nullptr)  p = r = pHead2;
                else{
                    r->next = pHead2;
                    r = r->next;
                }
                pHead2 = pHead2->next;
            }
         }
        if(pHead1 != nullptr) r->next = pHead1;
        if(pHead2 != nullptr) r->next = pHead2;
        return p;
    }
};

18. 树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
具体实现如下图所示:
在这里插入图片描述

/*
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;
        if(pRoot1->val == pRoot2->val && check(pRoot1, pRoot2))
            return true;
        return HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);//如果根结点不匹配,则判断是否与左右子树匹配
    }
    bool check(TreeNode *pRoot1, TreeNode *pRoot2)
    {
        if(pRoot2 == nullptr) return true;// 说明root2遍历完了,返回true
        if(pRoot1 == nullptr) return false;
        if(pRoot1->val == pRoot2->val) return check(pRoot1->left, pRoot2->left) && check(pRoot1->right, pRoot2->right);
        else   return false;
    }
};

19. 二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
二叉树的镜像定义:源二叉树
8
/
6 10
/ \ /
5 7 9 11
镜像二叉树
8
/
10 6
/ \ /
11 9 7 5
代码:

/*
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;
        tmp = pRoot->right;
        pRoot->right = pRoot->left;
        pRoot->left = tmp;
    }
};

20. 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

思路:
把矩阵看成由若干个顺时针方向的圈组成,循环打印矩阵中的每个圈,每次循环打印一个圈。打印一圈通常分为四步,第一步从左到右打印一行;第二步从上到下打印一列;第三步从右到左打印一行;第四步从下到上打印一列。设置四个变量left,right,top,below,用于表示圈的方位,每一步根据起始坐标和终止坐标循环打印。

注意:最后一圈有可能不需要四步,有可能只有一行,只有一列,只有一个数字,因此我们要仔细分析打印每一步的前提条件:

打印第一步,第一步总是需要的。
在这里插入图片描述
打印第二步的前提条件是(top<below)
在这里插入图片描述
打印第三步的前提条件是(top<below && left<right)
在这里插入图片描述

打印第四步的前提条件是(top+1<below&&left<right)
在这里插入图片描述`

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> res;
        if(matrix.empty()) return res;
        int row = matrix.size();
        int col = matrix[0].size();
        int top = 0, below = row - 1;
        int left = 0, right = col -1;
        while(left <= right && top <= below)
        {
            for(int i=left; i<=right; ++i)
                res.push_back(matrix[top][i]);
            if(top < below)
                for(int i=top+1; i<=below; ++i)
                    res.push_back(matrix[i][right]);
            if(top < below && left < right)
                for(int i=right-1; i>=left; --i)
                    res.push_back(matrix[below][i]);
            if(top + 1 < below && left <right)
                for(int i=below-1; i>=top + 1; --i)
                    res.push_back(matrix[i][left]);
            ++ left; -- right; ++ top; --below;
        }
        return res;
    }
};
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!