Skip to content

Files

Latest commit

fbbceb5 · May 10, 2020

History

History

剑指Offer

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
May 10, 2020
May 4, 2020
May 4, 2020
May 4, 2020
May 9, 2020
May 9, 2020
May 9, 2020
May 9, 2020
May 9, 2020
May 10, 2020

剑指 Offer

剑指 Offer


面试题03. 数组中重复的数字

找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

题解:

  1. indexOf 和 lastIndexOf。时间复杂度应该是 O(n^2)。

  2. 先排序再遍历。时间复杂度是 O(n * logN)。

  3. 哈希。时间复杂度和空间复杂度都是 O(n)。

  4. 因为所有数字都在 0~n-1 的范围内,也就意味着如果没有重复数的话, 0~n-1 这 n 个数在数组内都会存在一个。所以可以排序后遍历数组去看当前的元素值是否等于数组下标,有一个不等于的话说明它是重复数字。但如果要将时间复杂度和空间复杂度都降到 O(n) 的话,就不能直接使用排序。所以可以遍历数组,每次都去比较当前的数组元素(姑且称作 val)和数组下标是否相等,不相等的话再去看下标为 val 的数组元素 nums[val]。如果 nums[val] 和 val 相等,说明 val 重复了,可以直接退出循环。否则交换 nums[val] 和 val。这个方法的重点就在于,通过不断的交换使当前的数组元素值等于它的下标,相当于排序了一次, 从而达到唯一的效果。

面试题04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:
现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。

限制:
0 <= n <= 1000
0 <= m <= 1000

题解:

  1. 从二维数组的右上角开始查找,如果当前数组元素大于 target,则列-1向左查找;小于的话,则行+1,向下查找;等于的话就直接返回;如果查找到边界还没找到的话,说明不存在 target。有点像排序二叉树,利用右上角左边的都小于它,下边的都大于它,一次性排除掉一行或一列。时间复杂度 O(n+m)

面试题05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
 
限制:0 <= s 的长度 <= 10000

题解:

  1. replace + 正则表达式。

  2. 使用一个新的字符串,遇到空格就拼接 %20,否则就拼接原来的字符。

面试题06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
 
限制:
0 <= 链表长度 <= 10000

题解:

  1. 先构造双向链表再从后向前遍历链表打印。

  2. 通过 unshift 直接插入到数组头,或者使用 reverse 翻转数组。

面试题09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]

示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用

题解:

  1. 把 arr1 和 arr2 当做是两个栈,后进先出,所以只能使用 push 和 pop 方法。入队的时候,都将数据放到 arr1 里面。出队的时候,如果 arr2 为空,就将 arr1 的所有元素 pop 到 arr2 中,这样 arr2 中的数据顺序就符合先进先出了;如果 arr2 不为空,就直接从 arr2 里取数据,这样就不用每次出队都将 arr1 里的数据放到 arr2 里。

面试题10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
输入:n = 2
输出:1

示例 2:
输入:n = 5
输出:5
 
提示:
0 <= n <= 100

题解:

  1. 单纯递归,会超时。时间复杂度 O(n^2),空间复杂度 O(1)。

  2. 递归 + 记忆数组,防止对已经计算出来的值重复递归计算。时间复杂度 O(n),空间复杂度 O(n)。

  3. DP,使用两个变量来保存上一个和上上一个值。时间复杂度 O(n),空间复杂度 O(1)。

  4. 斐波那契数列还可以用矩阵来求解,印象中用矩阵是最快的(时间复杂度是 O(logN)),但已经忘了怎么写了 Orz。

面试题11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。  

示例 1:
输入:[3,4,5,1,2]
输出:1

示例 2:
输入:[2,2,2,0,1]
输出:0

题解:

  1. 直接循环判断 numbers[i] 是否会小于 numbers[i-1],是的话就是旋转点。时间复杂度为 O(n)。

  2. 二分,时间复杂度为 O(logN)。

通过数组旋转,可以将原数组切分为两个升序排序的子数组,左子数组较大,右子数组较小,如 [3,4,5,1,2]。通过二分取中间的数组元素 numbers[mid],将其和数组右边界元素 numbers[r] 比较。

大于的话说明 numbers[mid] 在右子数组中,那么旋转点在 numbers[mid] 右边,所以将寻找范围缩小到 [mid+1, r]

小于的话说明 numbers[mid] 在左子数组中,那么旋转点可能是 numbers[mid] 或者在其左边,所以将寻找范围缩小到 [l, mid]

等于的话只需 r-- 舍弃掉 numbers[r],因为 r-- 后旋转点还是在 [l, r] 上。

需要注意的是(采坑了 Orz),不能将 numbers[mid]numbers[l] 比较,因为两者比较无法得出 numbers[mid] 是在左子数组还是在右子数组。例如 [3, 4, 5, 1, 2][1, 2, 3, 4, 5]numbers[mid] 都大于 numbers[l],但最小值一个在后面,一个在前面。


题解: