字符串的全排列
1. 题目描述输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串 abc,则打印出由字符 a、b、c 所能排列出来的所有字符串 abc、acb、bac、bca、cab 和 cba。
2. 思路分析把集合看成 2 个部分,第一部分是第一个元素,第二部分是后面剩余元素。所有字符都要与当前集合的第一个元素交换,交换后的元素是固定的,也就是一种情况。
每次交换,都继续处理后面剩余元素,它们又可以分成 2 部分,和之前讲述的一样。就这样一直递归下去,直到最后一个元素,那么就排出了其中一种情况。所有情况放在一起,就是全排列的结果。
3. 代码实现12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152/** * 交换数组指定坐标的2个元素 * @param {Array} arr * @param {Number} i * @param {Number} j */function swap(ar ...
翻转单词顺序
1. 题目描述输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。
为简单起见,标点符号和普通字母一样处理。例如输入字符串”I am a student.”,则输出”student. a am I”。
2. 思路分析进行 2 次不同层次的翻转。第一个层次的翻转,是对整体字符串进行翻转。第二个层次的翻转,是对翻转后字符串中的单词进行翻转。
3. 代码实现注意:因为 js 按位重写字符,所以第一次整体字符串翻转后的每个字符,都放入了数组中。
1234567891011121314151617181920212223242526272829/** * @param {String} sentence */function reverseSentence(sentence) { // 第一次翻转:每个字符 const chars = sentence.split('').reverse() let result = '', last = [] // 保存上一个空格到当前空格之间的所 ...
实现atoi
1. 题目描述请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−2^31, 2^31 − 1]。如果数值超过这个范围,qing 返回 INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) 。
题目来自 LeetCode,可以直接前往这个网址查看题目各种情况下要求的输出。
2. 思路分析这种题目主要就是考察细心,要主动处理所 ...
旋转数组最小的数字
1. 题目描述把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为 1。
2. 解题思路最简单的肯定是从头到尾遍历,复杂度是 $O(N)$。这种方法没有利用“旋转数组”的特性。
借助二分查找的思想,时间复杂度可以降低到 $O(log(N))$。
可以通过以下方法确定最小值元素的位置,然后移动指针,缩小范围:
中间指针对应的元素 ≥ 左侧元素, 那么中间元素位于原递增数组中, 最小值在右侧
中间指针对应的元素 ≤ 右侧元素, 那么中间元素位于被移动的递增数组中,最小值在左侧
特殊情况,如果三者相等,那么无法判断最小值元素的位置,就退化为普通遍历即可。
3. 代码先上一段二分查找和实现思路:
12345678910111213141516171819202122232425262728293031323334/** * 二分查找 * @param {Array} arr * @param {*& ...
数字在排序数组中出现的次数
1. 题目统计一个数字在排序数组中出现的次数。
2. 思路解析题目说是排序数组,所以可以使用“二分查找”的思想。
一种思路是查找到指定数字,然后向前向后遍历,复杂度是 O(N)。
另一种是不需要遍历所有的数字,只需要找到数字在数组中的左右边界即可,做差即可得到出现次数。
3. 代码实现1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768/** * 寻找指定数字的左 / 右边界 * * @param {Array} nums * @param {*} target * @param {String} mode left | right 寻找左 | 右边界 */function findBoundary(nums, target, mode) { let left = 0, right = nums.l ...
从尾到头打印链表
1. 题目描述输入一个链表,从尾到头打印链表每个节点的值。
2. 解题思路可以从头到尾遍历一遍链表,将节点放入栈中,然后依次取出打印(后入先出)。
优化就是借助“递归”,先向下查找再打印输出,也可实现这种“后入先出”。可以类比二叉树的后序遍历。
3. 代码用 JS 实现了简单实现了链表这种数据结构,这不是重点。
重点在printFromTailToHead函数。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061class Node { /** * 节点构造函数 * @param {*} value * @param {Node} next */ constructor(value, next) { this.value = value this.next = next ...
快速删除链表节点
1. 题目描述给定单向链表的头指针和一个结点指针,定义一个函数在 $O(1)$ 时间删除该结点。
2. 思路描述正常的做法肯定是在 $O(N)$ 时间内删除节点。而这么过分的要求,显然是通过“重新赋值”才能做到。
比如要删除节点 a,那么就将 a.next 的 value 和 next 赋值给节点 a,然后删除 a.next。
表面“看起来”像是删除了节点 a,其实是将其后节点的信息转移到了它自己身上。
除此之外,对于最后一个节点,还是要退化成 $O(N)$ 的复杂度。而整体分析一下复杂度:
$$O(T) = (O(N) + O(1) * (n - 1)) / n = O(1)$$
3. 代码实现123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354class Node { /** * 节点构造函数 * @param {*} value * @param {Node} ...
链表倒数第k节点
1. 题目描述输入一个单链表,输出该链表中倒数第 k 个结点。
2. 思路描述思路一:从头到尾遍历一遍,统计长度length。再从头遍历,直到length - k个节点停止,这就是倒数第 k 个节点。
思路二:只需要遍历一遍。准备 2 个指针a和b均指向第一个节点,a先移动k个位置;然后a和b一起向后移动,此时两个只指针的位置差为k;直到a移动到尾结点停止,此时b指向的节点就是倒数第 k 个节点。
3. 代码实现下面是“思路二”的实现。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950/** * 节点定义 */class Node { constructor(value, next) { this.value = value this.next = next }}/** * 寻找倒数第k个节点 * @param {Node} head 初始节点 * @pa ...
反转链表
1. 题目描述定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
2. 思路描述思路一:经典的“链表头插法”,时间复杂度是 $O(N)$,但是空间复杂度也是 $O(N)$
思路二:链表原地操作,时间复杂度是 $O(N)$,但是空间复杂度只有 $O(1)$。
保存当前节点node的上一个节点pre
节点node的next指向pre
分别将pre和node向后移动 1 个位置
如果node为 null,链表翻转完毕,此时pre指向新的头节点,返回即可
否则,回到第 1 步继续执行
3. 代码实现123456789101112131415161718192021222324252627282930313233343536373839404142434445/** * 节点定义 */class Node { constructor(value, next) { this.value = value this.next = next }}/** * 翻转链表 * @param & ...
合并两个有序链表
1. 题目描述输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
2. 思路分析准备一个指针node,假设指向两个链表的中节点的指针分别是:p1和p2。
比较p1和p2的value大小
如果,p1.value 小于 p2.value, node.next 指向 p1, p1 向后移动
否则,node.next 指向 p2, p2 向后移动
重复第 1 步,直到其中一个链表遍历完
跳出循环,将 node.next 指向未遍历完的链表的剩余部分
整个过程的时间复杂度是 O(N), 空间复杂度是 O(1)
3. 代码实现123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960/** * 节点定义 */class Node { constructor(value = null, next = null) { this.value = value ...