大概过了一两遍剑指offer,思路基本上是复制网上大佬的思想。希望面试的时候能自己默写出来把,现在把剑指offer的题型和结题思路总结一下,便于后序复习和进阶。
JZ3 从尾到头打印链表
思路1:利用栈先入后出的特性,顺序存入,倒序打印
思路2:利用两个节点(双指针)原地反转后再打印出来
思路3:利用stl的reverse() 函数实现反转后再打印
JZ14 链表中倒数最后k个结点
思路1:可以直接暴力遍历,如果已知链表的长度,则顺序遍历的长度就已知了
思路2:利用栈,顺序存入,再pop出第k个节点
思路3:利用双指针,第1个指针先走k步后,第二个指针从头出发,此时两个指针同时走,直到第1个指针走到链表尾部,此时第二个指针走到倒数第k个节点。
JZ15 反转链表
思路同题目3
JZ16 合并两个排序的链表
思路1:新建一个表头,两个指针分别指向待合并的表头,依次比较后,加入到新表头的后面,注意因为比较是按照升序原则来的,循环结束后,记得要把最大的那个节点加上。
JZ35 复杂链表的复制
思路1:先遍历复制next关系,再遍历复制random关系。
思路2:利用hash表映射的关系,将待复制的节点和新建立的节点先存入hasp表中,再遍历该hash表,为新节点“画上next箭头和random箭头”,即所谓的建立映射关系。
思路3:待完成
JZ66 两个链表的第一个公共结点
思路1:暴力穷举,双重循环,外层循环确定链表1的节点,内存循环比较链表2的每一个节点,出现相同则返回。
思路2:凡是涉及到第一次,不重复的,我觉得可以考虑hash表,这里是先把表1按照<节点,次数>存入hash表中。再遍历表2,找出表2和表1相同的节点。
思路3:可以想到,如果两个链表有公共节点,那么该公共节点之后都全部是公共节点,即形如“>—”的形式。假设我们知道两个链表的长度,定义两个指针分别指向两个链表,若二表长度相同,则两个指针同时走,第一次到达相同节点时即为两个链表的第一个公共节点。若长度不同,那么较长的链表指针先走(长度之差)步,之后两个指针再同时走,直到指向的节点相同。
思路4:形如“>—”的形式。我们可以把两个链表分别压入两个栈中,再同时出栈,直到两个出栈的节点不同,记下这个位置。再顺序遍历链表到这个位置的下一个位置即可。
JZ55 链表中环的入口结点
思路1:快慢双指针。初始化两个指针指向表头,快指针p1每次走两步,慢指针p2每次走一步。开始走,当两个指针第一次相遇时,快指针回到原点。继续走,直到快慢指针第二次相遇即找到入口节点。画个图很好理解简单的追及问题。
JZ56 删除链表中重复的结点
思路1:重复问题,用hash表。将链表存入hash表中。新建头节点,遍历hash表,将出现次数为1的节点依次接到头节点的后面。
JZ1 二维数组的查找_c++完整版 JZ6 旋转数组的最小数字 JZ13 调整数组顺序使奇数位于偶数前面 JZ19 顺时针打印矩阵 JZ28 数组中出现次数超过一半的数字 JZ30 连续子数组的最大和 思路1:穷举出所有情况,找出最大情况。由于是连续子数组,用两层循环即可。第一层用来控制边界,第二层用来计算子数组的和。每次循环维护一个最大值即可。 JZ35 数组中的逆序对 JZ40 数组中只出现一次的两个数字 JZ41 和为S的连续正数序列 JZ42 和为S的两个数字 JZ50 数组中重复的数字 JZ51 构建乘积数组 JZ63 数据流中的中位数 JZ64 滑动窗口的最大值 JZ24 二叉树中和为某一值的路径 JZ27 字符串的排列 JZ65 矩阵中的路径 JZ66 机器人的运动范围 JZ4 重建二叉树 这题出现在很多填空选择题里面,相信我们手算起来还是比较快的。但把手算的思路转化成相应的代码可能会有点陌生。思路就是不断地找到左子树和右子树的前序和后序遍历,递归即可。 JZ10 二叉树的镜像 JZ23 二叉搜索树的后序遍历序列 JZ38 二叉树的深度 JZ39 平衡二叉树 JZ57 二叉树的下一个结点 JZ22 从上往下打印二叉树 JZ61 序列化二叉树 JZ62 二叉搜索树的第k个结点 JZ5 用两个栈实现队列 JZ20 包含min函数的栈 思路1:借用辅助栈。对push进去的元素排序后出栈虽然可实现min函数,但不是在O(1)时间内。因此我们在可一直去维护一个最小元素栈。 JZ21 栈的压入、弹出序列 JZ2 替换空格 JZ34 第一个只出现一次的字符 JZ44 翻转单词序列 思路1:同样,我们先翻转每个单词,再整体翻转。记录下空格的位置即可。 JZ53 表示数值的字符串 JZ54 字符流中第一个不重复的字符 JZ11 二进制中1的个数 JZ29 最小的K个数 JZ31 整数中1出现的次数(从1到n整数中1出现的次数) JZ47 求1+2+3+…+n JZ45 扑克牌顺子 JZ46 孩子们的游戏(圆圈中最后剩下的数) JZ48 不用加减乘除做加法
思路1:暴力查找,双重循环,外层循环控制行,内层循环控制列,进行查找。
思路2:利用数组,从左到右递增,从上到下递增的特点。从左下角或者右上角开始。假设当前点为f(r,c),若f(r,c)>target,则r上移一行。若f(r,c)
思路1:利用旋转数组的特性,定义下标i,遍历数组array直到array[i]>array[i+1];
思路2:二分法,
思路3:排序后输出第1个元素/用STL中min_element ()函数返回最小元素的迭代器。
思路1:两次循环,第一次遍历找出奇数加入新数组,第二次遍历找出偶数加入新数组。
思路2:这里牛客上不同于leetcode,牛客上要求新数组的奇偶的相对顺序不变。仍然可以采用双指针left和right思想,left从前往后遍历,碰到奇数顺序放入新数组中。right从后往前遍历,碰到偶数倒序放入新数组中。
思路1:可以模拟打印矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。(这个思路是复制的)
思路1:直接法,用hash表统计次数,输出次数大于数组长一半的对应的数字。
思路2:可以想到,若出现次数超过一般,则排序后,中间的即为目标数字。
思路2:动态规划。定义dp[j] 表示以元素j结尾的连续子数组的最大和,可写出状态转移方程。
思路1:暴力穷举,双重循环,外层循环确定待比较的数,内层循环选择后面的数与待比较的数进行比较,计数器进行对应操作。
思路2:归并排序
思路1:次数问题用hash表,遍历即可。
思路2:位运算,2 ^ 3 ^ 2 ^ 4 ^ 4等价于 2 ^ 2 ^ 4 ^ 4 ^ 3 => 0 ^ 0 ^3 => 3
思路1:暴力穷举,双重循环,外层循环控制左边界,内层循环控制右边界,输出符合条件的结果。
思路2:滑动窗口,初始化一个窗口,求窗口内元素的和sum,若sum>target,左边界右移;若sum
思路3:假设已知左边界和右边界,那么可通过等差数列前n项和求出sum。同理,假设已知左边界和sum,可以解方程求出右边界。
思路1:左右指针滑动,初始化左指针指向起点,右指针指向边界。考录到数组有序,循环中,若二指针指向元素之和sum
思路1:重复问题用hash表。遍历hash表直到遇到出现次数大于1的则返回。
思路2:排序后遍历直到array[i]=array[i+1] 则返回
思路3:: 抽屉原理
思路1: 暴力求解,双重循环,外层循环确定A数组的每一个元素,内层循环在B中排除A数组中的元素。
思路1:暴力求解,根据题目意思排序后输出,注意讨论奇数和偶数个数
思路2:待补充
思路1:遍历,一层循环确定左指针,则右指针为左指针+3。遍历求出最大值即可。2.3 回溯法
思路1:很典型的回溯算法,递归终止条件为和等于目标值且到达叶子节点。
思路1: 很典型的回溯法,去重等思路同数字的全排列类似。
思路1 :同样是回溯法的应用,不断去尝试直到满足情况。在该二维数组中可采用一个访问标记去表示当前字符是否被访问过了。因为初始起点并不固定,可采用双重for循环,每次循环回溯一个初始点的可能存在的路径。
思路1:可采用一个访问标记去表示当前格子是否被走过了。起点固定为原点了。2.4 二叉树
JZ17 树的子结构
思路1:d
思路1:第一步交换根节点的左右子树,之后交换左右子树自己的左右子树。递归即可。
思路2:dfs栈实现
思路1:循环判断。最右边的是根节点。每次都记录下节点个数先小于再大于最右边节点的个数,若小于size-1.则说明不满足。
思路1:广度优先遍历用来层序遍历,也可用来求二叉树的深度。
思路2:递归分析左右子树求解。
平衡二叉树的性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
思路1:这题可利用题38的结论,求左右子树的深度再递归判断即可。
思路1:依题意,先根据next指针找到根节点,再中序遍历即可。
JZ59 按之字形顺序打印二叉树
JZ60 把二叉树打印成多行
JZ38 二叉树的深度
思路1:上述三题就是二叉树的BFS遍历及变体,22题是BFS。59题需要加一个标记,奇数层正序偶数层倒序即可。60题同样,每遍历完一层存为一行即可。38题求深度也是如此,每遍历完一层计数器加一。
思路1:
思路1:利用bst性质,中序遍历后即为升序排列。
思路2:待完成2.5栈
思路1:本题的目的是采用栈的数据结构,实现一个队列。题目给了两个栈s1和s2,push操作直接假定使用s1进行push。那么在pop操作的时候,我们要实现先入先出的性质,直接使用s1来pop是不行的。可以借用s2来进行两个翻转实现先入后出。那么问题来了,假如pop的时候s2是空的,我们则需要先把s1的数据添加到s2中,s2再pop出来。同时,在添加的过程中,要保证s1非空。
思路1:思路类似,明白计算过程。利用辅助栈即可。2.6字符串
思路1:由于从前往后替换空格为‘20%’的话,每碰到一次空格,后面的字符都要后移两位,复杂度太大,所以可以考虑从后往前替换空格。定义两个指针,一个指向原字符串的最后,一个指向新字符串的最后。每次碰到空格,新指针前移3次,原指针前移1次。否则新原指针同时前移。
思路2:利用STL的replace函数。
思路3:正则表达式。
思路1:暴力法,双重循环,求出次数为1的字符输出其位置。
思路2:碰到一次这样的字眼,很直接的利用hash表保存字符和次数,再输出次数为1的位置即可。
思路3:利用STL的函数find_first_of()和 find_last_of()。find_first_of() 是找出 str中第一个出现该字符c的位置,同理,find_last_of() 是找出 str中最后一次出现该字符c的位置。
可想象,我们遍历该字符串,当某一次字符的find_first_of() 和find_last_of() 返回值均为该字符的下标时,说明该字符只出现了一次。
#############################
JZ43 左旋转字符串
思路1:这个题目的思路,我们可以先选择前3个字符,再选择后面的字符。最后再旋转所有的字符,即可以得到想要的结果。
以上两题需要熟悉字符串反转的操作,即双指针。
#############################
思路1:参考leetcode上一个比较清晰的分类,先去除多余的空格,再找出指数的位置,接着判断底数和指数是否合法。
思路1:同样hash表直接处理2.7动态规划
2.8 数学
思路1:利用库函数 bitset 将十进制数转换为二进制数。再将该二进制数转换为字符串遍历即可。
思路2:利用位运算。把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0。那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。对正负数均适用。
JZ12 数值的整数次方
思路1:这个题比较简单,可以循环求解。也可以递归求解,只要对指数的正负分类讨论即可。
思路2:递归的状态方程为 f(n) = basef(n-1), 复杂度较大。可采用二分递归的方法。考虑n为奇数时,f(n) = basef(n/2)*f(n/2);n为偶数时,f(n) = f(n/2)*f(n/2)。这样递归可降低复杂度。
思路3:
思路1:直接来,排序后输出
思路1:堆排序
思路1:直接法,遍历每个数。每次循环求整数的1出现的次数(取余后整除得到位数)//也可以将整数转换为字符串遍历求解。
(要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C))
思路1:单纯的使用循环会碰到for或者while,而使用递归 f(n)=n+f(n-1),递归终止条件会出现if语句。可利用短路原理,
A&&B
1.A为true,则计算并返回表达式B的bool值
2.A为false,则直接返回false
思路1:模拟,统计0的个数和数字之间的差距,比较即可。注意存在元素相等则不对。
思路1:模拟法,按照题目给的过程,模拟整个过程的实现。
思路1:位运算