文章目录

  • 总结备忘
  • 第二章 面试需要的基础知识
    • 2.3 数据结构
      • 面试题3:数组中重复的数字
        • 题目一:找出数组中重复的数字
        • 题目二:不修改数组找出重复的数字
      • 面试题4:二维数组中的查找
      • 面试题5:替换空格
        • 相关题目:
      • 面试题6:从尾到头打印链表
      • 面试题7:重建二叉树(递归)(重要)
      • 面试题8:二叉树的下一个节点(典型)
      • 面试题9:用两个栈实现队列
    • 2.4 算法和数据操作
      • 面试题10:斐波那契数列
        • 题目一:求斐波那契数列的第n项
        • 题目二:青蛙跳台阶问题
        • 相关题目:小矩形覆盖大矩形问题
      • 面试题11:旋转数组的最小数字(注意特殊的测试用例)
      • 面试题12:矩阵中的路径(回溯法)(重要)
      • 面试题13:机器人的运动范围(回溯法)
      • 面试题14:剪绳子(动态规划与贪婪算法)
      • 面试题15:二进制中1的个数(位运算)
  • 第三章 高质量的代码
    • 3.3 代码的完整性
      • 面试题16:数值的整数次方
      • 面试题17:打印从1到最大的n位数
      • 面试题18:删除链表的节点
        • 题目一:在O(1)时间删除链表结点
        • 题目二:删除链表所有重复的节点
      • 面试题19:正则表达式匹配(重要)
      • 面试题20:表示数值的字符串(重要)
      • 面试题21:调整数组顺序使奇数位于偶数前面
        • 考虑这道题的可扩展解法
      • 面试题22:链表中倒数第k个节点
        • 相关题目:求链表的中间节点
      • 面试题23:链表中环的入口节点(快慢指针)(重要)
      • 面试题24:反转链表
      • 面试题25:合并两个排序的链表(递归)
      • 面试题26:树的子结构(递归)(重要)
  • 第四章 解决面试题的思路
    • 4.2 画图让抽象问题形象化
      • 面试题27:二叉树的镜像(递归/循环)
      • 面试题28:对称的二叉树(递归)
      • 面试题29:顺时针打印矩阵(重要)
    • 4.3 举例让抽象问题具体化
      • 面试题30:包含min函数的栈
      • 面试题31:栈的压入,弹出栈列(代码能力,重要)
        • 【判断一个序列是不是栈的弹出序列】
      • 面试题32:从上到下打印二叉树
        • 扩展0:广度优先遍历有向图
        • 扩展1:分行从上到下打印二叉树
        • 扩展2:之字形打印二叉树(重要)
      • 面试题33:二叉搜索树的后序遍历(重要)
          • 【二叉树后序遍历序列的规律】
        • 扩展0:二叉搜索树的前序遍历
      • 面试题34:二叉树中和为某一值的路径(重要)
    • 4.4 分解让复杂问题简单化
      • 面试题35:复杂链表的复制(掌握方法三的思路)
      • 面试题36:二叉搜索树与双向链表(中序遍历)(重要)
      • 面试题37:序列化二叉树(二叉树遍历&二叉树重构)
      • 面试题38:字符串的排列(全排列,递归)(重要)
        • 扩展:全组合(递归)(重要)
        • 相关题目(正方体顶点和,国际象棋上放置皇后)
  • 第五章 优化时间和空间效率
    • 5.2 时间效率
      • 面试题39:数组中出现次数超过一半的数字
      • 面试题40:最小的k个数
        • 方法一:基于Partition的方法,复杂度为O(n),必须要修改输入数组
        • 方法二:最大堆方法,复杂度为O(nlogk),无需修改输入数组, 适合海量数据
      • 面试题41:数据流中的中位数
      • 面试题42:连续子数组的最大和
      • 面试题43:1~n整数中1出现的次数
      • 面试题44:数字序列中某一位的数字
      • 面试题45:把数组排成最小的数(重要)
      • 面试题46:把数字翻译成字符串
      • 面试题47:礼物的最大价值(动态规划)
      • 面试题48:最长不含重复字符的子字符串(动态规划)(重要)
    • 5.3 时间效率与空间效率的平衡
      • 面试题49:丑数
      • 面试题50:第一个只出现一次的字符
        • 相关题目
          • 相关题目一:从第一个字符串中删除在第二个字符串中出现过的所有字符
          • 相关题目二:删除字符串中所有重复出现的字符
          • 相关题目三:判断输入的两个字符串是不是互为变位词(Anagram问题)
        • 题目二:字符流中第一个只出现一次的字符
      • 面试题51:数组中的逆序对(归并排序)
      • 面试题52:两个链表的第一个公共节点
  • 第六章 面试中的各项能力
    • 6.3 知识迁移能力
      • 面试题53:在排序数组中查找数字(二分查找)
        • 题目二: 0~n-1中缺失的数字(二分查找)
        • 题目三: 数组中数值和下标相等的元素(二分查找)
      • 面试题54:二叉搜索树的第k大节点
      • 面试题55:二叉树的深度
        • 题目一:二叉树深度
        • 题目二:平衡二叉树
      • 面试题56:数组中数字出现的次数
        • 题目一:数组中只出现一次的两个数字(异或)(重要)
        • 题目二:数组中唯一只出现一次的数字(位运算)(重要)
      • 面试题57:和为s的数字
        • 题目一:和为s的两个数字
        • 题目二:和为s的连续正数序列
      • 面试题58:翻转字符串
        • 题目一:翻转单词顺序
        • 题目二:左旋转字符串
      • 面试题59:队列的最大值
        • 题目一:滑动窗口的最大值(重要)
        • 题目二:队列的最大值
    • 6.4 抽象建模能力
      • 面试题60:n个骰子的点数(重要)
      • 面试题61:扑克牌中的顺子
      • 面试题62:圆圈中最后剩下的数字-约瑟夫环问题
      • 面试题63:股票的最大利润
      • 面试题64:求1+2+...+n
      • 面试题65:不用加减乘除做加法(位运算)
        • 【扩展】使用位运算实现加减乘除
      • 面试题66:构建乘积数组
      • 面试题67:字符串转换成整数
      • 面试题68:树中两个节点的最低公共祖先
  • 其它面试常见问题总结
    • Top-k系列问题
    • 海量数据问题

总结备忘

备忘:
第7题,重建二叉树,有思路,不会实现
12题,矩阵中的路径,回溯法


备忘:
第一遍没做出来(或者做的时候比较吃力的题目):
17,打印从1到最大的n位数,overflow函数(模拟字符串加法),自己没写出来
18,题目二,删除链表中的重复数字,debug了无数遍,考虑不全,反思!
19,正则表达式匹配,自己不会写
23,快慢指针的经典题,多做几遍。
24,反转链表,二刷通过
25,递归循环都可以,二刷通过
26,也是递归,二刷通过

二刷不会做的题目(需要加强练习的):
17,
18,
19,正则表达式的题目
23,快慢指针的经典题,多做几遍。


第一遍没做出来(或者做的时候比较吃力的题目):
29,顺时针打印矩阵,按照自己的想法没写下去,但是应该也是可行的,回头自己尝试写完。
32,扩展2,之字形打印二叉树。没有想到用两个栈,而是自己添加flag变量,通过设置flag变量的true或False来表示奇偶层。这个想法的问题在于:当前层还没打印完的时候, 子节点就已经压进栈中了。
33,二叉搜索树的后序遍历(反推),拿到题之后全没思路。
34,二叉树中和为某一值的路径(递归),自己没写出来。回头需要重点加强。
36题,拿到题之后完全不会,后来才发现其实就是中序遍历的 一个应用而已,还是要加强练习。
37,二叉树的反序列化(即二叉树重构), 给定前序,中序,后序遍历的字符串吗,重构二叉树,这块还需加强。
38, 全排列,全组合。递归解法,需要掌握。

二刷记录:
31,思路简单,但是写错了。
33, 小坑没跳过去(判断右子树的所有值都要大于root)
36,


备忘:
46题,没思路
47题,循环解法没写出来(写错了)


备忘:
54题,中序遍历,必须要封装成class类才能输出排好序的数组??
59题,队列的最大值,没有思路,
60,n个骰子的点数,不会做
62,第二种解法没看懂,二刷的时候回来看

第二章 面试需要的基础知识

2.3 数据结构

面试题3:数组中重复的数字

题目一:找出数组中重复的数字

【题目描述】:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

【解题思路】:
方法1: 先排序,然后按顺序查找,排序复杂度O(nlogn),按顺序查找复杂度O(n)

方法2: 哈希表,遍历数组,判断哈希表里是否存在该数,如果不存在则加入,如果存在则输出该数。时间复杂度O(n)(额外占用O(n)的空间)

方法3: 时间O(n)+空间O(1)
0~n-1正常的排序应该是A[i]=i;因此可以通过交换的方式,将它们都各自放回属于自己的位置;

  • 从头到尾扫描数组A,当扫描到下标为i的数字m时,首先比较这个数字m是不是等于i,
  • 如果是,则继续扫描下一个数字;
  • 如果不是,则判断它和A[m]是否相等,如果是,则找到了第一个重复的数字(在下标为i和m的位置都出现了m);
  • 如果不是,则把A[i]和A[m]交换,即把m放回属于它的位置;

重复上述过程,直至找到一个重复的数字;(将每个数字放到属于自己的位置最多交换两次)

题目二:不修改数组找出重复的数字

【题目描述】:
在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或者3。

【解题思路】:

方法1(时间O(n),空间O(n)):创建一个长度为n+1的辅助数组,然后逐一把原数组中的每个元素复制到辅助数组中对应的位置(按下标存放),如果辅助数组中已经存在该元素了,说明该元素重复了,直接返回。

方法2(类似于二分查找法,时间O(n),空间O(1)):如果没有重复的数字,那么在1~n的范围里只有n个数字, 如果在某个范围里,数字的个数大于范围的长度,证明该范围内的数字有重复(本题的关键!)

范围是1~n,我们使用该范围的中间值,将范围1~n分成两部分(注意:二分的不是数组,而是数组中元素的取值范围),分别统计大于该范围的数字个数,以及小于该范围的数字个数,如果某个范围的数字个数大于范围长度,证明重复数字出现在该范围内,然后在该范围内二分查找,最终将范围锁定在重复的数字上。

面试题4:二维数组中的查找

【题目描述】:
在一个二维数组中,每一行元素都按照从左到右递增的顺序排序,每一列元素都按照从上到下递增的顺序排序。实现一个查找功能的函数,函数的输入为二维数组和一个整数,判断数组中是否含有该整数。

【解题思路】:
这个思路关键的地方在于右上角点的选取因为这个点的值是所在列的最小值和所在行的最大值,这就意味着:
要查找的数值如果比右上角的值大,那么它将大于整个行;
要查找的数值比如果右上角的值小,那么它将小于整个列。

面试题5:替换空格

【题目描述】:
请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.”。

【解题思路】:
先遍历一遍字符串,统计空格的个数,第二遍遍历的时候,再从尾到头依次替换,这样的话,所有字符只需移动一次。复杂度O(n)

相关题目:

【题目描述】:
有两个排序的数组A1和A2,内存在A1末尾有足够多的空间容纳A2,请实现一个函数,把A2中的所有数字插入到A1中,并且所有的数字是排序的。

【解题思路】:
从尾到头比较A1和A2中的数字,并且把较大的数字复制到A1中的合适位置。

面试题6:从尾到头打印链表

【题目描述】:
输入一个链表,从尾到头打印链表每个节点的值。

【解题思路】:
方法1:使用栈这个数据结构,遍历链表,将节点依次存进栈中,再从栈中依次弹出即可。(鲁棒性更好)
方法2:使用递归来实现,每访问到一个节点时,先递归输出它后面的节点,再输出它自身。

面试题7:重建二叉树(递归)(重要)

【题目描述】:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回其头结点。

【解题思路】:
前序遍历的第一个数字就是根节点的值,扫描中序遍历序列,找到根节点的位置,根节点左边的都是左子树,右边的都是右子树。然后递归找即可。

class Solution:def Construct(self, preorder, inorder):if not preorder or not inorder:return Noneroot = TreeNode(preorder[0])index = inorder.index(root.val)root.left = self.Construct(preorder[1:index + 1],inorder[:index])root.right = self.Construct(preorder[index + 1:], inorder[index + 1:])return root

面试题8:二叉树的下一个节点(典型)

【题目描述】:
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
如下图,如果输入3,则输出1;如果输入8,则输出0;如果输入6,则输出None。

【解题思路】:
给定节点node,要输出中序遍历的下一个节点,分两种情况讨论:

  1. 如果node有右子树,则下一个肯定是右子树中最左边的节点
  2. 如果node没有右子树,则只能往父节点方向回溯,这里又有两个小点:
    (1)如果node是其父节点的左孩子,则下一个节点就输出的是父节点
    (2)如果node是父节点的右孩子(则说明父节点已经遍历过了),那只能继续往上回溯,找到满足这样条件的节点A:A是它父节点的左节点,而我们要输出的就是这个A的父节点。
    说法可能有点绕,自己体会一下即可。为什么不讨论node的左节点?因为中序遍历的规则,既然已经到node了,node的左节点肯定已经遍历过了。

面试题9:用两个栈实现队列

【题目描述】:
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

【解题思路】:
很简单。两个队列实现栈同理。

2.4 算法和数据操作

面试题10:斐波那契数列

题目一:求斐波那契数列的第n项

【题目描述】:
写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。斐波那契数列的定义如下: 

【解题思路】:
方法1: 递归
方法2:迭代(循环),定义一个长为n+1的数组array,遍历数组中的每个元素,array[i] = array[i-1] + array[i - 2],最后输出array[n]即可。

题目二:青蛙跳台阶问题

【题目描述】:
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法。

【解题思路】:
假设跳上n级台阶共有f(n)种跳法,由题意可知,f(n) = f(n-1)+f(n-2),斐波那契数列。

【本题扩展】:变态跳台阶
如果把条件改成:一只青蛙依次可以跳上1级台阶,也可以跳上2级,…也可以跳上n级,此时,该青蛙跳上一个n级的台阶总共有多少种跳法。
由数学归纳法得出结论,f(n)=2n−1f(n)=2^{n-1}f(n)=2n−1

相关题目:小矩形覆盖大矩形问题

【题目描述】:
我们可以用2×1的小矩形横着或者竖着去覆盖更大的矩形。请问用8个2×1的小矩形无重叠的覆盖2×8的大矩形,总共有多少种方法?

【解题思路】:
我们先把2×8的覆盖方法记为f(8),用第一个2×1小矩形去覆盖大矩形的最左边时有两种选择:竖着放或横着放。当竖着放的时候,右边还剩下2×7的区域,这种情形下的覆盖方法记为 f(7)。接下来考虑横着放的情况。当2×1的小矩形横着放左上角的时候,左下角必须和横着放一个2×1的小矩形,而在右边还剩下2×6的区域,这种情形下的覆盖方法记为f(6),因此f(8)=f(7)+f(6)。仍然是斐波那契数列。

面试题11:旋转数组的最小数字(注意特殊的测试用例)

【题目描述】:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。

【解题思路】:
二分查找法。具体步骤:设置两个指针start和end,每次找中间的位置mid = (start + end) //2,然后判断array[mid]和array[start]谁更大,如果mid对应的值更大,说明前半段都是递增序列,最小的数在后半段,start=mid;如果start对应的值更大,说明最小的数在前半段。(注意边界条件的处理,如果start==mid对应的值,则说明有重复元素)
循环终止的条件:
start和end指向相邻的两个元素,而end指针指向的即为最小的元素。

【重要】【测试用例】:可以参考牛客网

  1. [1,1,1,1,1]
  2. [1000,1000, 1001, 9999]
  3. null
  4. [1,2,3,4,5]

面试题12:矩阵中的路径(回溯法)(重要)

【题目描述】:
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。矩阵如下:

【解题思路】:
任选一个格子作为起点,假设矩阵中的某个格子的字符为ch,并且这个格子将对应于路径上的第i个位置,如果路径上的第i个字符正好是ch,那么到相邻的格子寻找路径上的第i+1个字符,重复这个过程,直到路径上的所有字符都在矩阵中找到相对应的位置。
如果在ch相邻的四个格子中没有找到路径中的第i+1个字符匹配,则表明第i个字符定位不正确,需要返回到i-1个字符,然后重新定位。
由于路径不能重复进入一个格子,所以需要定义一个visit矩阵,用来存储该格子是否被访问过。

面试题13:机器人的运动范围(回溯法)

【题目描述】:
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

【解题思路】:
机器人从(0,0)开始移动,当它准备进入坐标为(i,j)的格子时,通过检查坐标的数位和来判断机器人是否能够进入,如果机器人能够进入坐标为(i,j)的格子,再判断它能否进入相邻的四个格子。使用回溯法实现。

面试题14:剪绳子(动态规划与贪婪算法)

【题目描述】:
给定一根长度为n的绳子,请把绳子剪成m段,(m和n都是整数,且都大于1)每段绳子记为k[0],k[1]……k[m]。请问k[0]*k[1]……*k[m]可能的最大乘积是多少?例如:当绳子长度为8时,我们把它剪成长度分别为2,3,3段,此时最大乘积为18.

【解题思路】:
方法1:动态规划
对于长度为n的绳子有:f(n)=max(f(i)∗f(n−i)),其中i从1取到i−1f(n) = max(f(i)*f(n-i)),其中i从1取到i-1f(n)=max(f(i)∗f(n−i)),其中i从1取到i−1。这是一个自上到下的递归公式,递归会产生大量的重复计算,因此,我们可以从下到上顺序计算,也就是说,先计算f(2),f(3)…再计算f(n)。(需要注意的是初始值的设定,当绳子长度为0,1,2,3的时候,f(n)分别的取值为0,1,2,3)

方法2:贪婪算法
当n>=5n>=5n>=5时,尽可能的多剪长度为3的绳子,剩下的绳子长度为4时,把绳子剪成两个长度为2的绳子,可以使得总乘积最大。

面试题15:二进制中1的个数(位运算)

【题目描述】:
请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。

【解题思路】:
最佳解法:
把一个整数减去1,再与它本身做与运算,相当于把它最右边的1变成0,。那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。

【总结】:
位运算总共只有五种运算:与、或、异或、左移、右移
在计算机系统中,数值一律用补码来表示和存储。正数的补码是其本身 负数的补码是符号位不变 其他位取反之后再加1。 例如-1在计算机中表示方法为:

  1. 先取1的原码:0000 0000 0000 0000 0000 0000 0000 0001
  2. 取反码(原码按位取反): 1111 1111 1111 1111 1111 1111 1111 1110
  3. 得补码(反码+1): 1111 1111 1111 1111 1111 1111 1111 1111
  4. 所以,-1在计算机里用二进制表达就是全1。16进制为:0xFFFFFF

第三章 高质量的代码

3.3 代码的完整性

面试题16:数值的整数次方

【题目描述】:
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。不能使用库函数,同时不需要考虑大数问题。

【解题思路】:
【常规解法】
使用如下公式递归求解,关键步骤是:1. exponent除以2操作,2. 求余操作(判断奇偶数)

需要考虑几种特殊情况:

  1. base=0时,无论exponent为多少,返回0
  2. exponent=0时,返回1
  3. exponent<0时,先算正数次幂,然后求倒数
  4. exponent>0时,正常计算。

【改进版做法】(使用位运算)
用位运算中的右移操作代替除以2操作,用位与运算符代替求余运算符来判断一个数是奇数还是偶数。

面试题17:打印从1到最大的n位数

【题目描述】:
输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。

【解题思路】:
方法1:常规思路(for循环)
for循环,从1开始打印,但是缺点是,当n很大的时候,会溢出。
方法2:使用字符串模拟数字加法,避免溢出问题
主要有两步:

  1. 在字符串表达的数字上模拟加法(关键!
  2. 把字符串表达的数字打印出来(高位如果是0,则不打0)

面试题18:删除链表的节点

题目一:在O(1)时间删除链表结点

【题目描述】:
给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该 结点。

【解题思路】:
方法1:常规思路 O(n)
从头到尾遍历链表,当找到待删除节点时,将其删除即可。
方法2:
由于已经给定了待删除的节点,那么它的下一个节点其实我们是已知的,直接用下一个节点的内容复制到待删除的节点上,覆盖原有内容,再把下一个节点删除即可。

根据待删节点p的位置,可以分成以下两种情况:

  1. 待删节点p有后续节点(表明该节点不是尾节点)
    直接将p.next内容覆盖当前节点p的内容,再把p.next节点删除掉即可,无需遍历链表。
  2. 待删节点没有后续节点(表明该节点是尾节点)
    又可分为两种情况:
    a)该节点既是头结点又是尾节点(即链表只有一个节点)
    需要将该节点删除的同时,将头结点指针置空。
    b)链表中有多个节点,要删的是尾节点
    这时候需要从头遍历链表,遍历到该节点进行删除操作。

题目二:删除链表所有重复的节点

【题目描述】:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

【解题思路】:
参考 https://blog.csdn.net/gangstudyit/article/details/80623477
https://blog.csdn.net/sixingmiyi39473/article/details/79420241
这一题debug了无数遍,哭。。。
把出bug的测试用例粘过来:

  1. [1,1,1,1,1,1]——输出None
  2. [1,1,1,1,1,2]——输出[2]
  3. [1,1,2,2,3,3,4,4]——输出None
  4. [1,1,2,3,3,4,5,5] ——输出[2,4]

面试题19:正则表达式匹配(重要)

【题目描述】:
请实现一个函数用来匹配包括'.''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但是与"aa.a""ab*a"均不匹配

【解题思路】:
https://www.cnblogs.com/yanmk/p/9196535.html (代码清楚 python)
https://www.cnblogs.com/xuanxufeng/p/6914472.html (思路详细)
分析:这道题的核心其实在于分析'*',对于'.'来说,它和任意字符都匹配,可把其当做普通字符。对于'*'的分析,我们要进行分情况讨论,当所有的情况都搞清楚了以后,就可以写代码了。

在每轮匹配中,Patttern第二个字符是’*'时:

  • 第一个字符不匹配(’.'与任意字符视作匹配),那么'*'只能代表匹配0次,比如’ba’与'a*ba',字符串不变,模式向后移动两个字符,然后匹配剩余字符串和模式
  • 第一个字符匹配,那么’*'可能代表匹配0次,1次,多次,比如'aaa''a*aaa''aba''a*ba''aaaba''a*ba'。匹配0次时,字符串不变,模式向后移动两个字符,然后匹配剩余字符串和模式;匹配1次时,字符串往后移动一个字符,模式向后移动2个字符;匹配多次时,字符串往后移动一个字符,模式不变;

而当Patttern第二个字符不是'*'时,情况就简单多了:

  • 如果字符串的第一个字符和模式中的第一个字符匹配,那么在字符串和模式上都向后移动一个字符,然后匹配剩余字符串和模式。
  • 如果字符串的第一个字符和模式中的第一个字符不匹配,那么直接返回false。

面试题20:表示数值的字符串(重要)

【题目描述】:
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

【解题思路】:
表示数值的字符串遵循模式A[.[B]][e|EC],其中A为整数部分,B为小数部分,e或者E为指数符号,C为指数部分。

  • 小数可以没有整数部分,例如.123等于0.123;因此A不是必须的。如果一个数没有整数部分,那么小数部分不能为空
  • 小数点后面可以没有数字,例如233.等于233.0;
  • A和C前面可以含有正负号,但是B前面不能有正负号;
  • 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
  • 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4;

具体步骤:
首先尽可能多的扫描1~9的数位(有可能在起始处有正负号),如果遇到小数点,则开始扫描小数部分的B部分,如果遇到e或者E,则开始扫描指数的C部分。

面试题21:调整数组顺序使奇数位于偶数前面

【题目描述】:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。(并保证奇数和奇数,偶数和偶数之间的相对位置不变。)

【解题思路】:
如果没有括号中的限制条件,解决思路很简单:只需要维护两个指针i和j,i从头开始,j从尾开始,如果i指的是偶数,j指的是奇数,则将两个数值交换,当i和j相遇时,停止。但是这样的解法并不能保证奇数和偶数的相对位置不变。
要想保证原有次序,只能顺次移动或相邻交换。 这里使用相邻交换:

  1. 设置一个i指针,从头开始遍历;
  2. 对于每个固定的i,内层设置一个循环j,j从尾开始遍历直到i停止,如果相邻两个元素,前面的是偶数后面的是奇数,则交换这两个相邻元素的顺序。每执行完一次内层循环,可以将最前面的奇数“归位”。
  3. i++,继续下一轮遍历。

考虑这道题的可扩展解法

如果把题目改成把数组中的数按照大小分为两部分,所有负数在非负数的前面,如何操作?
如果把题目改成把数组中的数分成两部分,能被3整除的数都在不能被3整除的数前面,如何操作?

解决方案:将判断奇数偶数这个逻辑框架抽离出来,封装成函数,大的逻辑框架不需要改动。即可实现可扩展解法。

面试题22:链表中倒数第k个节点

【题目描述】:
输入一个链表,输出该链表中倒数第k个结点。

【解题思路】:

方法1:(需要遍历两次)
倒数第k个节点就是正数的第n-k+1个节点。直接往前走n-k+1步即可。但是由于只给了链表的头节点,并不知道n是多少,所以只能遍历两次:

  1. 第一次遍历,获取链表长度n,
  2. 第二次,然后从头结点开始,正向走n-k+1步。

方法2:(遍历一次)
正确做法:设置两个指针i和j,先保持第二个指针j不动,指针i往前走k-1步,这时两个指针的距离为k-1,;接下来两个指针同时往前走,当第一个指针i到达链表尾的时候,第二个指针j正好指向倒数第k个节点。

【Attention】注意边界条件的处理方法:

  1. 空链表
  2. k为0
  3. k大于整个链表长度
    对于空链表或者k=0的情况,直接返回null即可。对于k大于整个链表长度的情况,需要在第一个指针走k-1步的循环中多加一个判断。

相关题目:求链表的中间节点

【题目描述】:
如果链表中的节点总数为奇数,则返回中间节点;如果节点总数为偶数,则返回中间两个节点的任意一个。

【解题思路】:
定义快慢指针,同时从链表的头节点出发,快指针一次走两步,慢指针一次走一步,当快指针到链表尾部时,慢指针正好在中间。

面试题23:链表中环的入口节点(快慢指针)(重要)

【题目描述】:
一个链表中包含环,请找出该链表的环的入口结点。如图,环的入口节点是3.

这道题目分三个小步骤:

  1. 先判断链表是否有环
    方法:快慢指针,快指针一次走两步,慢指针一次走一步,如果有环,两个指针终将相遇,且相遇一定发生在环内部节点上,返回这个内部节点给第2步
  2. 如果链表有环,找到环的节点个数
    方法:以第1步的节点为起点,绕环一周,即可获得环内的节点个数n,将n传递给第3步
  3. 找到环的入口
    方法:双指针,定义两个指针p1和p2都指向链表的头结点,指针p1先独自走n步,然后p1和p2一起向前走,p1和p2的相遇点就是环的入口节点。(当p2走到环的入口节点时,p1已经绕环一圈正好回到了入口节点)

--------------2019.7.6二刷:----------------------
https://blog.csdn.net/qq_20141867/article/details/80931915
上述的2和3可以合为一步,即:

  1. 快慢指针,如果能相遇,说明有环,而且相遇的点一定在环内
  2. 慢指针回到表头, 快指针留在相遇点,二者同步往前,下一次相遇就是入口节点。

特殊用例:

  • 输入链表不含环的情况
  • 输入链表为空,或者只有一个节点的情况。

面试题24:反转链表

【题目描述】:
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。

【解题思路】:
本题的关键点:链表不能断!在修改next指针之前,一定要先把节点保存下来
设置三个指针pre,p,pnext,分别指向前一个,当前,后一个节点。在修改当前节点p的next指针时,一定要先将pnext节点保存下来,不然会出现链表断裂问题。这题主要考察指针的用法,思路虽简单,但其实不好写。

特殊用例:

  1. 输入链表为空
  2. 输入链表只有一个节点。

面试题25:合并两个排序的链表(递归)

【题目描述】:
输入两个单调递增的链表,合并这两个链表,并使新链表中的节点仍然是递增排序的。

【解题思路】:
设置两个指针p1和p2,分别指向两个排序链表的表头,然后比较指针所指位置的值大小,小的那个值成为合并链表的表头,对应的指针后移,直至遍历完两个链表。

----19.07.06 二刷------
不用递归也可以实现。

特殊用例:

  1. 两个链表有多个节点
  2. 存在节点值相等的情况
  3. 两个链表的一个或者两个头结点为空。
  4. 两个链表只有一个节点。

面试题26:树的子结构(递归)(重要)

【题目描述】:
输入两棵二叉树A和B,判断B是不是A的子结构。例如下图中的两棵二叉树,由于A中有一部分子树的结构和B是一样的,因此B是A的子结构。

【解题思路】:
主要分为两个部分,

  • 第一步:遍历A,搜寻B的根节点在A的什么位置,找到匹配的值之后,再进行第二步操作,
  • 第二步:判断匹配点下面的子树是否与B相同。

总结:用两个函数来实现,一个是搜寻匹配根节点的,一个是用来匹配子树的。两个函数都可以递归实现。

-----------2019.7.6 二刷------------
写了不借助辅助函数的版本。

特殊用例:

  1. B不是A的子结构(返回False的情况)
  2. 两个二叉树的一个或两个根节点为空
  3. 二叉树的所有节点都没有左子树或右子树

第四章 解决面试题的思路

4.2 画图让抽象问题形象化

面试题27:二叉树的镜像(递归/循环)

【题目描述】:
输入一颗二叉树,输出它的镜像。例如,将左图所示二叉树进行镜像处理,输出为右图。

【解题思路】:
思路比较简单,就是每一个节点的左右节点互换即可(叶节点除外,因为叶节点没有左右子节点)。可以使用递归和循环的方式来实现。

  • 递归:对于根节点,交换其左右节点;然后分别对其左右子节点递归调用函数。
  • 循环:用一个队列保存节点,只要队列不为空就一直循环,每次从队列头部取出一个节点,对于当前节点,交换其左右节点,并将其右节点和左节点依次加入队列。如果是叶节点(没有左右节点),直接continue即可。

面试题28:对称的二叉树(递归)

【题目描述】:
判断一棵二叉树是不是对称的。如果一颗二叉树和它的镜像一样,那么它是对称的。

【解题思路】:
类似上图这样的称为对称的二叉树, 对于同一层的两个节点(比如B和C),需要比较的是B的左孩子和C的右孩子,B的左孙子和C的右孙子,依次递归,但是根节点只有一个,如何处理呢?答案很简单,将根节点“分裂开”即可,这样根节点也就可以一并送入递归部分处理了。

--------2019.7.7 二刷---------
对于根节点,比较左右孩子的值是否相等;如果相等,比较左孩子的左孩子和右孩子的右孩子(对称的)。

特殊用例:

  1. 二叉树为空
  2. 二叉树只有一个节点
  3. 二叉树所有节点值都相同。

面试题29:顺时针打印矩阵(重要)

【题目描述】:
输入一个矩阵,按照从外向里,顺时针打印矩阵,如下图,打印出1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10

【解题思路】:
一圈一圈打印,每圈的起点是有规律可循的,都是对角线上的(start, start)点,而且对于nn矩阵,对于rowscols的矩阵,只需循环cols > startX × 2并且 rows > startY × 2次即可。
打印一圈需要经过四步:从左到右, 从上到下,从右到左,从下到上。其中:

  1. 从左到右(第一步):不需要任何条件,只要矩阵不为空,肯定要经过这步的。
  2. 从上到下(第二步):前提条件,要求至少两行
  3. 从右到左(第三步):前提条件,要求至少两行两列
  4. 从下到上(第四步):前提条件,要求至少三行两列。
    有了上述的分析,其实就很好写了,对于特定尺寸的矩阵,先判断需要循环几轮(走几圈),然后每圈按照以上四步打印即可。

4.3 举例让抽象问题具体化

面试题30:包含min函数的栈

【题目描述】:
定义栈的数据结构,使其能够在O(1)的时间内得到栈的最小元素。

【解法思路】:
在push和pop函数的时候需要修改一下,额外定义一个辅助栈,用来存栈里面的最小元素,push和pop栈顶元素的时候,辅助栈也要进行相应的push和pop操作。

面试题31:栈的压入,弹出栈列(代码能力,重要)

【题目描述】:
输入入栈序列和出栈序列,判断该出栈序列是不是该入栈序列可能的弹出顺序。

【判断一个序列是不是栈的弹出序列】

如果下一个弹出从数字刚好是栈顶数字,那么直接弹出;如果下一个弹出的数字不在栈顶,则把压栈序列中还没有入栈的数字压入辅助栈,直到把下一个需要弹出的数字压入栈顶为止;如果所有数字都压入栈后仍没有找到下一个弹出的数字,那么该序列不可能是一个弹出序列。

注意一个小细节:在比较的时候,不要手误写成类似if stack.pop() == pop_list.pop()的代码,比较完一轮之后,啥都没干列表就少元素了。。。检查了半天才发现emmm。只有在弹出的时候才能用pop。

面试题32:从上到下打印二叉树

【题目描述】:
二叉树的层序遍历。

【解题思路】:
借助队列实现。

扩展0:广度优先遍历有向图

同样可以基于队列实现,树是图的退化形式,层序遍历二叉树,本质上来说就是广度优先遍历二叉树。

扩展1:分行从上到下打印二叉树

【解题思路】:
不能直接打印一串,还要对节点进行分层。
需要额外设置两个变量,一个是当前层还有几个节点等待打印,一个是下一层有几个节点。

扩展2:之字形打印二叉树(重要)

【解题思路】:
需要维护两个栈,在打印一个栈里的节点时,将它的子节点保存到另一个栈中,当一层所有节点都打印完毕时,交换着两个栈,并继续打印下一层。

面试题33:二叉搜索树的后序遍历(重要)

【题目要求】:
输入一个数组,判断该数组是不是二查搜索树的后序遍历结果,返回True或False。

回顾:二叉搜索树,是一个二叉树,且满足左节点 < 父节点 < 右节点。

二叉树的后序遍历序列为:[5,7,6,9,11,10,8]

【二叉树后序遍历序列的规律】

最后一个数字是根节点的值,数组中前面的数字可以分为两部分,一部分是左子树节点的值,他们都比根节点的值小,另一部分是右子树节点的值,他们都比根节点的值大。

知道了上述规律,可以使用递归的方法实现。

【Attention】:本题需要特别注意的点是,每次递归左右子树之前,必须要保证左子树的所有数字都比root小,右子树的所有数字都比root大,否则没必要递归,直接返回False.

扩展0:二叉搜索树的前序遍历

也是同样的思路,只是前序遍历序列中,第一个数字是根节点的值。

面试题34:二叉树中和为某一值的路径(重要)

【题目描述】:
输入一个二叉树和一个整数,打印出二叉树中节点值的和为该整数的所有路径(从根节点到叶节点)。如图,给定二叉树和整数22,返回两条路径, [10,5,7]和[10,12]

【解题思路】:
这题其实是二叉树的深度优先遍历,在树的前序、中序、后序3中遍历方式中,只有**前序遍历是首先访问根节点的。**用递归的方式来解。
分析看这里,代码参考看这里。

4.4 分解让复杂问题简单化

面试题35:复杂链表的复制(掌握方法三的思路)

【题目描述】:
请实现函数ComplexListNode* Clone(ComplexListNode* pHead),复制一个复杂链表。在复杂链表中,每个结点除了有一个pNext指针指向下一个结点之外,还有一个pSibling指向链表中的任意结点或者NULL。

结点的定义如下:

struct ComplexListNode{int val;ComplexListNode* pNext;ComplexListNode* pSibling;
};

【解题思路】:【参考】
【方法1:(时间复杂度:O(N^2))】 分两步实现:

  1. 复制原始链表上的每一个结点,并通过pNext连接起来;
  2. 然后再设置每个结点的pSibling指针。

缺点:假设原始链表中某个结点N的pSibling指针指向结点S,那么就需要从头到尾遍历查找结点S,如果从原始链表的头指针开始,经过m步之后达到结点S,那么在复制链表中的结点N’的pSibling指针指向的结点也是距离复制链表s步的结点。通过这种办法就可以为复制链表上的每个结点设置pSibling指针。

【方法2:(时间复杂度:O(N), 空间复杂度O(N))】

方法1是通过链表查找来得到pSibling指针所指向的结点,实际上我们可以通过空间换取时间,将原始链表和复制链表的结点通过哈希表对应起来,这样查找的时间就从O(N)变为O(1)。具体如下:

复制原始链表上的每个结点N创建N’,然后把这些创建出来的结点用pNext连接起来。同时把<N,N’>的配对信息方法一个哈希表中;然后设置复制链表中的每个结点的pSibling指针,如果原始链表中结点N的pSibling指向结点S,那么在复制链表中,对应的N’应该指向S’。

【方法3:在不使用辅助空间的情况下实现O(N)的时间效率。】(重要,掌握)

  1. 先遍历链表,复制每个节点,clone的节点紧接在原节点后面,得到图示的一个交叉链表,复杂度O(n)。
  2. 设置复制出来的结点的pSibling。将原来的pSibling线复制到clone的节点上,此过程遍历一次就可以完成,复杂度O(n)。
  3. 把长链表拆分成两个链表,把奇数位置的结点用pNext连接起来的就是原始链表,把偶数位置的结点通过pNext连接起来的就是复制链表。 此过程复杂度O(n).

面试题36:二叉搜索树与双向链表(中序遍历)(重要)

题目要求:给定二叉搜索树,将该树转化为排序的双向链表,要求不能创建任何新的节点,只能调整树中节点指针的指向。

这个链接写的非常好 https://blog.csdn.net/u010005281/article/details/79657259
本题是二叉树中序遍历的一个应用,先中序遍历到最左侧的节点(即最小的元素对应的节点),然后将该节点设为双向链表的尾节点,在中序遍历的过程中,不断在其后添加新的节点,设置双向指针,并将尾节点后移,当遍历一轮后,双向链表即可生成。代码主要修改的是中序遍历的主体部分。

-------------2019.8.1二刷------------
一刷的解法创建了新的节点(尾节点),不符合题目要求。

所以,修改解法:(思路仍是递归的中序遍历)

  1. 递归的终止条件为:root为空,或者root为叶节点时,返回root值
  2. 中序遍历的主体部分为:先遍历左子树(左子树的返回值是左子树的根节点,并不是左子树中最右的节点,如上图所示),将左子树中最右的节点连到root的left指针上,然后进行root右子树的遍历(右子树的返回值是右子树的根节点,如上图,返回的是14,我们需要找右子树中最左的节点12),将右子树中最左的节点连到root的right指针上。
  3. 最后返回链表的头结点。

面试题37:序列化二叉树(二叉树遍历&二叉树重构)

题目要求:请实现两个函数,分别用来序列化和反序列化二叉树。
【二叉树的序列化】:把二叉树按照某种遍历方式(可以前序,中序,后序)的结果以某种格式保存为字符串,用特殊节点表示空节点
【二叉树的序列化】:序列化的反过程,利用字符串,重构二叉树。
这里写的 是前序遍历的序列及反序列化,序列化比较简单,重点是反序列化,相当于重建二叉树。需要加强。参考这里

面试题38:字符串的排列(全排列,递归)(重要)

题目要求:输入字符串,打印该字符串中字符的所有排列。
难点在于递归的实现,可以参考这篇博客,图文分析很详细 https://blog.csdn.net/u010591976/article/details/82048582

若含有重复字符,则要对全排列进行去重处理 https://blog.csdn.net/fuxuemingzhu/article/details/79513101

扩展:全组合(递归)(重要)

其实和全排列类似,也是用递归的方法实现,区别在于两点:

  1. 递归时传入的string字符串,由于是组合问题,所以ab和ba是一样的,没必要每次循环都传所有字符串进行,当首字符是a的时候,已经遍历了这种情况,因此,当首字符是b的时候,就只需要考虑和后面的字符之间的可能组合了。当遍历到最后一个字符时,只需要输出它本身即可。
  2. 全排列是要走到最后一步,才能输出完整的路径。但是对于全组合问题,需要每走一步打印一步,走到最后一步意味着终止。

    注意下图的关键点:任何一个路径,如果一旦到达最后一个字符(d),就意味着该路径的终止。

相关题目(正方体顶点和,国际象棋上放置皇后)

  1. 在正方体八个顶点上放数字,使得三组相对的面上的4个顶点之和相等。

  1. 8*8国际象棋上摆8个皇后,任何两个皇后不能处于同一行,同一列,同一对角线,问总共多少摆法?
    思路:每个皇后肯定占据一行,所以定义一个数组来表示皇后所在的列,i表示的是皇后所在的行,col[i]表示的是第i行的皇后所在的列,对col数组的八个值赋值0-7,然后对该数组全排列,这样一来,就能保证每个皇后不同行且不同列。只需判断每一个排列对应的皇后是不是在同一条对角线上即可,即判断j - i == col[j] - col[i] or i - j == col[i] - col[j],如果满足,则排除这个排列。

【举一反三】:
如果面试题是按照一定要求摆放若干个数字,则可以先求出所有排列,然后一一判断每个排列是否满足要求。

第五章 优化时间和空间效率

5.2 时间效率

面试题39:数组中出现次数超过一半的数字

【题目描述】
数组中一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为 9 的数组{1,2,3,2,2,2,5,4,2}。由于数字 2 在数组中出现了 5 次,超过数组长度的一半,因此输出 2.

【方法1:O(n)】
快速排序的思想,找到数组中第K大的数字的思路,出现次数超过一半的数字一定在第n/2位置上。

【方法2:O(n)】
题目中要找的数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现的次数的和还要多。
因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,

  • 如果下一个数字和当前我们保存的数字相同,则次数加 1;
  • 如果和当前我们保存的数字不同,则次数减 1;
  • 当次数减到 0 的时候,我们将保存的数字改为当前遍历所处的位置,并将次数更改为 1。

面试题40:最小的k个数

【题目描述】
输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

方法一:基于Partition的方法,复杂度为O(n),必须要修改输入数组

具体思路:通过快排的Partition函数,获取排序的主元位置r,判断r与k的大小关系,如果r=k或者r=k-1,直接返回前半部分的nums[:r]即可(为什么r=k或r=k-1都可以呢,因为主元前面的元素是最小的前k个,主元元素是最小的第k+1个,都已经找到了),如果r>k,对前半部分使用Patition函数,即可。

方法二:最大堆方法,复杂度为O(nlogk),无需修改输入数组, 适合海量数据

具体思路:维护一个含有k个元素的最大堆,最大堆的顶部为最大堆中最大的元素,遍历数组的元素,如果该元素比最大堆顶部的值还要大,则肯定不可能是最小的k个数,直接跳过,如果该元素小于最大堆顶部的元素,那么用这个元素替换堆顶元素,并且ShiftDown,找到该元素的合适位置。

两种解法的比较:

面试题41:数据流中的中位数

【题目描述】

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

【解题思路】

用一个最大堆来实现左边的数据容器,用一个最小堆来实现右边的数据容器,往堆中插入一个元素的复杂度为O(logn),由于只需要O(1)的时间就可以得到堆顶数据,因此得到中位数的复杂度为O(1)

【两个细节问题需要注意:】

  1. 要保证最大堆和最小堆中的数据平衡(长度之差不超过1),为了保证平均分配,可以在数据总数为偶数时,把新数据插入最小堆,在数据为奇数时,把新数据插入最大堆。
  2. 要保证最大堆中的所有元素都小于最小堆中的元素,因此会有特殊情况出现。当数据总数为偶数时,我们把新数据插入了最小堆,但这个新数据比最大堆中的一些数据小,此时应该把最大堆中的堆顶元素(最大元素)pop出来,append到最小堆中去,然后再把这个新数据append到最大堆中,再通过ShiftUp操作分别维护这两个堆。同理,当本来应该添加到最大堆中的新数据比最小堆中的一些元素还要大时,也要执行相应的操作。

【面试题】
给你k个有序数组,然后找出中位数,比如给了{1,2,8},{1,3,4},{2,5,9}三个数组,要求输出这k个有序数组的中位数为3。

【解题思路1】:

使用优先队列,把每个数组的第一个元素放进优先队列,然后输出最小的元素,并把最小元素的下一个数放进队列,~直到队列为空,这样就相当于是对k个数组排了序,输出中位数很容易,(其实如果记录下标的话,只需要排到中位数对应的下标即可,没必要遍历完)

【解题思路2】:

维护双堆,依次将k个数组中的所有元素插入堆中。如果两个堆数量一样,取两个堆顶元素求平均,如果数量差1,取数量大的那个堆的堆顶就是中位数。

面试题42:连续子数组的最大和

【题目描述】

例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和

【解题思路】:

遍历数组nums一遍即可实现,维护两个变量,一个是累加过程中的子数组和sum,一个保存的是最大的子数组和res。
判断条件:
如果sum < 0,那么相当于前面累加部分的结果都将被抛弃。
如果sum >=0,那么sum = sum + nums[i],并且不断更新res,保证res = max(res, sum)

【解法二】:动态规划
用res[i]表示以第i个数字结尾的子数组的最大和,则递推公式为:res[i] = max(array[i], res[i - 1] + array[i])

面试题43:1~n整数中1出现的次数

【题目描述】:

题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,1一共出现了5次。

【解题思路】:

采用递归的思路解决,将整数转换成字符串型,例如输入21345,拆成1-1345和1346-21345两部分,先来处理第二部分(第一部分可以递归处理):

  1. 首先分析1出现在最高位的次数:
    最高位大于1时,1出现在最高位(这里是万位)的次数共有10^4=10000次(当最高位等于1时(例如12345),1出现在最高位的次数为2345 + 1=2346次)
  2. 然后分析1出现在除最高位之外的其它四位数中:
    由于最高位是2,因此可以分为两段,1346-11345和11346-21345,每一段剩下的4位数字中,选择其中一位是1,其余三位可以在0~9这十个数字中任意选择,因此总共出现的次数为 2×4×103=80002×4×10^3=80002×4×103=8000次。

第一部分1-1345可以递归处理。

【注意】边界条件的处理,例如输出0、1等。

-----2019.8.9 更新----
leetcode233. Number of Digit One 数字1的个数就是这一题, 参考解法 GrandYang
解法:100以内的数字,除了10-19之间有11个‘1’之外,其余都只有1个。如果我们不考虑[10, 19]区间上那多出来的10个‘1’的话,那么我们在对任意一个两位数,十位数上的数字(加1)就代表1出现的个数,这时候我们再把多出的10个加上即可。比如56就有(5+1)+10=16个。如何知道是否要加上多出的10个呢,我们就要看十位上的数字是否大于等于2,是的话就要加上多余的10个’1’。那么我们就可以用(x+8)/10来判断一个数是否大于等于2。对于三位数区间 [100, 199] 内的数也是一样,除了[110, 119]之间多出的10个数之外,共21个‘1’,其余的每10个数的区间都只有11个‘1’,所以 [100, 199] 内共有21 + 11 * 9 = 120个‘1’。那么现在想想[0, 999]区间内‘1’的个数怎么求?根据前面的结果,[0, 99] 内共有20个,[100, 199] 内共有120个,而其他每100个数内‘1’的个数也应该符合之前的规律,即也是20个,那么总共就有 120 + 20 * 9 = 300 个‘1’。那么还是可以用相同的方法来判断并累加1的个数

面试题44:数字序列中某一位的数字

【题目描述】:

数字以01234567891011121314…的格式排列。在这个序列中,第5位(从0开始计)是5,第13位是1,第19位是4。求任意第n位对应的数字。

【解题思路】:

把序列根据数字的位数分成段,比如前10位是0-9这10个只有一位的数字,接下来的180位是90个10-99的两位数,接下来的2700位是 900个100-999的三位数,以此类推。
while循环,先找n在哪一段,然后找具体的那一位。

面试题45:把数组排成最小的数(重要)

【题目描述】:

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

【解题思路】:

为了防止数组拼接后越界,提前将所有数字都转成字符串处理。
这题的关键点在于,重新定义一个compare函数,用于比较数组中的数字,这个compare函数应该比较的是谁排在前面,而不是单纯的比较数字的大小。
https://blog.csdn.net/m0_38088359/article/details/82957670

自定义排序函数,用来修改sort()函数的key,使用functools 里的cmp_to_key

from functools import cmp_to_key # 调用cmp_to_key函数
def PrintMinNumber(nums):if not nums:return ""nums = list(map(str,nums))nums.sort(key=cmp_to_key(compare)) # 自定义compare函数return ''.join(i for i in nums).lstrip('0') # lstrip用于截掉字符串左边的空格或指定字符def compare(s1, s2):if s1 + s2 <= s2 + s1:return -1else:return 1

面试题46:把数字翻译成字符串

【题目描述】
给定一个数字,按照如下规则翻译成字符串:0翻译成“a”,1翻译成“b”…25翻译成“z”。一个数字有多种翻译可能,例如12258一共有5种,分别是bccfi,bwfi,bczi,mcfi,mzi。实现一个函数,用来计算一个数字有多少种不同的翻译方法。

【解题思路】(递归/动态规划问题)

从最小的问题开始,自下而上解决问题,可以避免重复,不断累加后面的结果,就可以逆序推出前面的结果。
定义函数res[i]表示从第i个数字开始的不同翻译数目,则有递推公式:
res[i] = res[i + 1] + g(i, i + 1) * res[i + 2]
解释:当第i位和第i+1拼起来在10~25范围内时,函数g(i,i+1)的值为1,否则为0.
https://blog.csdn.net/mabozi08/article/details/88931733

面试题47:礼物的最大价值(动态规划)

【题目描述】
在一个 m*n 的棋盘中的每一个格都放一个礼物,每个礼物都有一定的价值(价值大于0).你可以从棋盘的左上角开始拿各种里的礼物,并每次向右或者向下移动一格,直到到达棋盘的右下角。给定一个棋盘及上面个的礼物,请计算你最多能拿走多少价值的礼物?

【解题思路】
动态规划题,可以使用递归或循环的方法来求解(循环解法效率更高)。对于(x,y)处,一定有两种方法到达,从(x-1,y)或者(x,y-1)。如果要得到最大的礼物价值,上一步的时候肯定要选最大的。也就是max(f(left),f(up))。在具体的实现里面,这里构建了一个二维数组来存储各个位置所能得到的最大值。

还有改进版,将保存礼物最大价值的数组由二维压缩成一维数组来存储,一位数组的长度为列数,该数组前面j个数字分别是当前行(第i行)前面j个格子礼物的最大价值,之后的数字分别保存上一行(第i-1行)n-j个格子礼物的最大价值。

面试题48:最长不含重复字符的子字符串(动态规划)(重要)

【题目描述】:
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含从’a’到’z’的字符。例如,在字符串中”arabcacfr”,最长非重复子字符串为”acfr”,长度为4。

【解题思路】:(参考)
使用动态规划,记录当前字符之前的最长非重复子字符串长度f(i-1),其中i为当前字符的位置。每次遍历当前字符时,分两种情况:

1)若当前字符第一次出现,则最长非重复子字符串长度f(i) = f(i-1)+1。
2)若当前字符不是第一次出现,则首先计算当前字符与它上次出现位置之间的距离d。若d大于f(i-1),即说明前一个非重复子字符串中没有包含当前字符,则可以添加当前字符到前一个非重复子字符串中,所以,f(i) = f(i-1)+1。若d小于或等于f(i-1),即说明前一个非重复子字符串中已经包含当前字符,则不可以添加当前字符,所以,f(i) = d。

5.3 时间效率与空间效率的平衡

面试题49:丑数

【题目描述】:
我们把只包含因子2、3和5的数称作丑数(Ugly Number)。求按从小到大的顺序的第1500个丑数。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做第一个丑数。

【解题思路】:

【笨办法】:
使用遍历法求第k个丑数,从1开始遍历,如果是丑数则count++,直到count=k为止。那么如何判断丑数呢?根据丑数的定义,丑数只有2,3,5这三个因子,那么我们就拿数字除以这三个因子。具体算法如下:

  • Step1.如果一个数能够被2整除,那么让他继续除以2;
  • Step2.如果一个数能够被3整除,那么让他继续除以3;
  • Step3.如果一个数能够被5整除,那么让他继续除以5;
  • Step4.如果最后这个数变为1,那么这个数就是丑数,否则不是。

缺陷:所有整数都需要计算,即使一个数字不是丑数,我们还是需要对它做求余数和除法操作。因此该算法的时间效率不是很高,可以改进该算法,只计算丑数,而不在非丑数的整数上浪费时间。

【改进方法】:
假设数组中已经有若干个排好序的丑数,并且把已有的最大丑数记为M,接下来找下一个丑数。该丑数肯定是前面的某一个丑数乘以2、3或5的结果。

我们把得到的第一个丑数乘以2以后得到的大于M的结果记为M2。同样,我们把已有的每一个丑数乘以3和5,能得到第一个大于M的结果M3和M5。那么M后面的那一个丑数应该是M2,M3和M5当中的最小值:Min(M2,M3,M5)。所以我们需要维护三个变量p2,p3,p5,这三个变量分别指向的是数组中乘以2、3、5之后大于M的第一个数。

面试题50:第一个只出现一次的字符

【题目描述】:
在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出’b’。要求时间复杂度为O(n)。

【解题思路】:
维护一个哈希表,哈希表的key是字符,value是该字符出现的次数。第一次遍历时构造哈希表;接下来第二次遍历的时候,每扫描到一个字符时,就能从哈希表中得到该字符出现的次数,第一个只出现一次的字符就是需要的结果。

相关题目

相关题目一:从第一个字符串中删除在第二个字符串中出现过的所有字符

定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。例如从第一个字符串“We are students.”中删除在第二个字符串"aeiou"中出现过的字符串得到的结果是"W r Stdnts."。

【解题思路】:

我们可以创建一个用数组实现的简单哈希表来存储第二个字符串。这样我们从头到尾扫描第一个字符串的每一个字符时,用O(1)时间就能判断出该字符是不是在第二个字符中。如果第一个字符串的长度是n,那么总的时间复杂度是O(n)。

相关题目二:删除字符串中所有重复出现的字符

定义一个函数,删除字符串中所有重复出现的字符。例如输入"google",删除重复的字符串之后的结果是"gole"。

【解题思路】:

我们可以创建一个用布尔型数组实现的简单的哈希表。数组中的元素的意义是其下表看作ASCII码对应的字母在字符串中是否已经出现。我们先把数组中所有的元素都设为false。以google为例,当扫描到第一个g时,g的ASCII码是103,那么我们把数组下标为103的元素设为true,就知道g在前面已经出现了。即我们用O(1)时间就能判断出每个字符是否在前面已经出现过。如果是字符串的长度是n,那么总的时间复杂度是O(n)。

相关题目三:判断输入的两个字符串是不是互为变位词(Anagram问题)

在英语中,如果两个单词中出现的字母相同,并且每个字母出现的次数也相同,那么这两个单词互为变位词(Anagram)。例如silent与listen、evil与live等互为变位词。完成一个函数,判断输入的两个字符串是不是互为变位词。

【解题思路】:

我们可以创建一个用数组实现的简单哈希表,用来统计字符串中每个字符出现的次数。当扫描到第一个字符串中的每个字符时,为哈希表对应的项的值增加1。接下来扫面第二个字符串,扫描到每个字符串时,为哈西边对应的项的值减去1。如果扫描完第二个字符串后,哈希表所有的值都是0,那么这两个字符串就互为变位词。

题目二:字符流中第一个只出现一次的字符

【题目描述】:
实现一个函数,找出字符流中只出现一次的字符,例如,当字符流中只读出前两个字符时“go”时,第一个只出现一次的字符时“g”,当从字符流中读出前6个字符“google“时,第一个只出现一次的字符时”l“。

【解题思路】:
维护一个哈希表,哈希表的key为字符,字符对应的位置下标作为value,每次扫描到一个字符时,先判断这个字符对应的key是否已经在哈希表中了,如果已经在了,则说明该字符已经出现过了,不符合要求,将该字符对应的value设为-1,如果该字符对应的key不在哈希表中,则插入key,并且将value设为位置下标。
遍历哈希表,找到value值>=0的,且value最小的key,就是要输出的字符。

面试题51:数组中的逆序对(归并排序)

【题目描述】:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,在数组{7,5,6,4},逆序对总共有5对,{7,5},{7,6},{7,4},{5,4},{6,4};

【解题思路】:
先把数组分成子数组,统计出子数组内部的逆序对数目,然后再统计两个相邻子数组之间的逆序对的数目。

利用归并排序的思路,对数组进行分组,分成left和right两部分,组内进行排序,然后将排好序的left和right两部分再merge起来。 分组和排序的过程与归并排序完全相同,区别仅仅在于合并的部分,假定此时left和right两个子数组已经排好序了,分别使用两个指针指向left和right的尾元素,并比较两个指针指向的数字。如果left中的数字大于right中的数字,则构成逆序对,且逆序对的数目等于right数组中剩余元素的个数。如果left中的数字小于right中的数字,则不构成逆序对。每次比较时,将left和right中更大的数字添加进result列表中,并且将指针往前移,进行下一轮比较。

面试题52:两个链表的第一个公共节点

【题目描述】:
输入两个链表,找出它们的第一个公共结点。

【解题思路】:
方法1(蛮力法):
在第一个链表上顺序遍历每个节点,每遍历到一个节点,就在第二个链表上顺序遍历每个节点,如果第二个链表上有一个节点和第一个链表上的节点一样,说明两个链表在这个节点上重合,返回该节点即可。时间复杂度O(mn)(假设两个链表的长度分别为m和n)

方法2(栈):
将两个链表的节点分别存入两个栈中,然后从两个栈中分别弹出一个节点,如果两个节点相同,则弹出并进行下一轮,直到找到最后一个相同的节点。时间复杂度O(m+n),空间复杂度O(m+n)。

方法3:
首先遍历两个链表的长度,并且知道了长链表与短链表的长度差d。第二次遍历时,长链表先走d步,然后两个链表一起向前走,直到指向相同给节点为止。

第六章 面试中的各项能力

6.3 知识迁移能力

面试题53:在排序数组中查找数字(二分查找)

【题目描述】:
统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4。

【解题思路】:
用二分查找法即可解决问题,但是二分查找法只能找到一个数字3,由于3可能出现很多次,因此我们找到的3的左右两边可能都有3,因此要左右扫描,分别找到第一个3个最后一个3的位置。

方法1:找到一个3之后,左右两边顺序扫描,分别找出第一个3和最后一个3,复杂度O(n)(因为数组中可能全是3)

方法2:分别定义两个函数GetFirstK和GetLastK,使用二分查找算法分别找到第一个3和最后一个3的下标。即可得到3出现的次数, 复杂度O(logn)。

题目二: 0~n-1中缺失的数字(二分查找)

【题目描述】:
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0 ~ n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

【解题思路】:
【方法1】
先用公式n(n−1)2\frac{n(n-1)}{2}2n(n−1)​求出+数字0~n-1的所有数字之和,记为s1,接着求出数组中所有数字的和,记为s2。那个不在数组中的数字就是s1-s2的差。复杂度O(n)

【方法2】(重要)
因为数组一开始的一些数字与他们的下标相同,如果说不在数组中的那个数组记为m,那么所有比m小的数字下标与他们的值都相同。可以基于二分查找:

  • 如果中间元素的值和它的下标相等,那么下一轮只需要找它的右半边
  • 如果中间元素的值和它的下标不等,并且它前一个元素和它的下标相等,意味着这个中间的数字就是第一个值与下标不等的数字。
  • 如果中间元素的值和它的下标不等,并且他前面的元素和它的下标相等不等,那么只需查找左半边。

题目三: 数组中数值和下标相等的元素(二分查找)

【题目描述】:

假设一个单调递增的数组里每个元素都是整数并且是唯一的。请实现一个函数,找出数组中任意一个数值等于其下标的元素。例如,在数组{-3,-1,1,3,5}中,数字3和它的下标相等。

【解题思路】:

方法一(遍历):从头到尾依次扫描数组中的数字,逐一检验数字是不是和下标相等。复杂度O(n)

方法二(二分查找):由于数组单调递增且唯一,所以使用二分查找。

  • 如果mid=nums[mid],则返回mid;
  • 如果mid>nums[mid],则mid左侧的元素肯定不再匹配。如果mid=n-1,则返回-1,否则令low=mid+1;
  • 如果mid<nums[mid],则mid右侧的元素肯定不再匹配。如果mid=0,则返回-1,否则令high=mid-1;

面试题54:二叉搜索树的第k大节点

【题目描述】:
给定一棵二叉搜索树,找出其中第k大的节点。例如,在下图的树里,按节点值大小排序,第三大节点的值是4。

【解题思路】:
按照中序遍历的顺序,可以得到有序数组,在有序数组中找第k大的值,时间复杂度为O(1)。

面试题55:二叉树的深度

题目一:二叉树深度

【题目描述】:
输入一棵二叉树的根结点,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

【解题思路】:
二叉树的深度,等于左右子树深度较大的值+1,递归求解即可。

def TreeDepth(root):if not root:return 0return max(TreeDepth(root.left), TreeDepth(root.right)) + 1

题目二:平衡二叉树

【题目描述】:
输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。例如,下图中的二叉树就是一棵平衡二叉树。

【解题思路】:
可以利用上面的TreeDepth函数,递归求解

def IsBalenced(root):if not root:return Falseif abs(TreeDepth(root.left) - TreeDepth(root.right)) <= 1:return Trueelse:return False

面试题56:数组中数字出现的次数

相似题目:leetcode136. Single Number, 137. Single Number II, 260.Single Number III

题目一:数组中只出现一次的两个数字(异或)(重要)

【题目描述】:
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度O(n),空间复杂度O(1)。

【解题思路】:

【思路概括:】
由于异或运算的性质,如果数组中有两个只出现一次的数字A和B,那么遍历一轮,异或一次后,最后得到的结果就是A和B的异或结果,由于A不等于B,那么A和B至少有一位不一样,根据不一样的这一位,将数组分成两部分,此时A和B就被分到了不同的组,分别在这两个组内,再异或一遍,得到的结果分别就是A和B。

【详细思路:】
异或运算的性质:任何一个数字异或它自己都等于0 。0异或任何数都等于任何数。
也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。

有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其它数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。

我们还是**从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。**因为其它数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,也就是说在这个结果数字的二进制表示中至少就有一位为1 。我们在结果数字中找到第一个为1 的位置,记为第N 位。现在我们以第N 位是不是1 为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。

现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。因此到此为止,所有的问题我们都已经解决。

代码很简洁 https://blog.csdn.net/fuxuemingzhu/article/details/79688059

题目二:数组中唯一只出现一次的数字(位运算)(重要)

【题目描述】:
一个数组里除了一个数字只出现一次之外,其他的数字都出现了三次。请找出那个只出现一次的数字。

【解题思路】:
由于其它数字出现了三次(奇数次),因此不能使用异或来抵消运算,但是可以使用位运算。如果一个数字出现三次,那么它的二进制的每一位也出现三次,如果把所有出现三次的数字的二进制表示的每一位加起来,那么每一位的和都能被3整除。

所以解法是:把数组中所有数字的所有位加起来,如果那一位的和能被3整除,说明只出现一个的那个数字的对应位为0,否则对应位为1。

面试题57:和为s的数字

题目一:和为s的两个数字

【题目描述】:
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出任意一对即可。

【解题思路】:
使用两个指针i和j,分别指向数组的开始和末尾,计算nums[i] + nums[j] 的值,如果等于S,则输出,如果小于S,i指针后移,否则j指针前移。直到两个指针重合为止。复杂度O(n).

题目二:和为s的连续正数序列

【题目描述】:
输入一个正数s,打印出所有和为s的连续正数序列(至少含有两个数)。例如,输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以打印出三个连续序列:1-5,4-6,7-8。

【解题思路】:
用两个数start和end分别表示序列的最小值和最大值。首先把start初始化为1,end初始化为2。计算start到end的和current_sum,分三种情况讨论:

  • 如果大于s:则可以从序列中去掉较小的值(start加一,并从current_sum中减去start)
  • 如果小于s:则增大end的值(并且在current_sum中加上end)
  • 如果等于s:则输出当前序列,并且将end加一,继续循环,循环的终止条件为start >= end.

面试题58:翻转字符串

题目一:翻转单词顺序

【题目描述】:
输入一个英文句子,翻转句子中单词的顺序,但是单词内字符顺序不变,为了简单起见,标点符号与普通字母一样处理。例如:输入“I am a student.”,则输出“student. a am I”。

【解题思路】:
c++的思路:第一步翻转句子中的所有字符,第二部翻转每个单词中的字符顺序。
python写法很简单,两行代码可搞定(但这种写法完全不含任何算法思想在里面)

def ReverseSentence(string):if not string:returnres = list(reversed(string.split()))return ' '.join(res)

题目二:左旋转字符串

【题目描述】:
字符串的左旋转操作是把字符串前面的若干字符转移到字符串的后面。请定义一个函数实现字符串左旋转操作的功能。比如:输入字符串"abcdefg"和数字2,该函数将返回左旋转2位得到的结果"cdefgab"。

【解题思路】:
以"abcdefg"为例,根据需要翻转的部分,将字符串分成两部分“ab”和“cdefg”,先分别翻转这两部分,得到“bagfedc”,接下来翻转整个字符串即可得到“cdefgab”。

面试题59:队列的最大值

题目一:滑动窗口的最大值(重要)

【题目描述】:
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1},{2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1},{2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

【解题思路】:
方法一:
设置滑动窗口为k,可以扫描每个滑动窗口的所有数字并找出其中的最大值。如果滑动窗口为k,则需要O(k)时间才能找出滑动窗口的最大值。对于长度为n的输入数组,该方法的时间复杂度为O(kn)。
方法二:
使用一个双向队列(所谓双向队列,是指队头和队尾都可以出元素)作为辅助。队列中存放数组的下标,主要步骤:

  1. 遍历数组,取出元素,如果此时队列为空,直接将元素加入队列
  2. 如果取出数组元素的下标和队列头元素的下标的间距超过了滑动窗口的大小,就将队列头元素弹出,这一步保证了队列中的元素的个数永远小于滑动窗口的大小。
  3. 如果取出的数组的元素大于队列中已有的元素,就弹出比数组元素小的元素
  4. 向队列中添加元素。

题目二:队列的最大值

【题目描述】:
定义一个队列并实现函数max得到队列里的最大值。要求max,pushBack,popFront的时间复杂度都是o(1)。

【解题思路】:(滑动窗口)

push和pop是队列的常规操作,无须赘述。本题的关键点在于max函数的维护, 维护的方法是:使用一个list存储队列的最大值,列表中的第一个元素存储的永远是队列中的最大值,每次调用max函数时,只需将return list[0],即可在O(1)复杂度内完成;维护list的具体方法:

  • push_back的时候,需要比较新加入的item与队列中最后一个元素的大小,pop掉所有比该item小的值后,将当前item加进list中;(如果当前item是队列的最大值,该操作会将list清空,然后将item加入进去)
  • pop的时候,每弹出队列中的一个item后,需要比较该item是不是当前队列的最大元素,如果是的话,则list中的list[0](队列最大值)也应该被pop掉。

6.4 抽象建模能力

面试题60:n个骰子的点数(重要)

【题目描述】:
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s,输入n,打印出s的所有可能的值出现的概率。

【解题思路】:
定义两个数组。数组的长度为6*n,即可能出现的最大和。数组每个元素的值代表出现的次数,比如probability[5]表示点数和为5出现的次数。在一轮循环中,一个数组记录之前一次所有出现的情况,一个数组记录这一次的所有出现的情况。例如这一次出现和为n的次数,应该等于另一个数组里记录的n-1,n-2, n-3, n-4, n-5, n-6的和。

代码参考 https://blog.csdn.net/mabozi08/article/details/88960673

面试题61:扑克牌中的顺子

【题目描述】:
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。其中A为1,J为11,Q为12,K为13,而大小王为0,且大小王能够当做任意一张牌。

【解题思路】:

  1. 排序
  2. 计算0的个数
  3. 计算相邻数字的“距离”,并且保证除0外相邻数字不能重复。(非零数字如果有重复,一定不能构成顺子)
  4. 比较“距离”是否小于0的个数。

面试题62:圆圈中最后剩下的数字-约瑟夫环问题

【题目描述】:
题目:0,1,…,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

【解题思路】:
https://blog.csdn.net/mabozi08/article/details/88984751

面试题63:股票的最大利润

【题目描述】:
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可获得的最大利润是多少?例如,一只股票在某些时间节点的价格为{9,11,8,5,7,12,16,14}。如果我们能在价格为5的时候买入并在价格为16时卖出,则能获得最大的利润为11.

【解题思路】:
参考这里 https://blog.csdn.net/aaon22357/article/details/91357655

面试题64:求1+2+…+n

【题目描述】:
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

【解题思路】:递归。

面试题65:不用加减乘除做加法(位运算)

【题目描述】:
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

【解题思路】:
不用四则运算,那就只能用位运算了。

  • 第一步:先按位加法,不进位,具体的:通过异或运算来实现,一个0和一个1的时候,异或结果为1,两个0或者两个1的异或结果为0
  • 第二步,考虑进位问题。具体的:两个数做与运算(只有1&1=1,产生进位),然后再向左移动一位。
  • 第三步:前两步结果相加,,直到不产生进位为止。
    https://www.jianshu.com/p/21fd1598d4ae

【扩展】使用位运算实现加减乘除

https://github.com/HoGiggle/learning/blob/master/leetcode/ElementOperator.py

面试题66:构建乘积数组

【题目描述】:
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。

【解题思路】:
常规思路:正常连乘的话时间复杂度为O(n^2),效率非常低。而且考虑到计算每个B[i]时都会有重复,思考B[i]之间的联系,找出规律,提高效率。

优化方法:将B[i]看错过看成A[0]*A[1]*...*A[i-1]A[i+1]*...*A[n-1]两部分的乘积,因此,数组B可以用一个矩阵来创建。如图所示,B[i]为矩阵中第i行所有元素的乘积。 复杂度O(n)

  • B[i]的左半部分(红色部分)和B[i-1]有关(将B[i]的左半部分乘积看成C[i],C[i]可以自上而下计算 得到,有C[i]=C[i-1]*A[i-1]),
  • B[i]的右半部分(紫色部分)与B[i+1]有关(将B[i]的右半部分乘积看成D[i],D[i]可以自下而上计算得到,有D[i]=D[i+1]*A[i+1]),
  • 因此我们先从0到n-1遍历,计算每个B[i]的左半部分; 然后定义一个变量temp代表右半部分的乘积,从n-1到0遍历,令B[i]*=temp,而每次的temp与上次的temp关系即为temp*=A[i+1]

面试题67:字符串转换成整数

【题目要求】:
实现C++中StrToInt功能,输入字符串,转成整数输出。(leetcode 8)

【解题思路】:
这一题关键在细节的处理,分为以下几个情况:

  1. 定义一个变量标记输入是否合法,如果输入为空指针,则标记该变量并且返回(为了防止无法区分空指针和输入“0”的情况)
  2. 遍历字符串,先看第一个字符是否为正负号,先处理正负号问题;
  3. 在遍历过程中,如果遇到“0”-“9”以外的数,终止遍历,如果遇到的是数字,就把数字累加上去;
  4. 最后输出的结果需要判断是否溢出。

面试题68:树中两个节点的最低公共祖先

https://blog.csdn.net/qq_25827845/article/details/74612786
【题目描述】:
给定一个二叉树,和树中的任意两个节点,求这两个节点的最低公共祖先。

限制条件1:这个二叉树是二叉搜索树
【解题思路】:
二叉搜索树是经过排序的,位于左子树的节点都比父节点小,位于右子树的节点都比父节点大。既然要找最低的公共祖先节点,我们可以从根节点开始进行比较。

  • 若当前节点的值比两个节点的值都大,那么最低的祖先节点一定在当前节点的左子树中,则遍历当前节点的左子节点;
  • 反之,若当前节点的值比两个节点的值都小,那么最低的祖先节点一定在当前节点的右子树中,则遍历当前节点的右子节点;
  • 直到找到一个节点,位于两个节点值的中间,则找到了最低的公共祖先节点。

递归的终止条件是:当根节点为空,或者其中一个节点等于根节点的时候,返回根节点

限制条件2:这个二叉树是一颗普通的二叉树
【解题思路】:
(递归的方法)递归的终止条件和二叉搜索树一样,当根节点为空,或者其中一个节点等于根节点的时候,返回根节点。递归的主体部分,分别在根节点的左右子树上进行递归调用。

(迭代解法)需要我们保存下由root根节点到p和q节点的路径,并且将路径存入list中,则问题转化为求两个list集合的最后一个共同元素。


其它面试常见问题总结

Top-k系列问题

https://blog.csdn.net/u010355144/article/details/44985595
【问题】:
无序数组中的前k大元素?第k大元素?

解法:

  • 求前k大元素——最小堆 O(nlogk)
  • 求第k大元素——Partition(快排)O(logn)
  • 求前k小元素——最大堆
  • 求第k小元素——Parttion

【总结】:Top-k类问题的套路无非这么几种:

  • 如果需要第k个,考虑使用快排的Partation
  • 如果要前k个,两种方案:
    1. 使用最大堆或最小堆
    2. 局部淘汰法,使用哈希表或其它方式将数据分成多组,组内求Top-k,然后归并排序。

海量数据问题

的Topk/中位数/众数问题
【问题】:
一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。

解法:用trie树(前缀树)统计每个词出现的次数,时间复杂度是O(n*le)(le表示单词的平准长度)。然后是找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(nlog10)。所以总的时间复杂度,是O(n*le)与O(nlog10)中较大的哪一个。

【问题2】:
一个文本文件,找出前10个经常出现的词,但这次文件比较长,说是上亿行或十亿行,总之无法一次读入内存,问最优解。

解法:首先根据用hash并求模,将文件分解为多个小文件,对于单个文件利用上题的方法求出每个文件件中10个最常出现的词。然后再进行归并处理,找出最终的10个最常出现的词。

【只用2GB内存,在20亿个整数中找到出现次数最多的数】
题目:

    有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数。限制内存只有2GB。

解法:

    32位bit,占用4Byte,20亿,就是8GB大小。2GB内存,很明显,我们无法直接加载到内存。

所以继续是分而治之的思路,我们可以把这个大文件,hash成20个小文件,这样每个文件,加载到内存,只需要400mb。

我们可以先后加载每个小文件,计算统计里面每个整数的出现次数。最后只取top 1.

【剑指offer】解题思路汇总相关推荐

  1. 剑指offer解题思路锦集11-20题

    又来更新剑指offer上的题目思路啦. 11.[二进制中1的个数] 题目:输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. eg:NumberOf1(1)=1 NumberOf1(2) ...

  2. 《剑指Offer》题解汇总索引表(leetcode)

    <剑指Offer>题解汇总索引表(leetcode)

  3. 剑指offer (From Leetcode) 汇总

    剑指 Offer 03. 数组中重复的数字 找出数组中重复的数字. 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知 ...

  4. 《剑指 Offer》题目汇总

    文章目录 1. 数组 2. 链表 3. 栈和队列 4. 哈希表 5. 字符串 6. 树 7. 堆 8. 回溯和深度优先搜索 9. 递归和循环 10. 双指针 11. 动态规划 12. 贪心算法 13. ...

  5. 剑指offer解题记录(JAVA)

    面试题3:数组中重复的数字 题目链接 import java.util.Arrays;/*** P39 面试题3:数组中重复的数字* 在一个长度为n的数组里所有数字都在0~n-1的范围内 数组中某些数 ...

  6. 过分!虾皮被曝大范围毁约;深度学习技巧全辑;MongoDB开源替代 4.7K★;剑指Offer解题代码;大数据算法笔记汇总;前沿论文 | ShowMeAI资讯日报

    ShowMeAI日报系列全新升级!覆盖AI人工智能 工具&框架 | 项目&代码 | 博文&分享 | 数据&资源 | 研究&论文 等方向.点击查看 历史文章列表, ...

  7. java翻转单词顺序split_剑指offer解题报告(Java版)——翻转单词顺序 左旋字符串 42...

    引言 这种翻转的问题会遇到很多,其实就是一个倒序的问题,对于第一个题只是想翻转单词的顺序,而并不想把整个字符串翻转了,如果完全翻转的话,比如I am a student.中所有字符翻转得到.tnedu ...

  8. java计算筛子概率_剑指Offer解题报告(Java版)——n个骰子的点数 43

    问题 n个骰子朝上的数之和为s,求s的所有可能以及概率 分析问题 如果是用笨方法,一般人最开始都会想到笨方法,那就是枚举法 举个例子,比如两个骰子,第一个骰子的结果为1,2,3,4,5,6,两个骰子的 ...

  9. java 数组中某个数出现的概率_剑指Offer解题报告(Java版)——排序数组中某个数的个数 38...

    分析问题 问题只需要找到排序数组中某个数K的个数,由于已经是排序了,K一定是在一堆的,所以我们只需要找到第一个K的index1,然后找到最后一个K的index2就可以了 而寻找K的过程我们一般通过二分 ...

  10. C++剑指offer:解题报告之DP优化学习记 (二) ——浅论DP斜率优化 (Print Article 【HDU - 3507】 )

    链接:https://share.weiyun.com/5LzbzAc 目录 前言 斜率优化前期准备 1.从状态转移方程出发 2.推理状态转移方程 对结论的进一步推导 干货!综合结论 判断斜率大小的方 ...

最新文章

  1. valgrind概述及错误分析
  2. mysql 存储引擎 介绍
  3. Eureka-服务注册
  4. 关于昌平100度健身俱乐部全民健身情况调查报告
  5. spark 上游rdd的缓存
  6. 强人工智能基本问题:神经网络分层还是不分层
  7. 【算法基础】动态规划的理解
  8. java笔记--查看和修改线程名称
  9. 全面超越Swin Transformer | Facebook用ResNet思想升级MViT
  10. 买了个国产平台,竟然无法自行安装系统
  11. Atitit.excel导出 功能解决方案 php java C#.net版总集合.doc
  12. CMU 15-213 Introduction to Computer Systems学习笔记(7) Machine-Level Programming-Data
  13. layui - 模板引擎
  14. Collection、List、泛型、数据结构
  15. 箱形图的优缺点,python绘制箱形图
  16. Twincat3报错AdsWarning: 4115 (0x1013, RTIME: system clock setup fails
  17. 荣耀8X成为全球首款通过TUV莱茵低蓝光认证的手机
  18. 解线性方程组的迭代法--大型稀疏矩阵---松弛迭代法C++实现
  19. 计算机无法ghost安装系统安装系统安装,详细教您重装Ghost系统错误怎么办
  20. 不确定度在线计算_测氡仪校准结果不确定度产生的5个因素

热门文章

  1. 如何让nextcloud支持avi文件在线播放
  2. 【前端面试题】前端基础 | 八股文 | HTTP网络 | Vue | React
  3. 这样的牛皮凉席清水席要慎买!!!
  4. 十三、买空卖空、融资融券、配资与杠杆
  5. godot mysql_go godotenv配置环境变量
  6. M1 Mac使用photoshop液化、存储为web格式黑屏如何解决
  7. 史上最详细的新浪广告系统技术架构优化历程
  8. 南卫理公会计算机官网,南卫理工会大学
  9. 2021-09-06心率体脂秤模块
  10. Java如何将文件打包成Zip、Rar压缩包