剑指offer小结

剑指offer

01AssignmentOperator赋值运算符

  1. 返回值类型为该类型的引用,以允许连续赋值
  2. 传入的参数类型为常量引用,避免调用复制构造函数造成无谓消耗
  3. 释放实例自身已有的内存
  4. 判断是否是同一个实例
  5. 考虑异常安全性

02Singleton实现单例模式

  1. 私有构造函数
  2. 懒汉式:静态成员变量,类外初始化,静态方法实例化对象,并返回
  3. 饿汉式:静态局部变量在第一次使用时初始化,并不会销毁直到程序退出

03FindInSortedMatrix有序二维数组中查找

  1. 从数组的角上(例如右上角)开始查找,直接定位或剔除一列或一行

04ReplaceSpaces替换空格

  1. 先遍历一次获取空格数,由此计算出替换后的数组总长
  2. 从数组尾部开始向前复制和替换

05PrintListReversed从尾到头打印列表

  1. 利用“后进先出”的栈
  2. 递归:每次访问一个节点时先打印其后面的节点,再打印自身

06ConstructBinaryTrees重建二叉树

  1. 二叉树的前序遍历中,第一个节点总是树的根节点
  2. 中序遍历中,左树在根节点的左边,右树在右边
  3. 利用前序遍历定位根节点,在中序遍历中找到根节点,获取左右子树的长度,递归处理左子树和右子树

07QueueWithTwoStacks用两个栈实现队列

  1. 取出元素时,如果stack2为空,先将stack1中元素都压入stack2,再从stack2顶部取出;如果stack2不为空,直接从stack2顶部取出

08MinNumberInRArray旋转数组的最小数字

  1. 理解旋转数组,把递增排序数组前面若干个数搬到尾部
  2. 由于数组一定程度是排序的,使用二分查找法:判断中间位置元素是处于前半递增数组还是后半递增数组,更新两端节点的位置
  3. 考虑数组中元素相等的情况,只能用顺序查找

09Fibonacci斐波那契数列

  1. 递归是逆序计算f(n)=f(n-1)+f(n-2),重复计算多,效率太低
  2. 利用for循环,正序计算到第n个数
  3. 跳n级台阶的跳法问题也可抽象成斐波那契数列问题

10Numberof1InBinary二进制中1的个数

  1. 最右位与1做位与运算,再右移一位(比除以2效率高)
  2. 考虑输入负数,不右移输入数字,而是左移判断标志
  3. 把一个整数减1,再和原整数做位与运算,会把该整数最右边一个1变成0,左边的保持不变。有多少个1就可以做多少次这种操作,直到最后与完的结果为0.

11Power数值的整数次方

  1. 考虑输入指数为0或负数,先对指数取绝对值,在将结果求倒数(注意不能对0求倒数)
  2. 判断小数(float和double)不能用==,要判断差的绝对值小于精度,比如0.0000001
  3. 对求正整数次方进行优化,例如16次方=8次方*8次方,递归,注意奇偶

12Print1ToMaxOfNDigits打印1到最大的n位数

  1. 考虑大数问题,用字符串表示数字,模拟加法、进位和打印
  2. 递归实现全排列解决

13DeleteNodeInList在O(1)时间删除链表节点

  1. 把下一个节点的内容复制到待删除节点,删除下一个节点即可
  2. 如果待删除节点是尾部节点,则仍得从头遍历到该节点,然后删除

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

  1. 类似于快速排序法,维护两个指针,一个指向头部的偶数,一个指向尾部的奇数,交换。直到相遇。

15KthNodeFromEnd链表中倒数第k个节点

  1. 维护两个指针,第一个指针从头开始先走k-1步,然后第二个指针从头出发一起走,直到第一个指针走到链表尾,第二个指针即所求
  2. 考虑链表为空,考虑链表长度不足k,考虑k=0

16ReverseList反转链表

  1. 维护三个指针h、i、j,将i的下一个节点指向h,然后滚动后移
  2. 考虑链表为空,链表只有一个节点,返回原链表的尾节点作为反转后的头结点

17MergeSortedLists合并两个排序的链表

  1. 递归:将两个链表中值较小的头节点链接到已合并链表的尾部
  2. 当一个链表为空时,直接将另一个链表链接到合并链表的尾部

18SubstructureInTree树的子结构

  1. 递归:在A树中遍历查找B树的根节点,找到后再递归判断以该节点为根节点的子树和B树结构是否相同
  2. 注意递归的退出条件,避免访问空指针

19MirrorOfBinaryTree二叉树的镜像

  1. 递归:交换根节点下的叶子节点
  2. 递归退出条件是根节点为空或左右叶子节点均为空

20PrintMatrix顺时针打印矩阵

  1. 循环继续的条件:columns>startX*2 && rows>startY*2
  2. 分析打印每一行、每一列时的前提条件、边界条件

21MinInStack包含min函数的栈

  1. 维护一辅助栈,栈顶为最小值。数据栈每次压入数据时,与辅助栈比较,如果大于辅助栈栈顶,则继续压入一次辅助栈顶的最小值;如果小于,则压入该更小值
  2. 数据栈弹出数据时,辅助栈也一起弹出顶部数据,以保持栈顶一直是对应的最小值

22StackPushPopOrder栈的压入、弹出序列

  1. 维护一辅助栈,如果下一个弹出的数x是辅助栈栈顶,直接弹出;如果不在栈顶,尝试从压入序列中按顺序压入数据,直到压入到栈顶的数据等于x,弹出x;如果压入序列中所有的数据都压入栈了还没找到x,则弹出序列不匹配

23PrintTreeFromTopToBottom从上往下打印二叉树

  1. 利用队列实现广度优先遍历
  2. 先打印出根节点,将其子节点放入队列;从队列首取出一节点打印,将该节点的子节点(如果有)放入队列尾,重复操作

24SquenceOfBST二叉搜索树的后序遍历序列

  1. 后序遍历得到的序列中,最后一个数字是树的根节点;前面的第一部分是左子树,均小于根节点,第二部分是右子树,均大于根节点
  2. 先找到根节点,依据大小关系将序列拆成左、右子树序列,再递归处理这两个子序列

25PathInTree二叉树中和为某一值的路径

  1. 前序遍历,访问节点时先将节点加入路径,累加到路径之和
  2. 如果该节点不是叶子节点,继续访问其子节点;如果该节点是叶子节点,且路径之和与所求匹配则打印;如果是叶子节点,但路径之和不匹配,则将该节点从路径中剔除,并从路径之和中减去
  3. 路径的递归过程本质是压栈出栈,可以借用栈结构,这里用vector来实现,易于打印路径

26CopyComplexList复杂链表的复制

  1. 利用O(n)的空间消耗把时间复杂度有O(n^^2)降到O(n)
  2. 复制原始链表的任意节点N为N’,将N’插入到N节点之后
  3. 假设原始链表N的m_pSibling指向的是节点S,则N’的m_pSibling指向的是S->m_pNext
  4. 拆分链表,将奇数位置的节点用m_pNext链接起来就是原始链表;将偶数位置的节点用m_pNext链接起来就是复制出的链表;

27ConvertBinarySearchTree二义搜索树与双向链表

  1. 中序遍历,从小到大遍历二叉搜索树
  2. 递归:左子树转换成的链表的最大节点<->根节点<->右子树转换成的链表的最小节点

28StringPermutation字符串的排列

  1. 递归:将字符串分成首字符和后续字符串,再将后续字符串分为首字符和后续字符串
  2. 拿首字符循环和后续字符逐个交换,每交换一次后将后续字符串递归排列,再交换回来,继续下一次循环

29MoreThanHalfNumber数组中出现次数超过一半的数字

  1. 利用快排中的partition函数,返回随机选取的临界值下标。如果该下标刚好是n/2,则该数字为数组中位数;如果下标小于n/2,中位数在右边,更新partition函数边界,递归;如果大于n/2,中位数在左边,更新边界,递归。
  2. 遍历数组时保持两个值,一个是观察数,一个是该观察数出现次数。如果遍历到下一个数字与观察数不同,次数减1,相同则次数加1;如果次数为0,则更新观察数为下一个数字,且将次数置为1。因为待找数字超过一半,所以其余数字总数加起来都不及待找数出现的次数,最后的观察数必定是待找数字。

30KLeastNumbers最小的k个数

  1. 利用快排中的partition函数,返回随机选取的临界值下标,如果临界值下标为k,则其左边k个数即为最小的k个数
  2. 维护一个大小为k的排序容器,遍历数组时与容器中最大值max比较,max较大则更新容器,否则抛弃
  3. 用最大堆、红黑树(STL中set、multiset)等数据结构来实现排序容器。
  4. 第一种解法时间复杂度O(n),需要修改原数组,不适用于海量数据;第二种解法时间复杂度O(n*logk),不需修改原数组,适用于海量数据。

31GreatestSumOfSubarrays连续子数组的最大和

  1. 依次累加,保存累计和,如果累计和是负数,舍弃前面的累计和
  2. 如果加的是负数,保存加之前的累计和,继续累加该负数和后面的数

32NumberOf1从1到n整数中1出现的次数

  1. 循环,对10求余,是1则计数。对每个数字处理。
  2. char* strN为待求整数字符串,
    int first=*strN-'0';
    unsigned int length=static_cast<unsigned int>(strlen(strN));
    //假设strN是21345
    //numFirstDigit是最高位为1的数字个数,此处即万位为1的数
    int numFirstDigit=0;
    if(first>1)
        numFirstDigit=PowerBase10(length-1);
    else if(first==1)
        numFirstDigit=atoi(strN+1)+1;//除去最高位

    //numOtherDigits是1346-21345中除了最高位之外的包含1的数
    int numOtherDigits=first*(length-1)*PowerBase10(length-2);
    //numRecursive是1-1345中包含1的数个数
    int numRecursive=NumberOf1(strN+1);

    return numFirstDigit+numOtherDigits+numRecursive;

33SortArrayForMinNumber把数组排成最小的数

  1. 大数问题,把数字转为字符串
  2. 前后拼接两个字符串,比较mn和nm
  3. 利用上述规则+qsort库函数排序

34UglyNumber丑数

  1. 常规方法:分别判断能否被2、3、5连续整除,最后得到1
  2. 从前往后依次生成排序丑数:假设已有最大丑数为M,将已有的每个丑数分别乘以2、3、5,刚刚大于M的M2、M3、M5中最小值即为下一个丑数
  3. 实际上不用每个丑数都乘以2、3、5,对应乘2,已有丑数组中有个临界点,每次更新丑数组后更新该临界点;同理3、5也分别存在临界点

35FirstNotRepeatingChar第一个只出现一次的字符

  1. 创建一个哈希表存放每个字符出现的次数
  2. 遍历字符串两次,第一次统计次数,第二次获取第一个统计次数为1的字符
  3. 创建一个大小为256,以字符ASCII码为键值的数组作为哈希表

36InversePairs数组中的逆序对

  1. 先把数组分隔成子数组,先统计子数组内部逆序对的数目,再统计两个子数组之间的逆序对数目,统计过程中还需要对数组进行排序
  2. 先用两个指针分别指向两个子数组的末尾,每次比较两个指针指向的数字;如果第一个子数组中的数字较大,则构成逆序对,且逆序对的数目为第二个子数组中剩余数字个数;如果第二个子数组中的数字较大,不构成逆序对。
  3. 每一次比较时,将较大的数字从后往前复制到辅助数组中,然后把对应的指针向前移一位

37FirstCommonNodeInLists两个链表的第一个公共结点

  1. 公共节点之后所有节点都是重合的
  2. 先分别求得链表的长度,然后对于长的链表,先走len(长的-短的),然后再一起走同时遍历,第一个达到相同的节点值即为所求

38NumberOfK数字在排序数组中出现的次数

  1. 找首k,二分法,如果中间数等于k,判断中间数前一个数,若该数不等于k,则中间数恰好是首k,否则继续二分
  2. 找尾k,如果中间数等于k,判断中间数后一个数,若该数不等于k,则中间数恰好是尾k,否则继续二分

39TreeDepth二叉树的深度

  1. 递归:return (nleft>nright)?(nleft+1):(nright+1);
  2. 判断是否是平衡二叉树,后续遍历,遍历左右子节点后,根据左右子节点的深度判断是否平衡

40NumbersApperaOnce数组中只出现一次的数字

  1. 任何数字亦或自己都等于0,即亦或两次抵消
  2. 亦或全部数字得到结果,在结果数字的二进制中找到第一个为1的位的位置,记为第n位
  3. 以第n位是否为1为标准即可将数组分为两组,每组各含一个不同的数子

41_1TwoNumsWithSum和为s的两个数字

  1. 定义两个指针,h指首(最小),t指尾(最大)
  2. 求和,若大于s,t前移;若小于s,h后移

41_2ContinuousSequenceWithSum和为s的连续正数序列

  1. 两个指针分别表示子序列的最小值和最大值,small初始化为1,big初始化为2
  2. 如果从small到big序列的和小于s,big增大;如果序列和大于s,small增大,即去掉较小的值
  3. 因为序列至少两个数,一直增加small到(1+s)/2为止

42_1ReverseWordsInSentence翻转单词顺序

  1. 翻转两次,第一次翻转全部字符,第二次翻转每个单词中字符的顺序
  2. 翻转函数输入为字符串的首尾指针,用空格区分单词

42_2LeftRotateString左旋转字符串

  1. 先把字符串拆成两部分,分别翻转
  2. 合并字符串后翻转全部字符

参考

剑指offer-小胡

剑指Offer:名企面试官精讲典型编程题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值