剑指offer刷题记录

剑指offer刷题记录

内容描述

  本仓库链接:https://github.com/guyongpu/SwordOffer.git
  本仓库为牛客网剑指offer题目的刷题记录,网址为:https://www.nowcoder.com/ta/coding-interviews ,点击跳转到剑指offer
每个题的代码放在src中,在main.cpp引入对应类的头文件,然后运行测试函数即可,例如需要运行题1时,对main.cpp写成如下形式即可:

#include 
#include "src/P01_2d_array_find.h"

using namespace std;

int main() {
    P01_2d_array_find G;
    G.test();
    return 0;
}

  然后点击运行即可。

刷题日志

题号
题目名
完成日期
描述
备注
P01 二维数组的查找 2019.9.10 问题:在一个递增的二维数组中查找一个数是否存在。
思路:从左上角或者右上角开始查找
思考递减数组
P02 替换空格 2019.9.10 问题:将空格替换为%20。
思路:从后往前替换,避免移动,时间复杂度O(n)
小换大,从后往前
大换小,从前往后
P03 从尾到头打印链表 2019.9.10 问题:将链表从尾到头存放到vector中
思路:顺序遍历,然后对vector进行反转
不建议反转链表,会改变链表结构
P04 重建二叉树 2019.9.10 问题:根据前序和中序递归构建二叉树
思路:递归构建二叉树
复习时要回顾这个题
P05 用两个栈实现队列 2019.9.11 问题:用两个栈实现队列的push和pop操作
思路:push时,直接存入stack1;pop时,若stack2不为空,则直接弹出栈顶元素,否则,将stack1放入stack2,再弹出栈顶元素。
P06 旋转数组的最小数字 2019.9.11 问题:数组旋转一次后,找出数组的最小值
思路:用双指针的二分查找方法
不要顺序遍历
P07 斐波那契数列 2019.9.11 问题:输出斐波那契数列的第n项
思路:递归计算时间空间复杂度过大,用非递归做,F(n)=F(n-1)+F(n-2)
用非递归方法
P08 跳台阶 2019.9.11 问题:计算跳上number级台阶的跳法数量
思路:递归计算时间空间复杂度过大,用非递归做,F(n)=F(n-1)+F(n-2)
用非递归方法
P09 变态跳台阶 2019.9.11 问题:计算跳上number级台阶的跳法数量,每次可以选择跳1-n级
思路:利用数学归纳法推导,F(n)=2*F(n-1)
用非递归方法
P10 矩形覆盖 2019.9.11 问题:用2*1的矩形去覆盖2*number的大矩形,计算覆盖的方法数
思路:这个题也是斐波拉契数列,F(n)=F(n-1)+F(n-2)
用非递归方法
P11 二进制中1的个数 2019.9.11 问题:给定一个整数,计算其二进制中1的个数
思路:介绍了3种方法,关键是第2和第3种。方法1,采用每次右移n,提前处理负数;方法2采用每次将flag左移;方法3采用n=n&(n-1)的方式计算
掌握3种方法
P12 数值的整数次方 2019.9.12 问题:不用库函数,计算base的exponent次方
思路:考虑两个点,一是int的绝对值算法,考虑边界值,使用unsigned int处理;二是0的处理,返回0
绝对值的处理
P13 调整数组顺序使奇数位于偶数前面 2019.9.12 问题:将一个数组的奇数放在前面,偶数放在后面,奇数之间、偶数之间的相对位置不变
思路:可以采用双指针平移的做法,排序的做法,用两个vector组合做法都可以
如果只要求奇数前,偶数后,则用双指针做法
P14 链表中倒数第k个结点 2019.9.13 问题:输出该链表中倒数第k个结点
思路:利用双指针思想,指针1先走k步,之后指针2与指针1同步移动,直到链表结尾
考虑0,空链表,倒数第x个,倒数第1个,倒数第length个等情况
P15 反转链表 2019.9.13 描述:将链表进行反转后,然后输出新链表的表头
思路:使用三个指针,分别指向前,中,后三个位置,中表示正在处理的节点
常见题,注意复习
P16 合并两个排序的链表 2019.9.13 描述:将两个有序链表合并成一个有序链表
思路:使用一个指针,先构建头结点,然后再逐个构建起一条链表,类似于新建链表的过程
常见题,注意多复习
用非递归实现的,而书上使用递归,但本质是一样的
P17 树的子结构 2019.9.13 描述:第一步,先在树A中找到一个和树B根节点的值相等的节点R;第二步,判断树A中以R为根节点的子树是不是包含树B一样的结构。 HasSubtree,找到一个相等的节点;DoesTree1HaveTree2,开始计算两个树是否包含。 常见题,注意复习
P18 镜像二叉树 2019.9.13 描述:给定一个的二叉树,将其变换为原二叉树的镜像
思路:将树的左右子树进行交换,然后再分别对左右子树进行镜像处理。
常见题,注意复习
P19 顺时针打印矩阵 2019.9.13 描述:将一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字
思路:第一步:先打印整圈的,圈数是行列的最小值minNum/2;第二步,如果minNum是奇数,说明有单独的行/列,当行>列,则有列,从上到下打印,当列>=行,则有行,从左到右打印。
1.圈数是行列的最小值minNum/2;
2.minNum是奇数则打印单独行/列。
P20 包含min函数的栈 2019.9.14 描述:实现一个能够得到栈中所含最小元素的min函数
思路:本题目需要使用两个栈,数据栈和辅助栈。辅助栈只在压栈元素比栈顶元素小或等于才入栈,出栈则与辅助栈顶元素相等才出栈
入栈时与辅助栈栈顶相等也要入栈
P21 栈的压入、弹出序列 2019.9.14 描述:判断一个序列是否可能为一个栈的弹出顺序
思路:遍历pushV[i],如果遇到popV[cnt]相等,先压栈,然后popV[cnt]与栈中逐个比较,否则直接将pushV[i]压入栈中当cnt与popV的size相等时,则说明匹配成功,否则匹配失败。
比较阶段,结束判断条件
P22 从上往下打印二叉树 2019.9.14 描述:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路:本题书上的方法是采用先进先出队列,每遍历一个节点,则把该节点的左右节点放到队尾,知道队为空,则结束。本人做法原理是一样的,只是用的vector实现,vectree存放本轮遍历的节点,一轮完成后,清空vectree,再把其子节点加入到vectree中,然后进行下一轮遍历,直到vectree的size为0,则说明所有节点已遍历。
二叉树的层序遍历,用队列更好
P23 二叉搜索树的后序遍历序列 2019.9.14 描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。
思路:后序遍历的最后一个元素是根,则序列必须可以分为左右两部分,左部分root,不满足则返回false,满足则继续进行递归。递归结束条件:每一个sequence只有一个元素,则返回true.
常见题,注意多复习
输入空树返回false.
P24 二叉树中和为某一值的路径 2019.9.14 描述:打印出二叉树中结点值的和为输入整数的所有路径,终点为叶子节点,按长度降序
思路:按左右子树进行递归,当和相等且为叶子节点,则说明找到一条路径Path,然后将Path存入result中,如果不符合要求,则先将当前节点加入Path中,然后分别对左、右子树递归。
题目不难,但要复习
P25 复杂链表的复制 2019.9.14 描述:输入一个复杂链表,返回结果为复制后复杂链表的head
思路:本题分为3步。1.复制节点,并插入到原链表中,如A->A’->B->B’->C->C’;2.设置random指针的值;3.拆分原链表和新链表。
思路比较巧妙,注意不能破坏旧链表结构
P26 二叉搜索树与双向链表 2019.9.14 描述:将二叉搜索树转换成一个排序的双向链表
思路:二叉搜索树进行中序遍历恰好是排序的结果,借用一个指向当前链表最后一个节点的指针来构建链表
中序遍历思想,第一次不会做,要复习
P27 字符串排列 2019.9.15 描述:输入一个字符串,按字典序打印出该字符串中字符的所有排列。
思路:将字符串分为两部分,第一个字符、其后面的所有字符。然后用第一个字符和后面的所有字符逐个交换。
注意复习
P28 数组中出现次数超过一半的数字 2019.9.15 描述:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字,不存在则输出0.
思路:采用阵地攻守的思想。第一个数字作为第一个士兵,守阵地;count = 1;遇到相同元素,count++; 遇到不相同元素,即为敌人,同归于尽,count–;当遇到count为0的情况,又以新的i值作为守阵地的士兵,遍历到最后还留在阵地上的士兵,有可能是主元素。再加一次循环,记录这个士兵的个数看是否大于数组一半即可。
常见题,要复习
P29 最小的K个数 2019.9.15 描述:输入n个整数,找出其中最小的K个数
思路:利用大顶堆做即可,思路比较简单,先构造一个K大小的大顶堆,然后逐个对根比较,如果input[i]比根小,则替换根,重新构建,主要涉及堆的make、pop、push和sort等操作,O(n*logK)
常见题,要复习
P30 连续子数组的最大和 2019.9.16 描述:计算一个数组的最大连续子序列的和
思路:常规解法,逐个累加,当tmp_sum<0,则替换掉,每次与sum比较大小
常规解法
动态规划
P31 整数中1出现的次数 2019.9.16 描述:从1到整数N中1出现的次数
思路:逐位思考,可以计算1~9出现的次数,修改x即可,0除外
注意复习
P32 把数组排成最小的数 2019.9.16 描述:输入一个正整数数组,把数组里所有数字拼接起来排成一个最小的数
思路:主要运用到一个字符串排序,当str_ab< str_ba时,a在前,否则b在前
注意复习
P33 丑数 2019.9.16 描述:把只包含质因子2、3和5的数称作丑数,找第N个丑数。
思路:维护三个指针,每次找三个指针对应的乘积的最小值加入队列,同时进行移动指针。
常规解法和优化解法
P34 第一个只出现一次的字符 2016.9.16 描述:在一个字符串中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回-1
思路:可以采用构造hash表的思想,即构造pos[52],int cnt[52],遍历两次即可求解。也可以采用map思想,直接strmap,做法也比较简单
hash表
map方法
P35 数组中的逆序对 2019.9.22 描述:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P
思路:归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i。
常见题,要复习
P36 两个链表的第一个公共结点 2019.9.23 描述:输入两个链表,找出它们的第一个公共结点
思路:由于链表是单向,所以链表交点之后的结点都相同,则可以让长的先走x步,然后再一起走,直到最后相遇。或者通过将两个链表压入两个栈中,然后逐个出栈,开始时都相等,直到遇到第一个不相等的结点。
题目比较简单
P37 数字在排序数组中出现的次数 2019.9.23 描述:统计一个数字在排序数组中出现的次数。
思路:用2次二分查找,第一次查找K的位置,第二次使用二分查找确定K的起点和终点。或者,在确定好K的位置后,用双指针前后移动,统计数目。
data为空判断+降序反转
P38 二叉树的深度 2019.9.23 描述:输入一棵二叉树,求该树的深度。
思路:使用层序遍历思想,1.队列实现;2.vector实现;3.递归实现
重要题目。还可以改为输出最长的路径结果,结合P24做即可
P39 平衡二叉树 2019.9.23 描述:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
思路:法1:利用后序遍历思想,从下往上遍历,某一结点的深度等于其到叶子结点的路径的长度。法2:O(n^2),对每个结点调用求深度的函数,然后判断。
重要题目
P40 数组中只出现一次的数字 2019.9.24 描述:一个整型数组里除了两个数字之外,其他的数字都出现了两次
思路:采用异或的思想,将所有数异或,得到不等于0的数,然后根据1的位置分为两组异或,然后在两组中获得num1和num2.
如果只有一个数出现一次,则直接异或即可得到结果。
P41 和为S的连续正数序列 2019.9.24 描述:输出和为S的连续正数序列,要求序列内部升序,序列间按起始数字升序.
思路:采用双指针,small=1,big=2,然后逐步将指针往右移,约束条件:small <= sum/2.
记住方法,另外起始不能为0
P42 和为S的两个数字 2019.9.24 描述:在数组中查找两个数,使得他们的和正好是S,要求乘积最小.
思路:从两端开始找,采用双指针做法,一旦找到,就是乘积最小的.
两个数和为S时,两个数越接近乘积越大。要考虑找不到的情况.
P43 左旋转字符串 2019.9.24 描述:将一个字符串循环左移n位
思路:法1:采用旋转方式实现 YX = (X^T + YT)T ,通过三次reverse实现;法2:采用普通的substr截取字符串处理.
reverse第二个参数是下一个元素的地址
P44 翻转单词顺序列 2019.9.24 描述:将一个句子的单词顺序进行翻转,单词内部顺序不变
思路:利用YX = (X^T + YT)T的思想,先对每个单词翻转,然后再对整个单词翻转.
记住YX = (X^T + YT)T方法
P45 扑克牌顺子 2019.9.24 描述:给5张牌,判断是否能组成顺子
思路:1.排序;2.统计0的数量;3.遍历非0数字,判断0的数目是否够补位,如果不够输出false,要考虑到对子.
要仔细.
P46 孩子们的游戏(圆中最后剩下的数) 2019.9.24 描述:一个n大小的约瑟夫环,每次去掉一个数,知道只剩最后一个数即为结果
思路:标准结果和常规解法
掌握两种方法
P47 求1+2+3+…+n 2019.9.24 描述:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)
思路:利用与运算的特点,(n>0) && (sum = n + Sum_Solution(n - 1))进行递归运算
构造函数法也可求解
P48 不用加减乘除做加法 2019.9.24 描述:求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号
思路:对每一位进行讨论计算,使用flag诸位左移,设置sum的位.也可以采用标准解法.
数据以补码存储
P49 把字符串转换成整数 2019.9.25 描述:将一个字符串转换成一个整数的功能,当string不符合数字要求时返回0.
思路:正常思路,注意正负号,非法字符,标记位,空字符串,整数上下溢出.
注意细节.
P50 数组重复的数字 2019.9.26 描述:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,找出一个重复的数字.
思路:逐个遍历,如果numbers[i]!=i,判断numbers[i]与numbers[numbers[i]]是否相等,相等则说明是重复元素,否则numbers[i]与numbers[numbers[i]]交换位置.
n个数字,每个数组0~n-1之间,无此条件会越界,另外检查合法性.
P51 构建乘积数组 2019.9.26 描述:给定数组A[0,1,…,n-1],构建数组B[i]=A[0]*A[1]*…*A[i-1]*A[i+1]*…*A[n-1],不使用除法.
思路:如果直接计算时间复杂度为O(n^2),考虑将B[i]=array1[i]*array2[i],其中array1[i]=A[0]*…*A[i-1];而array2[i] = A[i+1]*…*A[len-1]
如果可以使用除法,则B[i]=II(j=0->n-1)A[i] / A[i],时间复杂度为O(n).
P52 正则表达式匹配 2019.9.26 描述:实现一个函数用来匹配包括’.‘和’*'的正则表达式.
思路:第1步,先处理*,若能匹配,则三种情况:(1)跳过x*,即matchCore(str,pattern+2);(2)匹配x*,即matchCore(str+1,pattern+2);(3)匹配下一个str,即matchCore(str+1,pattern);若不能匹配,则忽略跳过x*。第2步,然后处理正常字符的情况。
常见题
P53 表示数值的字符串 2019.9.26 描述:请实现一个函数用来判断字符串是否表示数值(包括整数和小数).
思路:直接判断,以E为界限,分为E的左边和E的右边,E的左边为正常数值,E的右边为整数.
仔细判断,也可考虑用正则表达式匹配.
P54 字符流中第一个不重复的字符 2019.9.26 描述:请实现一个函数用来找出字符流中第一个只出现一次的字符.
思路:使用 HashTable[256]大小的哈希表,初始值设置为-1,之后值为第一次出现的index,重复出现时设置为-2,然后输出当前HashTable[256]中最小的非负数对应的字符.
字符表考虑256大小的哈希表
P55 链表中环的入口结点 2019.9.26 描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null.
思路:第1步,判断链表是否成环,快慢指针相遇;第2步,pFast先走step步,然后再一起走,首次相遇就是入口.
使用链表注意细节
P56 删除链表中重复的结点 2019.9.27 描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针
思路:1.处理头结点 2.处理其他结点。使用双指针做法,pPreNode 指向不重复的结点,检测pNode和pNode->next是否重复,如重复,找到 pNext,使 pPreNode指向pNext.
保留和不保留重复节点两个版本,考虑全/不/头/尾/部分重复,单/空链表.
P57 二叉树的下一个结点 2019.9.27 描述:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回.
思路:分3种情况。1.pNode是父结点的左结点,则直接返回父结点;2.pNode是父结点的右节点,则一直向上找,直到pFFNode->left = pFNode,返回pFFNode;3.如果有右子树,则一直找最左结点.
考虑父结点为空.
P58 对称的二叉树 2019.9.27 描述:请实现一个函数,用来判断一颗二叉树是不是对称的.
思路:二叉树左右节点递归比较,左节点与右节点比较,同为空或者val值相等。
注意复习
P59 按之字形顺序打印二叉树 2019.9.27 描述:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,其他行以此类推。
思路:这是二叉树层序遍历的变式题,只需在打印是设置order控制打印顺序即可,其他和层序遍历是一样的。
复习层序遍历
P60 把二叉树打印成多行 2019.9.27 描述:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路:二叉树的层序遍历
掌握二叉树遍历方法
P61 序列号二叉树 2019.9.28 描述:实现二叉树的序列化,并实现二叉树的反序列化.
思路:序列化采用先序遍历完成,反序列化遍历字符串,逐步重建树.
此题比较麻烦,记得复习
P62 二叉搜索树的第k个结点 2019.9.28 描述:给定一棵二叉搜索树,请找出其中的第k小的结点.
思路:采用中序遍历做,其次,k要使用引用,否则k不会发生变化,当k等于0返回;当k小于0时停止递归.
备注:注意复习
P63 数据流中的中位数 2019.9.28 描述:输入一串数字流,得到其中的中位数.
思路:采用最大最小堆做效率最高,插入复杂度O(logn),获取中位数O(1)
也可以采用插入排序思想,或者快排Partition,排序链表,ALV树
P64 滑动窗口的最大值 2019.9.28 描述:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值.
思路:采用双端队列来做,队首始终是当前窗口的最大值.
学习该方法
P65 矩阵中的路径 2019.9.28 描述:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.
思路:测试所有可能的起点,然后使用递归回溯算法,visited用于标记是否访问,在匹配失败是进行回退.
在匹配失败要进行回退
P66 机器人的运动范围 2019.9.28 描述:一个m行n列的方格矩阵,机器人从(0,0)开始走,不能进入行坐标和列坐标的数位之和大于k的格子,机器人最多可到达多少个格子.
思路:使用visied做标记,访问过为true,未访问过为false,然后使用递归回溯计算.
递归回溯典型例题
P67 剪绳子 2019.9.28 描述:一根长度为n的绳子,请把绳子剪成m段,记为k[0]、k[1]、…k[m],请问k[0]xk[1]x…xk[m]可能的最大乘积.
思路:采用动态规划或者贪心算法均可求解.
两种方法都需要掌握

刷题总结

  本次是集中式的刷题,自2019.9.10早开始,于2019.9.28晚完成,其中9.17-9.21忙于着手实现C++服务器和搭建个人主页及博客,因此该段时间没有刷题。
  以前只听说这本书,一直没完整做一遍,这次一次性刷完后,感觉里面的题目还是有很多经典题目,比如树的遍历、镜像树、对称树、双指针、链表成环、字符统计、partition的使用等等,而且大多数是面试中常见的问题,因此还是需要复习的。
  接下来就是开始刷LeetCode了,毕竟是和剑指offer齐名的编程题集,不过不会像这次这么全天集中式地刷了,一边学习专业知识一边刷题。

于2019年9月28日21:56:00 记

你可能感兴趣的:(C/C++)