数值的整次方
1. 题目描述题目:实现函数 double Power(double base, intexponent),求 base 的 exponent 次方。不得使用库函数,同时不需要考虑大数问题
2. 思路分析简单思路:最简单的做法是循环,但是要考虑异常值的检验。比如指数是负数,底数为 0。
优化思路:书上提供了一种复杂度为 $O(logN)$ 的做法。比如我们要求 32 次方,那么只要求出 16 次方再平方即可。依次类推,是递归函数的结构。
递推公式如下:
$$a^n=\left{\begin{aligned}a^{n/2}*a^{n/2} ; n为偶数\a^{(n - 1)/2}*a^{(n - 1)/2} ; n为奇数\end{aligned}\right.$$
需要注意的是,如果幂是奇数,例如 5 次方,可以先计算 2 次方,结果平方后(4 次方),再乘以自身(5 次方)。按照此思路处理。
3. 代码实现简单思路123456789101112131415161718192021222324252627282930313233/** * * @param {Number ...
打印从1到最大的n位数
1. 题目描述题目:输入数字 n,按顺序打印出从 1 最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数即 999。
2. 思路分析主要的坑点在:大数的溢出。当然,es6 提供了BigInt数据类型,可以直接相加不用担心溢出。
除此之外,这题显然是要我们模拟“大数相加”:将最低位加 1,然后每次检查是否进位,如果不进位,直接退出循环;如果进位,需要保留进上来的 1,然后加到下一位,直到不进位或者超出了我们规定的范围。
3. 代码实现js 中不方便操作字符串中指定位置的字符,因此用数组对象来模拟。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/** * 用数组模拟大数相加操作 * @param {Array} arr * @return {Boolean} true, 超出arr.length位最大整数; false, 没有超出arr.length位最大整数 */fun ...
顺时针打印矩阵
1. 题目描述输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
2. 思路分析既然是顺时针打印,其实就是由外向内一圈圈打印,将过程分为 2 步:
第一步:printMatrix函数,确定要打印的圈的左上角坐标(比较简单)
第二步:printMatrixInCircle函数,根据左上角坐标,顺时针打印这一圈的信息。这个过程又分为四步:左上 -> 右上 -> 右下 -> 左下 -> 左上。
3. 代码实现如果觉得,函数printMatrixInCircle的条件判断不清楚,可以配合下面这张图一起看:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576/** * 打印从 (start, start) 与 (endX, endY) 围成的一圈矩形 * @param {Array} arr * @para ...
数组中出现次数超过一半的数字
1. 题目描述数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为 9 的数组{1,2,3,2,2,2,5,4,2}。
由于数字 2 在数组中出现了 5 次,超过数组长度的一半,因此输出 2。
2. 思路分析数组中有一个数字出现的次数超过数组长度的一半,说明它出现的次数比其他所有数字出现次数的和还要多。
在遍历的过程中保存两个变量:一个数字 + 一个次数。遍历到每个元素都会更新次数,元素 = 数字,加次数;否则,减次数;如果次数为 0,当前元素赋值给数字。
需要注意的是,最后结果不一定符合条件,比如数组 [1, 2, 3],结果是 3。所以要再统计一下最后数字的次数,是否有一半那么多。
3. 代码1234567891011121314151617181920212223242526272829303132333435// 检查指定元素的次数是否大于等于长度一半function checkMoreThanHalf(nums = [], target) { let times = 0 nums.forEach((num) => num ...
最小的k个数
1. 题目描述输入 n 个整数,找出其中最小的 k 个数。例如输入 4、5、1、6、2、7、3、8 这 8 个数字,则最小的 4 个数字是 1、2、3、4。
2. 思路分析利用“快速排序”的中的 partition 操作:返回 index,小于 index 对应元素的元素都放在了左边,大于 index 对应元素的元素都放在右边。
利用这个特性,只要我们的 partition 返回值是 k - 1,那么数组中前 k 个元素已经被摆放到了正确位置,直接遍历输出即可。
由于不需要排序全部,整体的时间复杂度是 O(N)。但美中不足的是:要在原数组操作,除非用 O(N)的空间来做拷贝。除此之外,针对海量动态增加的数据,也不能很好处理。这种情况需要用到“最大堆”,请前往《堆》章节查看。
3. 代码实现12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364function partiton(arr = [], star ...
和为s的两个数字
1. 题目描述输入一个递增排序的数组和一个数字 s,在数组中查找两个数,使得它们的和正好是 s。如果有多对数字的和等于 s,输出任意一对即可。
2. 解题思路如果这个数组不是递增的,就得用哈希表来解决,空间复杂度是 O(N)。
但是题目条件是“递增数组”,因此可以使用“双指针”的思路来实现:即一个指针指向开头,另一个指向结尾。
比较指针对应的 2 个元素的和与给定数组 s:
元素和 > s: 后指针向前移动
元素和 < s: 前指针向后移动
元素和 = s: 返回指针对应的 2 个元素
3. 代码实现12345678910111213141516171819202122232425262728293031/** * * @param {Array} data * @param {Number} sum */function findNumsWithSum(data, sum) { if (!Array.isArray(data) || data.length <= 1) { ...
和为s的连续正数序列
1. 题目描述输入一个正数 s,打印出所有和为 s 的连续正数序列(至少含有两个数)。例如输入 15,由于 1 + 2 + 3 + 4 + 5 = 4 + 5 + 6 = 7 + 8 = 15,所以结果打印出 3 个连续序列 1 ~ 5、4 ~ 6 和 7 ~ 8。
2. 思路分析和前面题目很相似,这里也是“双指针”的思路。不同的地方有 2 个点:
指针是从第 0 个和第 1 个位置开始的(下面称为 a 和 b)
这里要计算指针范围内的所有元素和(题目要求是“连续序列”)
每次移动 a、b 之前,都要计算一下当前[a,b]范围内的所有元素和。如果等于 s,打印并且 b 右移;如果小于 s,b 右移;如果大于 s,a 右移。
至于为什么相等的时候 b 右移而不是 a 右移?因为 a 右移会漏掉情况,而且指针可能重叠。比如对于数组 [1, 2, 2],给定 s 是 3。
3. 算法实现123456789101112131415161718192021222324252627282930313233343536373839404142434445/** * 打印指定数组的起始下标 ...
n个骰子的点数
1. 题目描述把 n 个骰子扔在地上,所有骰子朝上一面的点数之和为 s。输入 n,打印出 s 的所有可能的值出现的概率。
2. 思路分析递归的思路就是组合出所有情况,然后每种情况记录出现次数,最后除以 6^n 即可。其中,6^n 就是所有情况的总数。
书中提出的方法是使用循环来优化递归,递归是自顶向下,循环是自底向上,思考起来有难度。
技巧性很强,准备 2 个数组,假想每次投掷一个骰子,出现和为 n 的次数,就是之前骰子和为 n-1, n-2, …, n-6 的次数和。依次类推,每次存储结果都和之前的数组不同。
3. 算法实现注释中都有详细说明:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859const gMaxValue = 6 // 每个骰子的最大点数/** * * @param {Number} number 骰子的个数 */function printProbability(number) ...
扑克牌的顺子
1. 题目描述从扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这 5 张牌是不是连续的。
2 ~ 10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大、小王可以看成任意数字。
2. 思路分析难度不大,可以将大小王看成数字 0,可以在任何不连续的两个数字之间做填充。
首先将原数组排序,然后统计任意数字(0)的出现次数。再遍历之后的数字,找出不相邻数字之间总共差多少个数字。
最后比较 0 的出现次数和总共差多少个数字,两者的大小关系。
注意:连续两个相同的数字是对子,不符合要求。
3. 代码实现1234567891011121314151617181920212223242526272829/** * * @param {Array} numbers */function isContinuous(numbers) { numbers.sort() const length = numbers.length let zeroNum = 0 for (let i = 0; i < length && ...
圆圈中最后剩下的数字
1. 题目0,1,…,n-1 这 n 个数字排成一个圆圈,从数字 0 开始每次从这个圆圈里删除第 m 个数字。求出这个圆圈里剩下的最后一个数字。
2. 思路分析这个其实是经典的“约瑟夫环”问题。常用解法就是“循环取余”。每次都把下标移动 m 个位置,然后移除当前元素。直到只剩最后一个元素。
3. 代码实现12345678910111213141516171819202122232425/** * @param {Number} n 0, 1, 2, ..., n-1 一共n个数字 * @param {Number} m 被删除的第m个数字(从0计算) */function lastRemain(n, m) { // 生成 [0, 1, ... , n-1] 的列表 const nums = new Array(n) for (let i = 0; i < n; ++i) { nums[i] = i } // 逐步移除第m个数字 let start = 0 ...