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

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;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容