剑指offer简单思路与总结

需要找到重复的数字。用哈希表法(k为数组的值,value为bool),遍历数组,每一个值标为true,如果第二次访问了那么直接返回即可

在有规律的二维数组查找目标值是否存在。用类比二叉搜索树法,将左下角或者右上角的元素看做是根节点(观察可以知道符合二叉搜索树的特征);该坐标作为条件遍历,如果大于或者小于该值,移动行或者列,找到直接返回即可。

将空格替换为(%20)。可以直接使用库方法strings.Replace(s, " ", "%20", -1);或者遍历字符串,每次将值转换为string类型加入到新字符串,遇到空格,加入%20,返回即可

将链表的值用数组反向输出。可以直接遍历链表,将值加入到数组,然后反转数组;也可以先反转链表,然后将值加入到数组。

根据前序和中序遍历结果构建二叉树。首先前序遍历的第一个元素是根节点;在中序遍历结果中找到这个值,得到他的索引;通过这个索引可以分别在先序和中序划定左右子树,最后递归得到左右子树,返回根节点即可。

用两个栈实现队列。从整体上来看就是实现入队和出队的操作

  • 入队:这时候用第一个栈,直接将元素放进去即可
  • 出队:如果只有一个栈,以为着只能去栈头,不满足我们出队先进先出的条件,所以我们需要第二个栈,这个栈主要是将第一个栈中的元素出栈放到第二个栈,这样一来第二个栈的栈头就是我们需要的队头了。

青蛙一次可以跳一个台阶或两个,一共有多少种跳法。两道题是一样的,这里不可以用递归的方法,会超时,所以只能用非递归,用数组保存元素,先是第一个和第二个已知的,然后循环,求a[i]=a[i-1]+a[i-2],当然按照题目的意思需要取模,最后返回a[n]即可

找到旋转数组最小值。使用二分法,如果中间值大于最右边的值,那么最小值在中间值的右边,left=mid+1(因为不可能是中间值,所以可以+1),如果中间值小于最右边的值,那么最小值在中间值的左边,right=mid(因为可能这个中间值就是最小值);最后是相等的情况,考虑到有重复元素的存在,所以将right向右移动一步即可,最后返回nums[left]

在一个网格中找单词是否存在。采用深度搜索遍历的方法,如果越界,值与单词字母不相等都返回false,如果访问过就标为'0',用一个布尔值来记录上下左右的返回值(只要有一边满足条件即可),直到单词匹配完毕,才返回true,另外如果出现不满足的回溯,将标记过的返回原字母

将绳子分成m段,每段相乘,求乘积最大值。我使用的方法是,开始从两段开始,分别找每种情况的最大值,可以知道,分出来的每段越近,乘积越大。所以可以依次求出每段的长度,求出每种情况的最大值,取最大即可。

另外一种方法是动态规划,dp表示长度为i的绳子,最大乘积;对于一根绳子,一种情况是剪成两段:先剪j,然后剪i-j, j*(i-j)第二种情况是i-j继续剪 j*dp[i-j]

满足条件数位和不大于k。使用的是深度优先遍历,用标记数组来标记访问过的元素。

和上一题题目一样,只不过要对最后的结果取对。使用的方法是快速幂。

输入一个二进制,求出有多少个一。我们可以知道,一个数和这个数减一取与,会让这个数少一个一,依据这个原理,我们可以用一个计数器来计数,只要这个数不为0,每次计数器加一,最后返回即可。

求x的n次方。使用递归+二分的方法,递归结束的条件是n为0,返回1即可。每次递归将n变为一半(可以理解为二分),保存每次递归的值,判断n是否为奇数,如果是奇数,那么除了这个递归值相乘之外还需要乘以x,因为是奇数,n/2会丢失一个值。

打印到10的几次方(参数)的所有数。首先求出这个数,然后从1开始循环打印出来即可。

在链表中找到指定的值,删除这个节点。考虑到第一个节点可能会被删除,所以需要借助哑巴结点,然后往后遍历,遇到值相等,指向下一个结点,最后返回head.Next即可。

使用的是动态规划,有点复杂

使用的模拟法,看到目前为止是否构成数字

最后的数组奇数在前面,偶数在后面。我的方法是,先将偶数放在原数组的最后面,把奇数放在新数组中,最后在指定原数组偶数那一段,append到新数组即可。

找到倒数第k个节点。我的方法是,找到正数第len-k+1个,首先遍历链表求出它的长度,然后再次遍历链表,这一次借用一个标志flag,当flag=len-k+1时证明到了,退出循环,返回此时的节点即可。

反转链表,真的很简单的题目。我们需要做的就是把一个节点的下一个节点变为它的上一个节点,下一结点被改变了,那么如果要循环就需要一个节点来保存这个值,另外由于不是双链表,没有上一个节点,所以需要一个来保存上一个节点,保持循环就可以了。

两种方法解决

  • 递归:用l1来综合两个节点,下一个节点取决于谁小,l2小的话就交换l1l2
  • 哑巴节点:用一个哑巴节点来辅助,判断两个链表的值,让哑巴节点指向小的,最后两个不为空的加入到哑巴节点的后面即可

三个题目都是用递归方法,具体参考二叉树_递归目录下的递归.md`文件

这一题要求我们顺时针把矩阵的值打印出来。也就是按照右下左上的顺序打印。我们采用的方法是给四边r, l, t, b设置边界,均可以取到,当我们从左往右打印的时候,上边界就会往下移动一步,其他类似;直到上边界值大于下边界,左边界值大于右边界,我们break退出循环即可。

这一题就是增加一个栈,栈顶都是最小值。首先是push,普通栈就直接可以将元素push进去即可,但是最小栈只能push比栈顶小的元素,这时候pop就有讲究了,因为需要pop的值不一定在最小栈中,所以需要进行比较,如果在,才会pop,最后就是最小值,直接取最小栈的栈顶即可。

这一题就是给我们栈的压入和弹出序列,让我们来判断弹出序列合不合理。这一题我们采用的方法是模拟栈, 遍历入栈序列加入到一个新的栈,然后弹出栈序列和新栈的栈顶比较,如果相等,就移动弹出序列的指针,模拟栈栈顶出栈,直到不相等;最后我们看一下模拟栈是否为空,为空就说明匹配完了,返回true

这一题的难度是逐渐增加的,主要考察的是层次遍历。第一道题是一层一层的加入到一个一维数组中,第二道题是每一层加入到一个二维数组,第三道题是偶数层的需要逆序输出。这三题都是考察层序遍历,都使用队列的方式实现,遍历每一层,每一层都有一个固定的元素个数,遍历他们,取对头,将队头的值加入到数组,然后出队,看一下这个队头有没有左右子树,有的话加入到指针数组。第三题主要是用一个标志位,如果是true的话,就需要反转,我们将这一层的数组结果反转一下就好了,然后flag=!flag

给定一个二叉搜索树的后序遍历,判断是否合理。我们采用的方法是分治递归法, 因为后序遍历的最后一个元素肯定是根元素,那么前面的元素就被分成两份,左半部分是小于最后元素的,右半部分是大于的,所以我们关键要找到这个下标,可以遍历数组,直到值大于最后一个值的时候break,我们就可以获得这个索引,然后如果满足的话,就意味着从这个索引往后都是大于最后一个元素值的,所以我们继续遍历,如果有元素没有大于,直接返回false,之后我们就递归左半部分和右半部分,执行一样的操作即可!

给定一个目标值,这二叉树中找到一条路径,路径上的值相加等于目标值。我们采用的方法是回溯法, 遍历二叉树,每次都将元素值加入到一个一维数组中,然后目标值依据这个值减小,当最后满足target=0时(当然左右子树也要是空),我们将一维数组的值copy到另一个数组中(主要是为了防止之后的回溯),将copy数组加入到最后的结果二维数组中,然后递归左右子树,最后不满足的话就要撤销选择,将元素从一维数组中pop出去,最后返回二维数组即可.

这一题要求我们复制一个多了一个随机结点的复杂链表。我们采用的方法是哈希表,首先我们将值放到哈希表中,因为随机结点的指向是随机的,所以先要有值,再一次遍历,增加Next指针和Random指针hashtable[cur].Random = hashtable[cur.Random]

因为是二叉搜索树,所以中序遍历是有序的,我们可以进行中序遍历,先遍历左节点,当第一次当遍历到根节点的时候,此时pre为空将head = cur,然后用pre保存当前结点cur的值,之后进行修改pre的right变为cur,cur的left变为pre,每一次pre都保存cur的值,遍历结束之后此时变成了双向链表,但是没有循环,所以我们需要让head的left指向pre即头结点指向尾结点,pre的right指向head即尾结点指向头结点。

需要打印字符串的所有排列。这一题采用的是回溯法,因为可能会有重复的元素,就会出现相同的排列,所以需要避免,一开始对字符串进行排序,然后将字符串变成数组的形式,依次将元素加入到每一种情况的数组,为了避免重复选择,我们借助了一个数组来标记,如果选择了,标记为true,之后遇到之间剪枝,另外如果当前元素与前面的元素值相等,并且之前的那个元素未被标记,也需要剪枝,然后回溯撤销选择,最后当每一种情况的数组长度等于那个数组长度的时候,将结果加入到最终的结果,需要转换,返回结果即可。

返回数组中超过一半的数字。我们用一个哈希表来计数,当哈希值大于一半的时候返回这个值即可。

直接从小到大进行排序,返回数组前k个元素即可。

	// 思路:用一个大跟堆保存 较小的一半数据,堆顶为 最大值。
    // 用一个小根堆保存 较大的一半数据,堆顶为最小值
    // 这样判断元素个数,如果是奇数,那么中位数就是 大跟堆的堆顶
    // 如果是偶数,那么中位数就是 两个堆顶的平均值

    // 维护两个堆
    // 如果大跟堆的堆顶 大于 小根堆的堆顶了,那么说明逆序了,交换两个堆顶
    // 如果大跟堆的长度 大于 小根堆的长度 超过2  说明大跟堆元素太多,弹出一个 放到小根堆中

这一题要求我们找到所以子数组和中的最大值。我们直接遍历一遍数组,很容易知道,当后面一个数加上前面一个数的和比自己还要小的时候,那么这样构成的数组肯定不是最大的,我们要从他变大的那一刻开始记录,当相加比自己大的时候,把自己变成这个最大值,然后再去和要求的最大值去比较,最后返回最大值即可。

abdef
_ _ d _ _ 
前面可以是 
1、 0 ~ ab-1 第一种情况  
2、 ab 是另一种情况,此时需要讨论 d 的值 d = 1  后面可以取 0~df df + 1种, d = 0 无,d > 1 可以取0~99 100种
以这样的方式求出每一位出现1的次数

数字以0123456789101112131415…的格式序列化到一个字符序列中,我们的目标是给定一个位数,判断它的值。

这一题我们将问题分成三步

  1. 确定所求数位的所在数字的位数。比如我们要求第19位,那么这个数字是二位数还是三位数

    用一个count来判断,n和count进行比较。start表示每一个位数的开始值,digit表示位数。初始状态

    digit=1		start=1		cnt=9
    

    循环

    n -= count	start *= 10		digit += 1		count = 9*start*digit
    
  2. 确定所求数位所在的数字。比如我们要求第19位,我们就先确定第19位是在14这个数字

    num = start+(n-1)/digit
    
  3. 确定所求数位在 num 的哪一数位。是在14的1还是4

    将num 变成 一个字符串,找到下标为 (n-1)%digit	的值
    

使用快速排序的方法。

思路很简单,动态规划,dp表示到当前的总数,两种情况:

  1. 要么和前一个数结合 dp[i] = dp[i - 1] + dp[i - 2] 。 dp[i - 2] 是结合的总数,dp[i - 1] 是结合的总数,需要相加
  2. 要么结合不了(也就是大于25或者前一个为0)dp[i] = dp[i - 1]

剑指offer下的动态规划目录文件

从左上一直到右下,找到礼物的最大价值。我们采用动态规划的方法,遍历数组,当前值的dp取上或者左的最大值。最后返回dp即可

算法**是滑动窗口,通过控制左端点和当前的索引构成的窗口里都是不重复的,不断更新最长的值

需要注意的是当map中找到出现过的了,需要更新 left = max(left, map[i] + 1),这里之所以需要和left取最大值,是因为如果是

abba的话,那么遍历到 第二个a的时候,此时left如果不取最大值就是 1, 但是实际上应该是 2。所以说最基本还是得从left开始

剑指offer数组_双指针_滑动窗口目录下的文件。

剑指offer动态规划目录下的文件。

给定一个字符串,返回第一个只出现一次的字符。所以我们需要进行计数,首先可以用哈希表,遍历字符串进行计数,再次遍历返回第一个哈希值为1的字符,但是这种时间复杂度太高了,我们可以用26位数组来进行计数,下标是s[i]-'a', 值就是次数,最后同样再次遍历字符串即可,不能遍历哈希表,因为顺序不对,答案需要返回第一次出现的。

使用的是归并排序的方法。

总的逆序对数为 左边逆序对数 + 右边逆序对数 + 两边形成的逆序对

在合并的同时,如果左边数组的某个数大于右边数组的某个数,那么左边数组 这个数之后的和右边数组的那个数都构成逆序对,因为合并的是两个有序数组,所以左边这个数 之后的数都会小于右边数组的那个数。

找到两个链表的第一个公共节点。我们采用的方法是双指针,两个指针分别遍历两个链表,当遍历到空节点的时候,就把头结点变成另一个的头结点继续遍历,如果有公共的那么一定会相遇,否则当他们都到nil的时候也会停止。

直接遍历数组,如果和目标值相等的话,返回的结果加一,最后将结果返回即可。

还是一样遍历数组,因为下标和值是相等的,所以当遇到不相等的情况返回下标即可。注意的是可能会出现没有缺失的情况,这时候返回数组的长度即可,也就是后一位数字

因为是搜索树,我们可以通过中序遍历将节点的值加入到一个数组,那么这个数组将会是一个递增数组,最后通过这个数组即可找到第k大节点。

这一题直接使用分治递归,先算出左边子树的高度,再算出右边子树的高度,比较一下,哪一个大加一即可。然后递归的进行这样操作

平衡二叉树要求左右子树的高度不能超过1,我们还是和上一题一样使用分治递归法,先算出左子树的最大深度,再算出右子树的最大深度,然后要求差值绝对值不能大于1,然后递归,左边也得是平衡二叉树,右边也得是平衡二叉树。

// 思路:先将所有的数 异或 结果就是 只出现一次的那两个数 异或的结果
// 异或不为0 说明两个数的二进制位肯定有一位 是0和1,这样得到的异或值二进制这位是 1
// 根据这个位 可以将所有的数分为两半
// 一半是这位为0的,一半是这位为1的,另外相等的数一定会出现在同一边
// 这样就化为了两个集合,结果那两个数分别在两个集合中,集合异或即可 得到那个数

这一题使用的是位运算,具体见位运算目录下的文件。

这一题使用的是位运算,具体见位运算目录下的文件。

从一个升序排列的数组找到两个和为target的数。我们使用的是双指针法,左指针和右指针对应的两个数之和与target进行比较,如果大于的话,那么右指针左移动;如果小于target的话,左指针右移动,等于break,返回结果即可。

这一题使用的是滑动窗口,我们维护一个窗口,在这个窗口里的值总和,如果小于target,右边界移动,和增加,如果大于target,左边界移动,和减小,如果大于等于target,返回结果,然后继续左边界右移动,总和减小,继续寻找其他的结果。

这一题使用的是双指针。具体见数组_双指针_滑动窗口目录下的文件

直接用一个字符串加入n到最后,再加上到n即可。

这一题使用的方法是单调双向队列,具体见数组_双指针_滑动窗口目录。

用两个队列来实现,一个队列单纯入队,一个队列是单调队列,使得每次队头都是最大值

  • push的时候,正常队列直接入队即可,单调队列则需要从队尾判断是否小于将要入队的值,如果小于的话,那么就需要从队尾出队,这就意味着这个队列是一个双向队列,既可以从队尾出队,也可以正常从队头出队
  • pop的时候,正常队列每次直接出队即可,单调队列则需要判断正常队列的值和自己的队头是否相等,如果相等的话就要pop出去

这一题使用的方法是动态规划,具体见动态规划目录。

这一题要求给的五张牌是一个顺子,也就是连续的数字,而大王小王是0,可以变成任意的值让他们变成顺子。很容易知道除了0之外的其余所有数字,最大值和最小值相差需要小于5才能保证是顺子,另外还需要保证不能有重复的数字出现(除零之外),可以用哈希表进行判断。

这是一个数学问题,具体见数学问题目录

给定一个利润数组,要求我们求出最大股票利润。我们知道的是最大利润应该是之后的利润减去之前的利润的最大差值,我们可以遍历数组,先求出当前最大利润,就是当前利润减去之前的最小利润,然后再求出最小利润,不断的去更新这两个值,最后返回最大利润即可。

这是一道小学数学题目。直接返回 (1+n)*n/2即可。

这是一道位运算题目,具体见位运算目录。

要求我们构造一个乘积数组,每一个值是除了当前数组的值外其他所有的相乘。每个值在自己这个下标的时候就是乘以1,所以就形成了上下两个三角形,我们只需要相乘就好

  1. 第一步遍历数组,直接赋值res[i] = res[i-1] * a[i-1]
  2. 第二步再次遍历数组,用一个辅助来保存值,最后再和res数组的值相乘

用一个标志来表示正负号,一开始先处理之前的字符,如果是空格就跳过,如果是负号,标志变为-1,然后遍历字符串,res=res*10+int(str[i]-'0'),str[i]='0'主要是将数字字符化为数字,然后判断res是否超出32位整数范围,最后返回flag*res

直接判断两个节点和根节点的大小,如果都大于,说明两个节点最近公共祖先在右子树;如果都小于,说明两个节点最近公共祖先在左子树,否者就是根节点,然后递归即可。

分别在左子树和右子树递归的查找两个节点,如果左右两边都找到的话,说明两个节点在左右两子树,返回root,如果只在左子树找到,返回left即可,在右子树返回右即可。