【剑指offer】解题思路 53-68


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

第六章 面试中的各项能力

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(n1)求出+数字0~n-1的所有数字之和,记为s1,接着求出数组中所有数字的和,记为s2。那个不在数组中的数字就是s1-s2的差。复杂度O(n)

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

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

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

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

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

 

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

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

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

 

面试题55:二叉树的深度

题目一:二叉树深度

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

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

def TreeDepth(root):
    if not root:
        return 0
    return max(TreeDepth(root.left), TreeDepth(root.right)) + 1
题目二:平衡二叉树

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

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

def IsBalenced(root):
    if not root:
        return False
    if abs(TreeDepth(root.left) - TreeDepth(root.right)) <= 1:
        return True
    else:
        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:
        return
    res = 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)。

【解题思路】:
还是题目一的思路,滑动窗口可以看成一个队列,因此本题的解法可以实现带max函数的队列。

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:不用加减乘除做加法(位运算)

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

【解题思路】:
不用四则运算,那就只能用位运算了。第一步:先按位加法,不进位;第二步,循环处理进位问题。
https://www.jianshu.com/p/21fd1598d4ae

在这里插入图片描述

 

面试题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]。不能使用除法。

【解题思路】:

 

面试题67:构建乘积数组

【题目描述】:
给定一个数组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]。不能使用除法。

【解题思路】:

 

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

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

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

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值