美文网首页
剑指offer刷题记录(C++版本)(之二)

剑指offer刷题记录(C++版本)(之二)

作者: 傑jay | 来源:发表于2019-08-27 22:22 被阅读0次

11.二进制中1的个数

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

  • 思路如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

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

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

12.数值的整数次方

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


    主要弄清楚不同情况下的整数次方的分类问题,符号变换情况,另外是0值情况。
    将幂拆分成一半计算,主要使用快速幂方法,降幂计算从而降低时间复杂度
class Solution {
    double power(double base, int exp) {
        if (exp == 1) return base;
        if ((exp & 1) == 0) { //判断exp奇偶只需要看最后一位是否为1
            int tmp = power(base, exp >> 1);//右移一位求半幂,采用快速幂算法
            return tmp * tmp;
        } else {
            int tmp = power(base, (exp - 1) >> 1);
            return tmp * tmp * base;
        }
    }
public:
    double Power(double base, int exp) {
        if (base == 0)
         {
            if (exp > 0) return 0;
            else if (exp == 0) return 0;
            else throw invalid_argument("Invalid input!");//0的负数幂无意义
          } 
    else {
            if (exp > 0) return power(base, exp);
            else if (exp == 0) return 1;
            else return 1 / power(base, -exp);
          }
    }
};

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

  • 题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
  • 思路:本体是思路可以说非常多,冒泡法排序,遍历重组等等。。
    冒泡法,从后往前移动,每次循环都把所有奇数前移一次,偶数后移一次
void reOrderArray(vector<int> &array) {
 
         
        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) //前偶后奇交换
                {
                    swap(array[j], array[j-1]);
                }
            }
        }
    }

另有一种方法,遍历数组,奇数存一边,偶数存一边。因此时间复杂度更低,但需要占用更多空间

void reOrderArray(vector<int> &array) {
 
        vector<int> array_temp;
        vector<int>::iterator ib1, ie1;
        ib1 = array.begin();
 
 
        for (; ib1 != array.end();){            //遇见偶数,就保存到新数组,同时从原数组中删除
            if (*ib1 % 2 == 0) {
                array_temp.push_back(*ib1);
                ib1 = array.erase(ib1);
            }
            else{
                ib1++;
            }
 
        }
        vector<int>::iterator ib2, ie2;
        ib2 = array_temp.begin();
        ie2 = array_temp.end();
 
        for (; ib2 != ie2; ib2++)             //将新数组的数添加到老数组
        {
            array.push_back(*ib2);
        }
    }

14.链表倒数第k个节点

  • 题目:输入一个链表,输出该链表中倒数第k个结点。
  • 思路:该题的考点在于,链表的长度如果不从头到尾读取一遍是未知的,因此关键在于哪一个是倒数第K个节点。因此采用双指针的方式,一个指向第1个节点,一个指向第k-1个节点,同时后移,后一个指针的地址取到NULL时,即链表结束,前一个指针指向的即倒数第K个节点。
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        ListNode *p, *q;
        p = q = pListHead;
        int i = 0;
        for (; p != NULL; i++) {
            if (i >= k)
                q = q->next;
            p = p->next;
        }
        if (i < k)
            return NULL;
        else
            return q;
    }
};

15.翻转链表

  • 题目:输入一个链表,反转链表后,输出新链表的表头。
  • 思路:主要的思想是用两个指针,其中newHead指向的是反转成功的链表的头部,currentHead指向的是还没有反转的链表的头部:

    初始状态是newHead指向null,currentHead指向的是第一个元素,一直往后遍历直到newHead指向最后一个元素为止:

    下面展示的是其中某个时间点的指向细节:

核心在于理解,反转链表反转的只是地址的指向改变

public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *newHead = NULL;
        ListNode *currentHead = pHead;
        if(pHead == NULL || pHead->next == NULL){
            return pHead;
        }
 
        while(currentHead != NULL){
            ListNode *next = currentHead->next;
            currentHead->next = newHead;
            newHead = currentHead;
            currentHead = next;
        }
 
        return newHead;
    }

16.合并两个排序链表

  • 题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
  • 思路:1. 递归实现:两个当前数值比较,小的输出,然后后移一位继续更另一个数值比较,小的数值输出,如此循环
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == NULL){
           return pHead2;
       }
       if(pHead2 == NULL){
           return pHead1;
       }
       if(pHead1->val <= pHead2->val){
           pHead1->next = Merge(pHead1->next, pHead2);
           return pHead1;
       }else{
           pHead2->next = Merge(pHead1, pHead2->next);
           return pHead2;
       }   
    }
  1. 非递归原理一样,只不过看起来更复杂
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
        if(!pHead1)
            return pHead2;
        if(!pHead2)
            return pHead1;
        ListNode* Head;
        ListNode* p;
        //取较小值作头结点
        if(pHead1->val<=pHead2->val){
            Head=pHead1;
            pHead1=pHead1->next;
        }
        else{
            Head=pHead2;
            pHead2=pHead2->next;
        }  
        //开始遍历合并
        p=Head;                                                   //p为合并后的链表的工作指针
        while(pHead1&&pHead2){                       //当有一个链表到结尾时,循环结束
            if(pHead1->val<=pHead2->val){          //如果链表1的结点小于链表2的结点
                p->next=pHead1;                            //取这个结点加入合并链表
                pHead1=pHead1->next;                 //链表1后移一位
                p=p->next;                                      //工作指针后移一位
            }               
            else{                                               //否则取链表2的结点
                p->next=pHead2;
                pHead2=pHead2->next;
                p=p->next;
            }                
        }
        if(pHead1 == NULL)           //链表1遍历完了
            p->next = pHead2;         //如果链表2也遍历完了,则pHead2=NULL
        if(pHead2 == NULL)            //链表2遍历完了
            p->next = pHead1;         ///如果链表1也遍历完了,则pHead1=NULL
        return Head;
    }

17.树的子结构

  • 题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。
  • 思路:主要还是使用递归,用子函数判断两棵树是否相等,然后主函数递归判断
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool result=false;
        if(pRoot1!=NULL&&pRoot2!=NULL)
        {
            if(pRoot1->val==pRoot2->val)  //如果两个相等,则进行匹配
                result=DoseTree1HaveTree2(pRoot1,pRoot2);
            if(!result)
                result=HasSubtree(pRoot1->left,pRoot2);
            if(!result)
                result=HasSubtree(pRoot1->right,pRoot2);
            //如果不等的话则分别递归查找其左边和右边
        }
        return result;   //如果没找到这里还是false
    }
    
    bool DoseTree1HaveTree2(TreeNode* pRoot1,TreeNode* pRoot2)
    {
        if(pRoot2==NULL)    //如果已经比遍历到pRoot2位空,那么说明是子树
            return true;
        if(pRoot1==NULL)     //如果pRoot1为空pRoot2不为空(如果为空已经返回),则说明不是子树
            return false;
        if(pRoot1->val!=pRoot2->val)  //如果存在两个值不相等,那么肯定也不是子树
            return false;
        return DoseTree1HaveTree2(pRoot1->left,pRoot2->left)&&
            DoseTree1HaveTree2(pRoot1->right,pRoot2->right);
        //剩下的就是递归得判断左右是否相等了。
    }
};

18.二叉树的镜像

  • 题目:操作给定的二叉树,将其变换为源二叉树的镜像
  • 思路:递归分别交换左右子树
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
//终止条件就是当前的节点为空
        if(pRoot==NULL)   return;
        
        //交换节点
        TreeNode *tmp=new TreeNode(0);
        tmp->left=pRoot->left;
        tmp->right=pRoot->right;
        pRoot->left=tmp->right;
        pRoot->right=tmp->left;
        delete tmp;
        
        //判断左右分别是为空,不为空的话递归的来求镜像
        if(pRoot->left)
            Mirror(pRoot->left);
        if(pRoot->right)
            Mirror(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.
  • 思路:通过循环一圈一圈拨开矩阵,条件的设置是重中之重,本体的要运行正确需要花很大气力
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        int row = matrix.size();
        int col = matrix[0].size();
        vector<int> res;
         
        // 输入的二维数组非法,返回空的数组
        if (row == 0 || col == 0)  return res;
         
        // 定义四个关键变量,表示左上和右下的打印范围
        int left = 0, top = 0, right = col - 1, bottom = row - 1;
        while (left <= right && top <= bottom)
        {
            // left to right
            for (int i = left; i <= right; ++i)  res.push_back(matrix[top][i]);
            // top to bottom
            for (int i = top + 1; i <= bottom; ++i)  res.push_back(matrix[i][right]);
            // right to left
            if (top != bottom)
            for (int i = right - 1; i >= left; --i)  res.push_back(matrix[bottom][i]);
            // bottom to top
            if (left != right)
            for (int i = bottom - 1; i > top; --i)  res.push_back(matrix[i][left]);
            left++,top++,right--,bottom--;
        }
        return res;
    }
};

20.包含min函数的栈

  • 题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
  • 思路
    看到这个问题, 我们最开始可能会想, 添加一个成员变量用于保存最小元素, 每次压栈时如果压栈元素比当前最小元素更小, 就更新最小元素.
    但是这样会有一个问题, 如果最小元素被弹出了呢, 如何获得下一个最小元素呢? 分析到这里可以发现, 仅仅添加一个成员变量存放最小元素是不够的, 我们需要在最小元素弹出后还能得到次小元素, 次小的弹出后, 还要能得到次次小的.
    因此, 用另一个栈来保存这些元素是再合适不过的了. 我们叫它最小元素栈.
    每次压栈操作时, 如果压栈元素比当前最小元素更小, 就把这个元素压入最小元素栈, 原本的最小元素就成了次小元素. 同理, 弹栈时, 如果弹出的元素和最小元素栈的栈顶元素相等, 就把最小元素的栈顶弹出.
class Solution {
public:
    void push(int value) {
        s.push(value);
        if(sMin.empty())
            sMin.push(value);
        else if(value <= sMin.top())//进栈时比之前最小元素还小,则同时进最小值栈
            sMin.push(value);
    }
    void pop() {
        if(s.top() == sMin.top())//出栈时位当前最小元素,则最小值栈同时出
            sMin.pop();
        s.pop();
    }
    int top() {
        return s.top();
    }
    int min() {
       return sMin.top();
    }
private://定义两个栈,一个实现正常栈的功能,一个用了存最小值栈
    stack<int> s;
    stack<int> sMin;
};

相关文章

  • 剑指offer刷题记录(C++版本)(之二)

    11.二进制中1的个数 题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 思路:如果一个整数...

  • 剑指offer刷题记录(C++版本)(之一)

    剑指offer刷题记(C++版本) 部分参考上文和牛客网讨论 为了在秋招的手撕代码环节中不出纰漏,把剑指offer...

  • 全网最全剑指offer题目解答

    【剑指offer】Java版代码(完整版) 【剑指offer】1-10题 【剑指offer】11-20题 【剑指o...

  • 剑指offer刷题记(C++版本)

    也算是临时抱佛脚了吧,3月之前刷了lintcode100多道题吧,后来发文章什么的就放下了,最近秋招在即在牛客网上...

  • 剑指offer-Python版(上)

    剑指offer上面的66道算法题是面试高频题,书中用C/C++写的答案,本篇笔记用python刷一遍所有的算法题,...

  • 一点感想

    刷剑指offer之前刷过一百来道leetcode,不过都是刷的简单的题。剑指offer现在也刷了快一半了,觉得二叉...

  • 剑指offer刷题......

    学习 1.二维数组中的查找 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排...

  • 剑指Offer刷题

    刷一下算法题吧。https://foreti.me/2017/09/08/jianzhi-offer/ 替换空格 ...

  • 算法 | 一周刷完《剑指Offer》 Day6:第61~66题

    写在前面 本系列包含《剑指Offer》66道算法题,一周刷完,这是完结篇,撒花!系列汇总:剑指Offer 66题 ...

  • 剑指offer刷题记录(C++版本)(之五)

    41.和为S的连续正数序列 题目:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正...

网友评论

      本文标题:剑指offer刷题记录(C++版本)(之二)

      本文链接:https://www.haomeiwen.com/subject/scpjectx.html