leetcode解题思路(无代码) 归类汇总版,面试笔试经典例题

我将舍弃没有巧妙解法的简单题、部分题干、代码实现、非关键步骤,
目的是做成一篇每次面试前都能过一遍的思路问答题。
在我仅有的面试经验中,面试官似乎会按知识点提问。因此我认为归类极其重要。
简单的题不一定简单(自以为答对,实则因解法拙劣而扣分),
难的题不一定难(冷静地回答一部分思路也是可以的)。

https://github.com/azl397985856/leetcode(尚未开始)

文章目录

  • 纲领的纲领
  • 编程题的沟通
  • 常见健壮性问题
  • hash相关
  • 链表相关
  • 双指针
  • 遍历扫描
  • 快慢指针
  • 区间合并
  • 滑动窗口
  • 进制问题/大数模拟
  • 模拟
  • 随机算法
  • 栈和队列
  • 基本排序算法
  • 二叉树
  • 递归与回溯
  • 动态规划
  • 字典树
  • 二分查找
  • 循环排序
  • 数论/位运算
  • 贪心
  • 附录

纲领的纲领

优化空间复杂度时,尝试利用输入数据所在的容器。
数组题主要技巧是:排序,双指针,滑动窗口
链表题主要技巧:dummy,快慢指针,需要求长度就先求长度,需要改结构的就递归
二叉树一般是递归,但迭代遍历要会,特别是中序迭代。
平时做题形成特判的习惯,即使不存在边界问题,也可以特判一下针对一部分用例进行加速。目标是达到不特判不舒服的程度。

编程题的沟通

先讲思路,再写代码。
问能不能修改输入数据。
问时间优先还是空间优先。
可以要提示,但不能说不会。

常见健壮性问题

leetcode刷习惯了,本地写就容易出问题,因为leetcode会给出数据结构定义和输入范围。
第一句就问数据结构和输入范围。
整数可能溢出,
树可能不是二叉树,
指针不一定有。

hash相关

  • (#202)快乐数
    判断是否进入无限循环,hash可以胜任。

  • (#1)两数之和
    给定一个整数数组 nums 和一个整数目标值 target,找出和为目标值的那两个整数。
    如果结果允许重复(或者只需要找到任意一个)的话,用map记录补数的位置,O(n)。不允许重复的话就是先排序然后首尾双指针。

  • (#454) 四数相加
    这里需要辨析一下排序+双指针 vs hash。
    前者只能降一个数量级,而且排序本身是nlogn:三数之和是n平方,四数之和是n立方。此外它会破坏索引。
    hash法其实是一分为二,两数之和是n+n,三数之和是n+n平方,四数之和是n平方+n平方。但hash法要保证最终元组不重复,需要额外做去重(没排序的代价)。
    本题只要求索引不同而不要求最终元组不同,就不存在去重问题,用hash法更优。

  • (剑指offer 35)复制链表,链表除了next指针,还有一个sibling指针指向链上随机位置
    遍历一次旧链表,按next new出来,存<旧节点,新节点>的map;再遍历一次旧链表设sib指针。

链表相关

  • (#19)删除链表的倒数第 n 个结点
    两个指针,第一个先走n步,然后两个指针并进直到第一个走到头。
    其实思路是类似的,单向链表的问题就是不能走回头路,不能从尾部开始遍历,而两个指针可以解决这些问题。

  • (#25)链表,每 k 个节点一组进行翻转
    先写出普通的就地翻转,然后在处理第二组的时候把第一组连接到第二组,依此类推。这题很容易exception。
    普通的就地翻转,分为递归和非递归。非递归需要三个始终相邻的指针pre、cur、nxt。递归需要在当前结点和当前的下一个结点之间新建一个指针、擦除一个指针,然后注意把新的head层层回传。

  • (#61)旋转链表,将链表每个节点向右移动 k 个位置
    先求长度,k模长度。然后就是寻找倒数第k个位置,跟#19类似。

  • (#138)复制带随机指针的链表
    仿照#25的思路,先从经典场景出发,思考复制普通链表时的操作:依靠结点序号和结点value的对应关系。要想复制random信息,就要通过map转化为地址无关,如结点序号。恢复数据时将节点序号转回地址即可。
    如果想给一个数据结构增加一个域,办法之一是map。
    链表如果建立了<序号,node>的map,与数组就很相似了。

双指针

数组、链表、字符串都有可能用到双指针。

  • (#344)反转字符串
    交换首尾,是一种不用库函数、不使用额外空间的方法。
    s[:]=s[::-1]也行,这里前面得用s[:]而不是s,才能确保in-place修改。[::-1]是固定写法,如果只翻转列表的一部分,应该写a[start:end]=a[start:end][::-1]。性能实测很好。

  • (剑指offer 58)左旋转/右旋转字符串
    不使用额外空间:自己写首尾交换的就地翻转,对前半、后半、整体做三次翻转即可。

  • (#11) 柱状统计图,判断选择哪两个挡板能盛放最多水。
    最终必然要选两个挡板,所以可以用双指针。

  • (#15)判断数组中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有和为 0 且不重复的三元组。
    先排序。外层循环定第一个数,从小到大的顺序。然后就变成了两数相加问题,两个指针一个从左边出发,一个从右边出发。

  • (#26)给定一个排序数组, 原地删除重复出现的元素
    数组的短板是删除。如果需要删除多个元素,可以考虑双指针,第一个指针指向编辑位,第二个指针指向可能要留下的元素,这样就能O(n)解决。
    此题的变式不少,比如删除数组中的偶数,或者将字符串中的空格替换为%20。数组删除/替换问题基本双指针没跑。元素总数增多就从后往前复制。

  • (剑指offer 57)和为s的连续正数序列,如输入15,输出1-5、4-6、7-8。
    少时右指针往右,多时左指针往右。双指针初始化为1和2,整体从0往右滑,就不会漏。

遍历扫描

  • (#42)接雨水。柱状统计图,从上方下雨,统计能容纳的雨水容积。
    这题要先想出暴力解法:对每个位置都向左右寻求最大值,取小者后与当前位置作差。
    然后才有从左往右扫描一遍、从右往左扫描一遍的优化版(O(n))。
    一个简单版是买卖股票的最佳时机(#121),只需要从右往左扫描一遍即可。

快慢指针

  • (#141)链表中是否有环
    这是快慢指针的经典应用。除此之外,也可以遍历存hashmap。要找环入口点的话,相遇点和起点同时出发,相遇的就是环入口。
    本题有不少变式,实际上状态机可以用单向链表来表示。比如,(#202)快乐数(求各位平方和)。这一题没有明说是链表,但其实是一个状态转换的问题。
  • (#876)求链表的中间结点,实际上求任意等分点都能用快慢指针。
  • (#19) 删除倒数第n个链表结点,快慢指针和栈都可以做。

区间合并

  • (#56)合并区间(合并一些部分重叠的区间)
    类似的变式还有奶牛挤奶、买菜聊天等(给若干区间,求出有效区间长度或最长有效区间),都是按左端点排序。(#763)也是这类问题,合并26个区间即可,每个小写字母有一个区间,记录其第一次和最后一次出现。

滑动窗口

真实的滑动窗口首尾应该是不同步、只滑一次的。
虚假的滑动窗口是长度固定然后又从头换个长度再来,这其实根本就是暴力法。
外层循环是右边指针逐渐加一,内层循环则视情况。
滑动窗口也有点一看就会、一写就TLE的感觉。

  • (#209)长度最小的子数组
    给定目标sum,问至少要多少个连续数,求和后可以达到。
  • (#76) 最小覆盖字串
    模板,但实现的时候因为过多调用’'.join()导致tle。
  • (#904) 水果成篮
    模板,记得用python的counter。

进制问题/大数模拟

  • (#258)各位相加, 反复将各个位上的数字相加,直到结果为一位数
    问题的本质是,给定a x 100 + b x 10 + z,求a + b + c。所以mod 9即可。对9的倍数需要特判。

  • 快速幂和快速乘(除了快,还能防溢出
    这两个放在一起说,是因为思路和代码都极其相似。
    https://blog.csdn.net/liangllhahaha/article/details/82119378
    把指数(乘数)做二进制表示,并从右往左一次取1bit。
    快就快在,base变量每轮循环自乘(自加),因此其增长速度远超初始base。

  • 从字符串形式的数中取出k个数,使最小/最大(有点进制的意思,前面的权值大,后面的权值小)
    https://leetcode-cn.com/problems/remove-duplicate-letters/solution/yi-zhao-chi-bian-li-kou-si-dao-ti-ma-ma-zai-ye-b-4/
    这篇题解非常牛,一下讲了四道题,四道题循序渐进,但本质一样。

  • (#316)去除重复字母,使得每个字母只出现一次,需保证返回结果的字典序最小,保留原相对顺序
    问题等价于如何决定每个字母的去留。
    保留顺序的最大/最小问题,思想都一致,就是从前往后处理(因为前面的权值大),看下一位后马上决定这一位的去留。这样做是无法确定删除次数的,不过只需做如下考虑:
    如何避免多删?对于本题,确保最后至少剩一个就行,用map。(#402)移除k位数字,作为模板题更为直接,直接给定了删除次数上限k。
    如何避免少删?(#402)直接取前n-k位(因为前面的权值大),尾巴全部删掉;对于本题,也是尽量取前面的,只要维持每个字母最多出现一次即可。

  • (#7)整数翻转(12345->54321)
    如果一个int类型的数值的绝对值已经大于“INT_MAX / 10”,或者它等于“INT_MAX / 10”但是其下一个将要相加的数字大于了7(因为IN_MAX=2147483647),那就说明它溢出了。

  • (#9)回文数
    负数和整十数要特判。

  • (剑指offer 43)1-n整数中1出现的次数
    稳妥起见,先用%10和/10的循环法给个解。不过除法和求余较慢。以下为数字规律法。
    对个、十、百…位可能的1进行累加。当前位分0/1/2-9三种情况,由高/低位值和本位级得结果。

  • (剑指offer 44)数字序列中某一位的数字
    关键是补位。代码实现上,i无限增大,字符总数第一次足够时就可算出并return。
    leetcode解题思路(无代码) 归类汇总版,面试笔试经典例题_第1张图片

  • (剑指offer 45) 把数组排成最小的数,连接整数数组中的所有数,找到最小方案
    首先,拼接有溢出问题,需要转成字符串。
    定义一种新的比较规则:如果concat(a,b) 可以证明这种规则具有自反/对称/可传递性,用反证法可证按这种规则所得序列就是最小序列。

模拟

  • (#73)m x n矩阵置零,如果一个元素为 0,则将其所在行和列的所有元素都设为 0(要求原地)。
    先遍历一遍矩阵,记录有零的行和列,完毕后再修改原矩阵。这样需要m+n的额外空间。
    如果把记录放在行首和列首,就不需要额外空间。
    这题提供了一种启发,lazy操作所使用的标志位可以利用操作对象本身的field,可以节省空间。

  • (剑指offer 4)二维数组,左小右大,上小下大,查找数
    从右上角开始,如果过大,放弃整列,否则放弃整行,逐渐往左下角收缩。

  • (剑指offer 66)构建乘积数组b,b[i]等于数组a中除第i个元素以外的乘积,不使用除法
    直接做的话,有很多重复运算。画一个矩阵,第i行是b[i],第i列是a[i],矩阵元素表示是否参与。
    将b[i]看成两部分的乘积,对角线上都是false,矩阵被分为两部分,这两部分都可以累进求出。

  • (剑指offer 29)顺时针打印矩阵
    外层循环,循环圈数次,起点总是x=y;里层循环负责用4步打印一圈。要测一行/一列的情况。

  • (#48)旋转矩阵
    循环写法跟上题相似。先把坐标和循环次数的关系找好,然后四个元素旋转交换。

  • (剑指offer 62)约瑟夫环,从n元素环中删除第m个数字,求最后剩下的数字
    删除后从删后第一个元素开始数,所以f变成了g,+m求余的操作模拟了这种旋转操作。
    f(n, m) = g(n-1, m) = (f(n-1, m) + m) % n

  • (剑指offer 67)字符串转整数
    整数可能溢出,先检查字符合法性和长度,注意空串。合法性给个全局变量。开头可以是+,-。

随机算法

题外话:以生成1-9随机数为例,random()*8+1即可。一般方法是乘以区间大小,然后加上下界。

  • (#384)打乱数组,实现一个洗牌算法
    fisher-yates算法,指针i从尾到头走一遍,每一步random.nextint(i+1)([0,i])与i两个元素交换。
    从尾到头是因为nextint的下限默认为0,所以改上限比较容易写。
    交换是为了不使用额外空间,为了便于等概率证明的理解,可以先看使用额外空间的版本。

栈和队列

栈可以把递归写法改成迭代写法;队列也可以。

  • (#32)最长有效括号
    这题官解有dp、栈、扫描三种方法,很有内涵。
    扫描的方法很好,O(n)的时间复杂度和O(1)的空间复杂度。当右括号数>左括号数时放弃并清零,当左括号数=右括号数时结算。但这样会有(((())的情况,如果从右往左也扫描一遍(使用相反的规则)就没问题了。但如果不止一种括号的话这方法不行([)],因为单纯的计数是无法保留先后顺序和位置信息的(如果每次结算时都判断是否合法,这样就成O(pow(n,2)))了。而下面这种方法仍然是O(n)。
    栈的方法也很好,精髓在于栈中存放的是下标而不是字符。这样存的好处是保留了更多的信息,方便长度的计算。每次pop时都能以常量时间重新计算一次以当前为结尾的合法串长度,而不用借助栈顶所存下标以外的任何其它信息(如之前计算出的合法串长度,那样还要判断能不能连上,很不直接)。我认为,即使题目与长度无关,存下标也是一个可以保留的习惯。

  • (#155)最小栈,要求实现一个能实时查询最小值的栈。
    对于栈来说,状态转移的过程主要观察元素数量和栈顶元素(如果元素数量和栈顶元素相同,而其它元素不同,则要经过很多步操作才行,这是先入后出导致的)。辅助栈的元素数量始终与主栈一致,push和pop操作也共进退,而内容存(元素数量,栈顶元素)状态下的最小值即可。
    我认为,辅助栈的思想肯定会有其它用处。

  • (#232)用栈实现队列,(#225)用队列模拟栈
    栈相当于是颠倒了一次顺序,颠倒两次就成原顺序了。
    用队列模拟栈,吐了再塞回去,直到最后一个元素。所以其实只需要一个队列。
    回答的时候要有条理,队列的四种操作和栈的四种操作都要提到。建议按照empty,push,peek,pop的顺序讲。

  • 字符串删除相关问题,如(#1047) 删除字符串中的所有相邻重复项,或者是给了一个用户敲击键盘的序列,其中有退格符,要求返回最终输入字符串。

  • (#239) 滑动窗口中的最大值
    朴素方法是nxk的。
    单调队列的入队和出队都会维护队列的单调性。

  • (#84) 柱状图中最大矩形
    朴素方法是nxn的,对每个矩形向左右扩展。
    单调栈据说能做。但说实话写这段话的时候单调队列和单调栈我都不是很懂。

  • (#347)前k个高频元素
    前k大问题都可以用优先队列解决。推荐使用heapq,因为python优先队列类没有top很难受。

  • (#739) 每日温度
    一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,用单调栈。

基本排序算法

  • (#215)数组中第K大元素
    除了堆排序,还可以用特化的快速排序(对于每个pivot,只需递归一侧而无需递归两侧)。
    答快排的时候可以提一下pivot的随机化,可以缓和最坏情况。

  • 合并两个有序数组,不使用额外空间
    第一个数组长度为m,第二个数组长度为n,三个指针,分别指向第一个数组的m处,第二个数组的n处,第三个指向第一个数组的m+n处,三个指针都往左走,这样就不会有覆盖问题了。

  • 大量数中找前k大
    除了堆排序和快排减治以外,面试官还可能希望听见的:
    将数据切分,然后在多台机器上分别计算前1000大的数,最后再把这些数汇总

  • (剑指offer)员工年龄排序
    这个情景中,用一个0-100的map来统计每个年龄的出现次数即可排序。

  • (#23)合并k个排序链表
    优先队列是一种方法。反复两两合并(画出来就像二叉树。合并k个链表,分为两步,合并前k/2个链表,合并后k/2个链表,合并前两步的两个链表。即归并排序)也是一种方法。

  • (剑指offer 41)数据流的中位数
    所有中位数问题都可以用两个堆来解决,最小堆的最小值大于最大堆的最大值,保持规模平衡。
    具体操作上,数据流数据轮番入堆,入了之后堆顶元素进另一个堆。

  • (剑指offer 39)数组中次数超过一半的数字,既是众数也是中位数
    除了老实统计,还可以快排减治,堆,最大最小堆。
    最优:用<数字,出现次数>,同增异减,为0换数,最后剩下的就是答案。

  • (剑指offer 51)数组中的逆序对
    在归并排序的过程中顺便统计。每安放一个后数组里的数,ans+=前数组剩余数字数。

二叉树

  • (#101)对称二叉树,判断一棵二叉树是否对称
    开始有一点难想,对称实际上是对于两棵树来讲的,而不是一棵。(递归函数体的参数数量都没搞清,自然做不出来)
    两棵树对称的条件是:1.它们的根节点值相等;2.树A的右子树与树B的左子树对称,且树A的左子树与树B的右子树对称。

  • (剑指offer 68,#235) 二叉搜索树的最近公共祖先
    如果是普通树,返回左右子树中感兴趣点的数量,再判断本身是不是感兴趣点即可。
    搜索树的话,从上往下第一个落在大小区间内的就是。假设更下面有,会发现有矛盾。

  • (#669)修剪二叉搜索树;(#450)删除二叉搜索树中的结点
    递归函数的功能是,返回能够替代自己的结点。

  • (剑指offer 8)二叉树的下一个中序遍历节点
    主要是分类讨论,有右子树则返回右子树最左子节点,无右子树若为左儿子则返回父节点,无右子树也为右儿子则一直向上找第一个左儿子。

  • (剑指offer 26) 输入A、B两棵树,判断B是否为A的子结构
    先遍历A,找B的根节点,找到则调用“判断树相等”的函数。

  • (剑指offer 32)之字形打印二叉树
    层序遍历的变种,交替使用两个队列或者两个栈即可

  • (剑指offer 33)判断一个前/后序遍历输出是否为二叉搜索树
    BST的后序遍历有这样的特征:整个序列可以分为3个连续段,小的左子树,大的右子树,根。
    所以如果出现小-大-小或者大-小-大,就肯定不是二叉搜索树。递归判断子树能否这样三分。

  • (剑指offer 36)二叉搜索树转双向链表
    结构上是中序遍历,需要记录链表当前最后一个节点,递归的实体逻辑是设置当前链表节点和上一个链表节点之间的两个指针。

  • (剑指offer 37)序列化二叉树
    关键是把空指针用一个特殊字符来代替,这样就只需要前序序列,而且不必等数据读完就能开始。

递归与回溯

  • 排列组合
    排列组合问题可以这样拆解:在获取深度-1的池的全部排列组合之后,选择不同的开头与之配合,即可获得当前深度的池的全部排列组合。

  • (#17)电话号码的字母组合
    经典的回溯法。读完题如果脑子里有一棵树,并且需要沿途收集路上的东西,就是回溯法。

  • (剑指offer 10)斐波那契数列
    从小往大算即可。不要递归。

  • (剑指offer 17)打印从1到n位最大数的所有数,如n=3时打印1~999
    可以用全排列做,打印时去掉前导0。用字符串是为了避免溢出,即使用python也提一嘴。

  • (剑指offer 19)正则表达式匹配,输入是字符串和正则表达式,判断是否匹配
    用状态机实现。代码不长,递归实现。

  • (剑指offer 38)字符串全排列
    回溯法,全局标记是否访问过。

  • 八皇后
    一个长度为8的数组a,初始化为0~7,a[i]=j表示第i行的皇后在第j列。对0-7全排列,判断是否存在i,j,使得i-j==a[i]-a[j],即在同一条对角线上。

动态规划

  • (#5)最长回文子串
    平时的遍历都是枚举起点和终点,其实也可以枚举起点和长度。
    三个要点:递推式(子串回文+首尾相等),递推顺序(长度由小到大),递推起点(长度为1和2的串)
    这题也可以用中心扩散的思路,更为自然。

  • (#300)最长递增子序列
    要想确认递推式,就得搞清如何在先处理小规模的情况下得到大规模的解。每一个字符都依附在它之前的某一字符之后,所以这一次的状态转移不是max(a,b)这么简单,而是max(0…i-1)。
    官解有第二个非常牛的解法,关键是能依据贪心的思想,构造辅助数组d[i],表示长度为i的递增子序列的最小末尾。每访问一个新字符,就用二分查找确认其能更新的d[i]。
    只要有单调性(即使是部分单调),就要马上想到二分。

  • (#1143)最长公共子序列
    从部分串开始推导。建议先想dp数组含义,然后是递推式,然后是递推顺序、递推起点。

  • 最长公共子串,和上一题有点像,都是二维矩阵
    https://www.cnblogs.com/shijianchuzhenzhi/p/13048917.html
    一个字符串做行,另一个字符串做列;如果行和列相同,则矩阵A[i,j]置为1,求最长对角线长度;优化后将A[i,j]置为A[i-1,j-1]+1,寻找矩阵中最大值即可。

  • (#1277)统计全为1的正方形子矩阵
    dp[i][j]数组的含义是,以i j为右下角的最大正方形的边长。这个数累加恰好就是计数结果(这里有点难想到)。
    dp[i][j]可以由左、上、左上推导而来。经验总结表明(#1143和本题),二维dp如果想不到递推式,可以考虑一下这三个邻居。这比较自然,就像一维dp一般先考虑左邻居。

  • (剑指offer 46)把数字翻译成字符串,比如12258有bccfi和mcfi等不同的翻译方法。
    寻找f(i)和f(i+1)之间的关系,如果i和i+1连起来在1-26之间,则f(i)=f(i+1)+1,否则f(i)=f(i+1)。

  • (剑指offer 48)最长不包含重复字符的子字符串
    记录每一个字符最近出现位置,dp数组表示以当前位为结尾的最长无重复子串。
    如果一个字符之前出现过,衡量两次出现的距离,与dp[i-1]比较。

  • (剑指offer 60)n个骰子的点数
    我们其实不关心骰子序列信息,只需知道他们的和。实为二维dp,列数为n to 6n,行数为n。
    观测第id个骰子,写dp数组的第id行,写的范围是id to 6xid。如第一行在1-6处记1。
    第二行在第一行的基础上计算,在2-12处记sum(dp[1][pos-1],…,dp[1][pos-6])。
    以此类推,我们发现算第三行时可以忘记第一行。因此无需n行空间,只需交替使用两行。

字典树

  • (#648)单词替换,替换掉文本中以特定前缀开头的单词
    字典树是一种特殊的hash树,空间换时间,经典应用是统计词频。基本操作是查字符串全串/前缀是否是字典中的全串,或者是字符串全串是否是字典中的前缀。需要注意的是,为了知道已经到达词尾,字典树结点应当有一个数据域进行标记(如,boolean isWord, 或String word)。

二分查找

  • (剑指offer 53)排序数组中查找数字,变种如确认重复数字个数,寻找frist/last出现
    普通的二分找到的是任意一次出现,因为代码是找到==后立刻返回。
    找到之后不立刻返回,而是再加判断、再修改前后界。first/last就判断前/后一个还是不是。

  • (剑指offer 53)0~n-1中缺失的数字,数组递增排序
    全部求和,理论值-实际值就能找到,无需数组有序,On。
    二分查找第一个下标不等于元素的位置,就是缺失的数字。

  • 在经过排序的无限数组中搜索
    将索引设置为2的幂。

  • 平方根类问题
    比如判断一个数是不是完全平方数,或者求一个数的平方根向下取整

  • (剑指offer 11)循环数组的最小值/循环数组查找
    二分,左ptr始种在前半,右ptr始种在后半,关键是判断mid在前半还是后半。判断出来后,令左ptr为mid(而非mid+1)。当左ptr与右ptr相邻时结束。可以特判完全有序数组,此外如果左中右都相等需要顺序扫描。
    查找的话简单些,更接近普通二分查询,ptr的更新要加减1,也不用最后的特判。

循环排序

https://zhuanlan.zhihu.com/p/117347353 该专题有很多道题目,有待填坑

  • (#41)缺失的第一个正数
    法一,时间n,空间n:数组所有数存入hash表,从1到n循环看是否在hash表中
    法二,时间n2,空间1:从1到n循环看是否在数组中
    法三,时间n,空间1:置换,让1~len处于正确的位置上。注意连锁置换。

数论/位运算

  • 分解质因数
    从2到根号n(记得以乘代开方),while能除尽则除尽。最后如果大于1则为质数,输出本身即可。

  • (#78)数组中的元素互不相同。返回该数组所有可能的子集。
    二进制枚举的模板题。有pow(2,n)种状态,每种状态需要用(1<

  • 数组中一/两个数出现一次,其余出现两次
    用hashmap做,时间空间都是O(n);
    用位运算做,时间O(n),空间O(1);
    将所有数异或,如果是只有一个数出现一次,那么就得到结果;
    如果有两个数出现一次,则得到了a^b,根据a^b某一个为1的位(说明所有数在这一位上有分歧),根据这一位的01情况将所有数分为两组,则a和b必然分居两组,且出现两次的数肯定在同一组,在这两组中各自进行异或操作即可。

  • (剑指offer 56)数组中一个数字出现1次,其他数字出现3次
    二进制视角下,对所有数按位求和,如果某位和是3的倍数,则ans此位为0;否则为1。

  • 小于n的质数数量
    正解是埃氏筛。维护一个长度为n的bool数组,从2开始,不断自加赋false,然后把第一个遇到的true作为新的初始质因子。外层循环是质因子,内层循环负责标记合数。

  • (剑指offer 15)判断二进制数中1的个数
    常规解法是32位数就要循环32次,而n&(n-1)的效果是把二进制数最右边的1变成0。

  • (剑指offer 16)数的整数次方
    float型有效6位,double型有效15位,判0用python fabs即可。
    首先要讨论底数是否为0,直接返回0,因为0的负指数次会报错,而且数学上也会单独讨论。
    然后是讨论指数的负零正。最后是快速幂。

  • (剑指offer 49)丑数,从小到大生成只含2,3,5因子的数
    下一个丑数一定由之前某数ax2、某数bx3或某数cx5获得。寻找这三种情况中的最小值。
    这三个“某数”会单调增加,可以记录进度,只对进度之后的进行x2、x3、x5操作。

  • (剑指offer 65)不用加减乘除实现加法
    用位运算模拟,分为三步,不进位相加,求进位,前两步相加。

  • 不使用额外空间,交换两个数
    可以用加减法实现,也可以用异或运算实现。

  • 如何避免set的使用
    如果set很小,比如统计26个小写英文字母是否出现过,用bitmap即可。

贪心

  • (#55)从一维数组第一个数出发,数组元素代表步长上限,判断能不能到达最后一个数
    巧妙之处在于如果一个点可达,那么它之前的每个点都可达。所以只需维护最大可达。

  • (剑指offer 14)剪绳子
    比较典型的一维dp,但也可以用贪婪做(尽量剪长度3)。

附录

//初始化size,但每个元素值为默认值
vector<int> abc(10);    //初始化了10个默认值为0的元素
//初始化size,并且设置初始值
vector<int> cde(101);    //初始化了10个值为1的元素
int a[5] = {1,2,3,4,5};
//通过数组a的地址初始化,注意地址是从0到5(左闭右开区间)
vector<int> b(a, a+5);
bool myfunction (int i,int j) { return (i<j); }
struct myclass {
  bool operator() (int i,int j) { return (i<j);}
} myobject;
// using object as comp
 std::sort (myvector.begin(), myvector.end(), myobject);
 // using function as comp
 std::sort (myvector.begin()+4, myvector.end(), myfunction);

你可能感兴趣的:(面试)