堆排序:https://zhuanlan.zhihu.com/p/341864258
let c = [1,2];
c.push(3); // [1, 2, 3]
c.pop(); // [1, 2]
Math.ceil
1亿个数找出最前面的100个
// 背诵算法的技巧
堆排序的方式最佳
1. 平时只需要看懂逻辑。
2. 记忆只在每个早晨。
3. 算法贴墙上,每次清晰的默写出题目,和算法本质。
中位数,二分算法
slice()、substring()、substr()三者的区别
Math.floor向下取整,Math.round四舍五入
任务1. 写出图 的那个 题和解决 方案
记忆方式: copy之后,看懂了,删除后在自己写一遍
LeetCode 热题 HOT 100
labuladong
awesome-coding-js算法
awesome-coding-js数据结构
两个数组之类的,要空间换取时间,一般情况下两种方式,set或者obj。
如果是简答情况用set,负责情况用obj
打印:
ASCII编码
js判断字符串是否是中文
求中位数
二分查找
先序中序后序递归遍历一个树
写一个简单树类Tree,包含add, insert
写一个简单链表类LinkList,包含insert, append
写一个Set类,包含add
写一个Map类,包含set,get,calc
快速排序两数组实现
归并排序
------数组
两数之和
三数之和
数组反转
合并两个有序数组
寻找两个正序数组的中位数
两个数组的交集
在排序数组中查找元素的第一个和最后一个位置
------链表
**链表的中间结点
**环形链表
**反转链表
排序链表
合并两个有序链表
------二叉搜索树学习
二叉树的最大深度
二叉树的最小深度
**二叉搜索树中的搜索
**展平二叉搜索树
**二叉树展开为链表
二叉搜索树中两个节点之和
将有序数组转换为二叉搜索树
有序链表转换二叉搜索树
序列化和反序列化二叉搜索树
**路径总和
------其他算法
LRU 缓存
数据结构KMP算法配图详解
《算法和数据结构》算法篇
为有机会进大厂,程序员必须掌握的核心算法有哪些?
https://github.com/nonstriater/Learn-Algorithms
可视化算法网站汇总,从此简单学算法!(附动图)
前端该如何准备数据结构和算法?
面试会出哪些经典算法题?
互联网大厂算法面试题集合,看完我跪了!
mysql b树 io 读取_B树、B+树发展史
常用小知识点
时间复杂度 & 空间复杂度
时间复杂度:一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
手把手带你了解时间复杂度和空间复杂度 【超详细】
二维数组的空间复杂度_LeetCode 73——从一道 LeetCode 题看空间复杂度
空间复杂度
第一种情况 for循环中的i每次虽然都指向了同一个栈,销毁了,然后再使用。
for(var i = 0;i<n;i++) {}
第二种情况
function f1() {}
function f2() {}
function f3() {}
f1();f2();f3()这种情况三个函数的都指向了同一个栈,消费一个,在创建一个。
情况三:递归是因为FibN 到FIb1先执行,广度递归,后边的Fib(n-1)和Fib(N-2)执行的时候都是使用前边的空间,
其实每次递归都有一个变量,递归n次就是n个变量。但是如果递归内部有变量,例如下边的var a =1;那么每次就是2个变量,
递归n次就是2n,根据大o去掉2就是n
// 计算斐波那契递归Fib的时间复杂度?
long long Fib1(size_t N)
{
if(N < 3) return 1;
// var a = 1;
return Fib(N-1) + Fib(N-2);
}
##### 字符串的比较,汉语的比较
url切割,版本切割,字符串切割,排序
#### Infinity 和 -Infinity
#### Number.MAX_SAFE_INTEGER 和Infinity是一回事么
```javascript
Number.MAX_VALUE < Number.MAX_SAFE_INTEGER < Infinity
小于Number.MAX_VALUE 可以保证精度 < Math.pow(2, 53)
Number.MAX_SAFE_INTEGER 无法保证精度 < Math.pow(2, 1024)
Infinity 无法表示
// 超过 53 个二进制位的数值,无法保持精度
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
// 超过 2 的 1024 次方的数值,无法表示
Math.pow(2, 1024) // Infinity
Math.floor((low + high)/2) 和 (low + high) >> 1一样
ASCII编码 charCodeAt. charAt
'1'.charCodeAt() // 49
'a'.charCodeAt() // 97
-------
'abcdefg'[2] // c
'abcdefg'.charAt(2) // c
js判断字符串是否是中文
function isChinese(temp){
var re=/[^\u4E00-\u9FA5]/;
if (re.test(temp)) return false ;
return true ;
}
obj 添加属性是有顺序的
const obj = {};
obj.aaa = 1;
obj.bbb = 2;
obj.aab = 3;
console.log(Object.keys(obj))
// [ 'aaa', 'bbb', 'aab' ]
难的算法
动态规划
分治
贪心算法
面试常考的小题
默认排序
[2,3,4,4,2].sort((a,b) => a-b)
// [2, 2, 3, 4, 4]
url 切割
// 方法一
var url = 'https://www.baidu.com/a/b?name=王小二&age=8&hobby=敲代码&hobby=soccer';
function fn(str) {
let obj = {};
var arr = str.split('?')[1].split('&');
arr.forEach(item => {
const key = item.split('=')[0];
const value = item.split('=')[1];
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (!obj[key]) {
obj[key] = value !== undefined ? value ? true;
} else {
obj[key] = [].concat(obj[key], value);
}
})
return obj;
}
console.log(fn(url));
// { name: '王小二', age: '8', hobby: [ '敲代码', 'soccer' ] }
// 方法二
let str = 'sadf?key=14&key=24&key=34&test=44';
let result = {};
str.replace(/([^?=&]+)=([^&=]+)/g, function () {
// console.log(arguments[0], arguments[1], arguments[2])
// key=14 key 14
if (!result[arguments[1]]) {
result[arguments[1]] = arguments[2];
} else {
result[arguments[1]] = [].concat(result[arguments[1]], arguments[2])
}
});
// 不带括号的正则效果完全不同
// str.replace(/[^?=&]+=[^&=]+/g, function () {
// // console.log(arguments[0], arguments[1], arguments[2])
// // key=14 5 sadf?key=14&key=24&key=34&test=44
// if (!result[arguments[1]]) {
// result[arguments[1]] = arguments[2];
// } else {
// result[arguments[1]] = [].concat(result[arguments[1]], arguments[2])
// }
// });
// 方法三
'sadf?key=14&key=24&key=34&test=44'.match(/([^?=&]+)=([^&=]+)/g)
// ['key=14', 'key=24', 'key=34', 'test=44']
// 不带括号正则表达式
'sadf?key=14&key=24&key=34&test=44'.match(/[^?=&]+=[^&=]+/g)
// ['key=14', 'key=24', 'key=34', 'test=44']
// 方法四
function getParams(url, params) {
var res = (new RegExp("(?:&|/?)" + params + "=([^&$]+)")).exec(url);
// res [ 'id=2', '2', index: 7, input: 'xx.com?id=2&isShare=true', groups: undefined ]
return res ? res[1] : '';
}
const id = getParams('xx.com?id=2&isShare=true', 'id')
console.log(id) // 2
// 方法五 URLSearchParams
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
字符串每三位加一个逗号
---方法1:
// JS 自带的 toLocaleString
function formatNumber(num) {
const front = String(num).split('.')[0];
// 如果是金钱的话,角分位的四舍五入的处理
// let end = '0.'+ String(num).split('.')[1];
// end = Math.round(Number(end) * 100) / 100;
// end = end.toFixed(2);
return Number(front).toLocaleString() + end;
}
console.log(formatNumber(123456789.123)) // 123,456,789.123
---方法2:
// 正则表达式
function formatNumber(num) {
return num.toString().replace(/\d+/, function (n) {
console.log('n===>>', n);
// (\d{3}) 换成 (?:\d{3})也行
return n.replace(/(\d)(?=(\d{3})+$)/g, '$1,')
})
}
console.log(formatNumber(123456789.123)) // 123,456,789.123
---方法3:
// 字符串递归方法
function formatNumber(num, chart=',', length=3) {
let result = ''
let nums = num.toString().split('.')
let int = nums[0]
let decmial = nums[1] ? '.' + nums[1] : ''
let index = 0
for (let n = int.length - 1; n >= 0; n--) {
index ++
result = int[n] + result
if (index % length === 0 && n !== 0) { result = chart + result }
}
return result + decmial
}
console.log(formatNumber(123456789.123)) // 123,456,789.123
---方法4:
// slice 截取分割
function formatNumber(num, char=',', length=3) {
let result = ''
let nums = num.toString().split('.')
let int = nums[0];
let decmial = nums[1] ? '.' + nums[1] : ''
while (int.length > length) {
result = char + int.slice(-length) + result
int = int.slice(0, int.length - length)
}
if (int) { result = int + result }
return result + decmial
}
console.log(formatNumber(123456789.123)) // 123,456,789.123
-------数字有小数版本
let format = n => {
let num = n.toString() // 转成字符串
let decimals = ''
// 判断是否有小数
num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
let len = num.length
if (len <= 3) {
return num
} else {
let temp = ''
let remainder = len % 3
decimals ? temp = '.' + decimals : temp
if (remainder > 0) { // 不是3的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
} else { // 是3的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',') + temp
}
}
}
format(12323.33) // '12,323.33'
-------数字无小数版本
let format = n => {
let num = n.toString()
let len = num.length
if (len <= 3) {
return num
} else {
let remainder = len % 3
if (remainder > 0) { // 不是3的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')
} else { // 是3的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
format(1232323) // '1,232,323'
toLocaleString和toFixed都是4舍5入
红黄蓝排序题
let test = ['红', '黄', '蓝','蓝', '黄', '蓝', '黄'];
function aaa(arr) {
let blueChange = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] === '红') {
arr.splice(i, 1);
arr.unshift('红');
}
if (arr[i] === '蓝' && i < (arr.length - blueChange)) {
arr.splice(i, 1);
arr.push('蓝');
blueChange++;
i--;
}
}
return arr;
}
console.log(aaa(test))
红绿黄灯循环打印问题
async function test(arr) {
let i = 0;
const cb = (item) => {
return new Promise((resolve) => {
setTimeout(e => {
console.log(item);
resolve()
}, item.time * 1000);
})
}
while(true) {
await cb(arr[i]);
i++;
if (i === 3) {
i = 0;
}
}
}
test([
{
type: 'red',
time: 3
},
{
type: 'yellow',
time: 4
},
{
type: 'green',
time: 5
}
])
js大数运算
function add(a, b) {
a = a.split('');
b = b.split('');
var jinwei = 0, result = [];
// 这里一定不要忘了加上“ || jinwei”,否则最后面一个进位会被漏掉
while(a.length || b.length || jinwei)
{
var temp = parseInt(a.pop() || 0) + parseInt(b.pop() || 0) + jinwei;
result.push(temp % 10)
jinwei = Math.floor(temp / 10);
}
return result.reverse().join('');
}
let y=add('1234', '39'); // "1273"
console.log(y)
10进位转为17进位
const config = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G'];
function handle(num) {
let str = num.toString();
let p = str.length - 1;
const result = [];
tem = num;
while (tem !== 0) {
let value = parseInt(tem % 17);
tem = Math.floor(tem / 17);
result.push(config[value]);
}
result.reverse();
return result.join('');
}
console.log(handle(98134)) // 12G9A
39. 组合总和
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
var combinationSum = function(candidates, target) {
const ans = [];
const dfs = (target, combine, idx) => {
if (idx === candidates.length) {
return;
}
if (target === 0) {
ans.push(combine);
return;
}
// 直接跳过
dfs(target, combine, idx + 1);
// 选择当前数
if (target - candidates[idx] >= 0) {
dfs(target - candidates[idx], [...combine, candidates[idx]], idx);
}
}
dfs(target, [], 0);
return ans;
};
LCR 085. 括号生成
正整数 n 代表生成括号的对数,请设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
var generateParenthesis = function(n) {
const result = [];
const hash = {};
function handle(leftVal, rightVal, cache = []) {
let left = leftVal;
let right = rightVal;
if(cache.length === n * 2) {
const resultTem = cache.join('');
result.push(resultTem);
return;
}
if(left > 0) {
const temLeft = cache.slice(0);
temLeft.push('(')
left--;
handle(left, right, temLeft);
}
if(right > 0 && leftVal < right) {
const temRight = cache.slice(0);
temRight.push(')')
handle(leftVal, right-1, temRight);
}
}
handle(n, n);
return result;
};
基本的数据结构
数组,字符串
二叉树
堆
栈,队列
哈希
链表
线性,非线性,图
链表
链表,单向链表,双向链表
写一个简单链表类LinkList,包含insert, append
最简单链表的代码
class Node{ // 代表的是链表中的某一个节点
constructor(element){
this.val = element;
this.next = null;
}
}
class LinkList {
constructor() {
this.head = null;
this.length = 0;
}
insert(val, position){
let node = new Node(val);
if(!this.head) {
this.head = node;
} else {
let current = this.head;
let cursor = 0;
let pre;
// 前++后++都行
while(++cursor < position) {
pre = current;
current = current.next;
}
pre.next = node;
node.next = current;
this.length++
}
}
append(val){
let node = new Node(val);
if(!this.head) {
this.head = node;
} else {
let current = this.head;
let cursor = 0;
// 前++后++都行
while(cursor++ < this.length) {
current = current.next;
}
// let pre;
// while(current) {
// pre = current;
// current = current.next;
// }
current.next = node;
}
this.length++
}
}
let ll = new LinkList();
ll.append(1);
ll.append(2);
ll.append(3);
ll.insert(1,100);
// 实现删除 取值
console.log(JSON.stringify(ll));
Set
写一个Set类,包含add
class Set{
constructor(){
this.obj = {};
}
add(element){
if(!this.obj.hasOwnProperty(element)){
this.obj[element] = element;
}
}
}
let set = new Set(); // set的特点就是key value 相同
set.add(1);
set.add(1);
console.log(set);
Map
写一个Map类,包含set,get,calc
class Map{ // 松散 重复的话可以在加上链表
constructor(){
this.arr = [];
}
calc(key){
let total = 0;
for(let i = 0 ; i < key.length;i++){
total += key[i].charCodeAt()
}
return total % 100
}
set(key,value){
key = this.calc(key);
this.arr[key] = value
}
get(key){
key = this.calc(key);
return this.arr[key];
}
}
// 模拟hash表
let map = new Map(); // hash 表
map.set({a:1},123);
map.set('bbq',456);
console.log(map)
写一个简单树类Tree,包含add, insert
class Node {
constructor(val){
this.value = val;
this.left = null;
this.right = null
}
}
class Tree{
constructor(){
this.root = null; // 树的根
}
insert(root,newNode){
if(newNode.value < root.value){
if(root.left == null){
root.left = newNode
}else{
this.insert(root.left,newNode)
}
}else{
if(root.right == null){
root.right = newNode
}else{
this.insert(root.right,newNode)
}
}
}
add(element){
let node = new Node(element);
if(!this.root){
this.root = node;
}else{
this.insert(this.root,node);
}
}
}
let tree = new Tree;
tree.add(100);
tree.add(60);
tree.add(150);
tree.add(50);
console.log(JSON.stringify(tree));
二叉树
二叉树,红黑树,平衡树,B+树,B-树
二叉树、平衡二叉树、红黑树、B-树、B+树、B*树、T树之间的详解和比较
二叉树的先序、中序、后序的图形解释
二叉树:不一定右侧大于左侧,比如小顶堆之类的都属于二叉树。
二叉查找树(BST):解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表;
平衡二叉树(AVL):通过旋转解决了平衡的问题,但是频繁的插入,会有频繁的重复平衡旋转,导致旋转操作效率太低;
红黑树:通过舍弃严格的平衡和引入红黑节点,解决了AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多;
---红黑树比平衡二叉树的优点:红黑树的查询性能略微逊色于AVL树,因为其比AVL树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的AVL树最多多一次比较,但是,红黑树在插入和删除上优于AVL树,AVL树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于AVL树为了维持平衡的开销要小得多。
---实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择红黑树。
B树(B-树):通过将二叉树改为多路平衡查找树,解决了树过高的问题,并不一定是二叉的,关键字集合分布在整颗树中,任何一个关键字出现且只出现在一个结点中,搜索有可能在非叶子结点结束。
B+树:在B树的基础上,将非叶节点改造为不存储数据的纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。
B*树:B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针。
先序中序后序递归遍历一个树,和非递归算法可以看这里
// 先序遍历一个树
function treeRecurse(tree) {
if (!tree) return;
console.log(tree.element);
treeRecurse(tree.left);
treeRecurse(tree.right);
}
// 中序遍历一个树
function treeRecurse(tree) {
if (!tree) return;
treeRecurse(tree.left);
console.log(tree.element);
treeRecurse(tree.right);
}
// 后序遍历一个树
function treeRecurse(tree) {
if (!tree) return;
treeRecurse(tree.left);
treeRecurse(tree.right);
console.log(tree.element);
}
图
排序 自己的js排序
冒泡
function BubbleSort(array) {
var length = array.length;
for (var i = length - 1; i > 0; i--) { //用于缩小范围
for (var j = 0; j < i; j++) { // 在范围内进行冒泡,在此范围内最大的一个将冒到最后面
if (array[j] > array[j+1]) {
[array[j], array[j+1]] = [array[j+1], array[j]];
}
}
console.log(array);
console.log("-----------------------------");
}
return array;
}
var arr = [10,9,8,7,7,6,5,11,3];
var result = BubbleSort(arr);
console.log(result);
选择排序
插入排序
希尔排序
归并排序
快速排序 有两种方法,一个是两数组,另一个双指针, 时间复杂度是Nlogn,空间复杂度一个是1,另一个logn
快速博客1
稳定排序:插入排序,冒泡排序,归并排序
三种非比较排序:
排序算法之 计数排序 桶排序 基数排序
快速排序两数组实现
时间复杂度:平均O(nlogn),最坏O(n2),实际上大多数情况下小于O(nlogn)
空间复杂度:O(log1)(递归调用消耗),有的说是O(logn),我感觉也是O(logn)
function quickSort(array) {
if (array.length < 2) {
return array;
}
const target = array[0];
const left = [];
const right = [];
for (let i = 1; i < array.length; i++) {
if (array[i] < target) {
left.push(array[i]);
} else {
right.push(array[i]);
}
}
return quickSort(left).concat([target], quickSort(right));
}
快速排序两指针实现
时间复杂度:平均O(nlogn),最坏O(n2),实际上大多数情况下小于O(nlogn)
空间复杂度:O(logn)(递归调用消耗)
function quickSort(array, start = 0, end = array.length-1) {
if (end - start < 1) {
return;
}
const target = array[start];
let left = start;
let right = end;
while (left < right) {
while (left < right && array[right] >= target) {
right--;
}
array[left] = array[right];
while (left < right && array[left] < target) {
left++;
}
array[right] = array[left];
}
array[left] = target;
quickSort(array, start, left - 1);
quickSort(array, left + 1, end);
return array;
}
console.log(quickSort([3,1,9,2]))
归并排序
归并排序(两数组实现)
时间复杂度:O(nlogn). 空间复杂度:O(n)
function mergeSort(array) {
if (array.length < 2) {
return array;
}
const mid = Math.floor(array.length / 2);
const front = array.slice(0, mid);
const end = array.slice(mid);
return merge(mergeSort(front), mergeSort(end));
}
function merge(front, end) {
const temp = [];
while (front.length && end.length) {
if (front[0] < end[0]) {
temp.push(front.shift());
} else {
temp.push(end.shift());
}
}
while (front.length) {
temp.push(front.shift());
}
while (end.length) {
temp.push(end.shift());
}
return temp;
}
归并排序(两指针实现)
时间复杂度:O(nlogn). 空间复杂度:O(n)
function mergeSort(array, left =0 , right = array.length -1, temp = []) {
if (left < right) {
const mid = Math.floor((left + right) / 2);
mergeSort(array, left, mid, temp)
mergeSort(array, mid + 1, right, temp)
merge(array, left, right, temp);
}
return array;
}
function merge(array, start, end, temp) {
const mid = Math.floor((start + end) / 2);
let left = start;
let right = mid + 1;
let cursor = 0;
while (left <= mid && right <= end) {
if (array[left] < array[right]) {
temp[cursor++] = array[left++]
} else {
temp[cursor++] = array[right++]
}
}
while (left <= mid) {
temp[cursor++] = array[left++]
}
while (right <= end) {
temp[cursor++] = array[right++]
}
cursor = 0;
for (let i = start; i <= end; i++) {
array[i] = temp[cursor++];
}
}
多种查找方法
二分查找(折半查找)
let mid = Math.floor((left+right)/2);
let mid = left + Math.floor((right - left)/2);
顺序查找
递归查找
广度优先算法
深度优先算法
回溯算法
DFS与BFS算法
JS深度优先遍历和广度优先遍历
一般不用递归实现广度优先遍历,因为本来就不是一个递归的事情,如果非要用的话,代码特别不雅观。
// 递归遍历和 deque+while遍历 的区别
let obj = {
children: [
{
index: 0,
children: [
{
index: 1,
children: [
{
index: 3,
},
],
},
],
},
{
index: 4,
},
{
index: 6,
},
],
index: 0
};
// 深度递归(递归实现)
function test(node, result = []) {
if (node) {
result.push(node);
if (node.children) {
node.children.forEach(item => {
test(item, result)
})
}
}
return result
}
test(obj)
// 深度递归(非递归实现)
function test (node) {
const deque = [node];
const result = [];
while(deque.length) {
const item = deque.pop();
result.push(item);
item.children && item.children.forEach(i => {
deque.push(i)
})
}
return result;
}
test(obj)
// 广度遍历(非递归)
let test = (node) => {
let result = []
let deque = [node]
while (deque.length) {
//取第一个
let item = deque.shift()
result.push(item)
item.children && item.children.forEach(i => {
deque.push(i)
})
}
return result
}
先序深度优先算法
const rmdirSync = (filePath)=>{
let statObj = fs.statSync(filePath);
if(statObj.isDirectory()){
let dirs = fs.readdirSync(filePath); // 读取儿子目录
dirs = dirs.map(dir=>path.join(filePath,dir));
dirs.forEach(dir => { // 删除儿子
rmdirSync(dir);
});
fs.rmdirSync(filePath); // 再去删除自己
}else{
fs.unlinkSync(filePath); // 如果是文件删除即可
}
}
// 使用
rmdirSync('c');
leetcode刷题
二分查找
- 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
------答案
var search = function(nums, target) {
// right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间
let left = 0, right = nums.length - 1;
// 当left=right时,由于nums[right]在查找范围内,所以要包括此情况
while (left <= right) {
// 注意这里不是Math.floor((right + left)/2);
let mid = left + Math.floor((right - left)/2);
// 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内
if (nums[mid] > target) {
right = mid - 1; // 去左面闭区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右面闭区间寻找
} else {
return mid;
}
}
return -1;
};
来个变形,target不在是数据中的值,而是随机值
35. 搜索插入位置
```javascript
var search = function(nums, target) {
// right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间
let left = 0, right = nums.length - 1;
// 当left=right时,由于nums[right]在查找范围内,所以要包括此情况
while (left <= right) {
let mid = left + Math.floor((right - left)/2);
// 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内
if (nums[mid] > target) {
right = mid - 1; // 去左面闭区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右面闭区间寻找
} else {
return mid;
}
}
return right + 1;
};
const cursor = search([-1, 0, 3, 5, 9, 10], 9.5);
console.log(cursor);
- 山脉数组的峰顶索引
符合下列属性的数组 arr 称为 山脉数组 :
arr.length >= 3
存在 i(0 < i < arr.length - 1)使得:
arr[0] < arr[1] < … arr[i-1] < arr[i]
arr[i] > arr[i+1] > … > arr[arr.length - 1]
给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1] 的下标 i 。
示例 1:
输入:arr = [0,1,0]
输出:1
示例 2:
输入:arr = [0,2,1,0]
输出:1
示例 3:
输入:arr = [0,10,5,2]
输出:1
示例 4:
输入:arr = [3,4,5,1]
输出:2
示例 5:
输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2
-----------------------
var peakIndexInMountainArray = function(arr) {
let left=0;
let right=arr.length-1;
let mid=0;
while(left<right){
mid=left+Math.floor((right-left)/2);
//左右都小于mid,说明mid是山峰。
if(arr[mid-1]<arr[mid]&& arr[mid]>arr[mid+1]) break;
//右边比左边高,说明山峰在右侧
if(arr[mid+1]>arr[mid]) left=mid;
//右边比左边低,山峰在左侧
else if(arr[mid+1]<arr[mid]) right=mid;
}
return mid;
};
- 有效的完全平方数
给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
进阶:不要 使用任何内置的库函数,如 sqrt 。
示例 1:
输入:num = 16
输出:true
示例 2:
输入:num = 14
输出:false
-----答案
var isPerfectSquare = function(num) {
let left = 0;
let right = num;
while(left <= right){
let mid = left + Math.floor((right - left) /2);
let t = mid * mid;
if(t == num) return true;
else if(t < num) left = mid + 1;
else right = mid - 1;
}
return false;
};
- 寻找比目标字母大的最小字母
给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。
在比较时,字母是依序循环出现的。举个例子:
如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a'
示例 1:
输入: letters = ["c", "f", "j"],target = "a"
输出: "c"
示例 2:
输入: letters = ["c","f","j"], target = "c"
输出: "f"
示例 3:
输入: letters = ["c","f","j"], target = "d"
输出: "f"
----答案
var nextGreatestLetter = function(letters, target) {
var left = 0;
var right = letters.length - 1;
if(target >= letters[right]){
return letters[0];
}
while(left < right){
if(letters[left] > target){
break;
}else{
left++;
}
}
return letters[left];
};
- 第一个错误的版本
示例 1:
输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
示例 2:
输入:n = 1, bad = 1
输出:1
-----------答案
var solution = function(isBadVersion) {
/**
* @param {integer} n Total versions
* @return {integer} The first bad version
*/
return function(n) {
var left = 1;
var right = n;
while (left <= right) {
var mid = Math.floor((right-left)/2) + left;
// 只考虑一段,另一端不断缩进
/* 理解:
需要找到第一个bad的状态
若当前mid不是,则闭区间[left, mid]之间全不是
那第一个至少是mid+1的状态
其余什么都不用想
让另一边right = mid - 1即可(因为循环中是有=的)
*/
if (!isBadVersion(mid)) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
};
};
- 检查整数及其两倍数是否存在
给你一个整数数组 arr,请你检查是否存在两个整数 N 和 M,满足 N 是 M 的两倍(即,N = 2 * M)。
更正式地,检查是否存在两个下标 i 和 j 满足:
i != j
0 <= i, j < arr.length
arr[i] == 2 * arr[j]
示例 1:
输入:arr = [10,2,5,3]
输出:true
解释:N = 10 是 M = 5 的两倍,即 10 = 2 * 5 。
示例 2:
输入:arr = [7,1,14,11]
输出:true
解释:N = 14 是 M = 7 的两倍,即 14 = 2 * 7 。
示例 3:
输入:arr = [3,1,7,11]
输出:false
解释:在该情况下不存在 N 和 M 满足 N = 2 * M 。
------答案
var checkIfExist = function(arr) {
for (let i = 0; i < arr.length; i++) {
let num = arr[i] * 2;
for (let j = 0; j < arr.length; j++)
if (num == arr[j] && i != j)
return true;
}
return false;
};
--------
var checkIfExist2 = function(arr) {
const obj = new Map();
for (let i = 0; i < arr.length; i++) {
if (obj.has(i)) {
console.log('---', i)
return true;
}
obj.set(2 * arr[i], arr[i]);
}
return false;
}
console.log(checkIfExist([2,5,7,9]))
字符串(charAt, charCodeAt)
344. 反转字符串
示例 1:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
----答案
var reverseString = function(s) {
const n = s.length;
for (let left = 0, right = n - 1; left < right; ++left, --right) {
[s[left], s[right]] = [s[right], s[left]];
}
};
版本比较
function compare( version1 , version2 ) {
let arr1=version1.split(".");
let arr2=version2.split(".");
let length=Math.max(arr1.length,arr2.length);
for (let i = 0; i < length; i++) {
const n1 = Number(arr1[i]||0)
const n2 = Number(arr2[i]||0)
if (n1 > n2) return 1
if (n1 < n2) return -1
}
return 0
}
===== 变种题
function test(arr) {
arr.sort((a, b) => {
return compare(a, b);
});
return arr;
}
console.log(test(['2.1.2', '0.402.1', '3.20.1', '0.1.8', '5.1.2', '1.3.4.5']))
最长回文子串
方法1:中心扩展算法
时间复杂度n*n, 枚举中心位置的个数是2(n-1),每一次向两边扩散检测是否回文;
空间复杂度O(1),只用到常数个临时变量
function longestPalindrome(s) {
if (s == null || s.length < 1) {
return "";
}
var start = 0, end = 0;
for (var i = 0; i < s.length; i++) {
var {left: left1, right: right1} = expandAroundCenter(s, i, i);
var {left: left2, right: right2} = expandAroundCenter(s, i, i + 1);
if (right2 - left2 > right1 - left1) {
if(right2 - left2 > end -start) {
start = left2;
end = right2;
}
} else if(right1 - left1 > right2 - left2 ) {
if(right1 - left1 > end -start) {
start = left1;
end = right1;
}
}
}
return s.substring(start, end + 1);
}
function expandAroundCenter(s,left,right) {
while (left >= 0 && right <= s.length && s.charAt(right) && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return {
left: left+1,
right: right-1
};
}
substring第一个参数和第二个参数带小数都会忽略。
方法2:动态规划
时间复杂度n * n,这里的n为字符串的长度,
空间复杂度n*n
function longestPalindrome(s) {
var len = s.length;
if (len < 2) {
return s;
}
var maxLen = 1;
var begin = 0;
// dp[i][j] 表示 s[i..j] 是否是回文串
var dp = new Array(len).fill(false).map(w => new Array(len).fill(false));
// 初始化:所有长度为 1 的子串都是回文串
for (var i = 0; i < len; i++) {
dp[i][i] = true;
}
var charArray = s.split('');
// 递推开始
// 先枚举子串长度
for (var L = 2; L <= len; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (var i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
var j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
方法3:Manacher 算法
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度
方法一:滑动窗口
var lengthOfLongestSubstring = function (str) {
if (str.length <= 1) { return str.length }
let left = 0
let right = 1
let max = 0
let temp
while (right < str.length) {
temp = str.slice(left, right)
if (temp.indexOf(str.charAt(right)) > -1) {
left++
continue
} else {
right++
}
if (right - left > max) { max = right - left }
}
return max
};
------
function test (str) {
let p1 = 0;
let p2 = 1;
let max = '';
while(p2 <= str.length) {
const tem = str.slice(p1, p2);
if (tem.includes(str[p2])) {
p1++;
continue
} else {
p2++;
}
if (tem.length > max.length) {
max = tem;
}
}
return max;
}
console.log(test('asdfaqaw'))
139. 单词拆分
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词、--
-----答案
function wordBreak(str, dict) {
let dp = new Array(str.length + 1).fill(false);
dp[0] = true;
// 这里是1
for(let i =1;i<=str.length;i++) {
for(let j = 0; j <= i - 1; j++) {
if(dp[j] && dict.includes(str.slice(j, i))) {
dp[i] = true;
}
}
}
return dp[str.length]
}
76. 最小覆盖子串
子序列是有序的,而字串是无序的
left,right 初始都为0, [left, right)这样子就是一个空区间了
function minWindow(s, t) {
var cache = {};
var tLength = t.length;
for(var i =0;i< tLength;i++) {
cache[t[i]] = cache[t[i]] ? cache[t[i]]+1 : 1;
}
var cacheLeft = 0;
var cacheRight = 0;
for(var p1=0, p2=0;p2<s.length;p2++) {
if(cache[s[p2]]>0) {
tLength--;
}
if(cache[s[p2]] === undefined) {
cache[s[p2]] = -1;
} else {
cache[s[p2]]--;
}
if(tLength === 0) {
while(p1<p2 && cache[s[p1]] < 0) {
cache[s[p1]]++;
p1++;
}
if(cacheRight === 0 || p2 - p1 < cacheRight - cacheLeft) {
cacheRight = p2+1;
cacheLeft = p1;
}
}
}
return s.substring(cacheLeft, cacheRight);
}
数组
求中位数
let _median = arr => {
arr.sort((a, b) => {
if (a < b) return -1;
if (a > b) return 1;
return 0;
})
//求中位数
if (arr.length % 2 == 0) {
return (arr[arr.length / 2 - 1] + arr[arr.length / 2]) / 2;
} else {
return arr[Math.floor(arr.length / 2)];
}
};
1. 两数之和
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
// 答案一 双循环,暴力解法
算法复杂度: O(n^2) O(1)
var twoSum = function(nums, target) {
for(let i = 0, len = nums.length;i < len;i++){
// 因为同一元素不允许重复出现,所以从i的下一位开始遍历
for(let j = i + 1;j < len;j++) {
if(nums[i] + nums[j] === target) {
return [i, j];
}
}
}
// 所有样例都是有返回结果的,这里无所谓
return [-1, -1];
};
// 答案二 一次循环,使用HashMap进行记录
算法复杂度: O(n) O(n)
var twoSum = function(nums, target) {
const map = new Map();
for(let i = 0, len = nums.length;i < len;i++) {
if(map.has(target - nums[i])) {
return [map.get(target - nums[i]), i];
}
map.set(nums[i], i);
}
return [];
};
数组反转
---反转字符串
var reverseString = function(s) {
const n = s.length;
for (let left = 0, right = n - 1; left < right; ++left, --right) {
[s[left], s[right]] = [s[right], s[left]];
}
}
88. 合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
----方法1 方法一:直接合并后排序
时间复杂度:(m+n)log(m+n)
空间复杂度:log(m+n)
var merge = function(nums1, m, nums2, n) {
// 这里可以少定义一个变量,比起 [...nums1,...nums2]
nums1.splice(m, nums1.length - m, ...nums2);
nums1.sort((a, b) => a - b);
};
方法二:双指针
时间复杂度:O(m+n)
空间复杂度:O(m+n)
var merge = function(nums1, m, nums2, n) {
let p1 = 0, p2 = 0;
const sorted = new Array(m + n).fill(0);
var cur;
while (p1 < m || p2 < n) {
if (p1 === m) {
cur = nums2[p2++];
} else if (p2 === n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for (let i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
};
// 自己重写了
function merge2(nums1, nums2) {
let p1 = 0, p2 = 0;
let res = new Array(nums1.length + nums2.length).fill(0);
let current;
while(p1 < nums1.length || p2 < nums2.length) {
if (p1 === nums1.length) {
current = nums2[p2++];
} else if (p2 === nums2.length) {
current = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
current = nums1[p1++];
} else if (nums1[p1] >= nums2[p2]) {
current = nums2[p2++];
}
res[p1+p2-1] = current;
}
return res;
}
console.log(merge2([2,4,7], [1,3,5,20]))
17. 寻找两个正序数组的中位数
----方法1.合并数组 排序
时间复杂度:遍历全部数组 (m+n)
空间复杂度:开辟了一个数组,保存合并后的两个数组 O(m+n)
```clike
function test(arr1, arr2) {
arr1.splice(arr1.length, 0, ...arr2);
arr1.sort((a, b) => a-b); // 这里必须传参数
console.log(arr1)
if (arr1.length % 2 === 0) {
return (arr1[arr1.length / 2 - 1] + arr1[arr1.length / 2]) / 2
} else {
return arr1[arr1.length >> 1]
}
}
console.log(test([2,5,7], [1,3,9,11]))
----方法2:方法二:划分数组(太复杂,不建议背诵)
时间复杂度:O(log(min(m,n)))
(官网上说空间复杂度O(1))
思路:
这道题如果时间复杂度没有限定在 O(log(m+n))O(log(m+n)),我们可以用 O(m+n)O(m+n) 的算法解决,用两个指针分别指向两个数组,比较指针下的元素大小,一共移动次数为 (m+n + 1)/2,便是中位数。
首先,我们理解什么中位数:指的是该数左右个数相等。
比如:odd : [1,| 2 |,3],2 就是这个数组的中位数,左右两边都只要 1 位;
even: [1,| 2, 3 |,4],2,3 就是这个数组的中位数,左右两边 1 位;
那么,现在我们有两个数组:
nums1: [a1,a2,a3,...an]
nums2: [b1,b2,b3,...bn]
[nums1[:left1],nums2[:left2] | nums1[left1:], nums2[left2:]]
只要保证左右两边 个数 相同,中位数就在 | 这个边界旁边产生。
如何找边界值,我们可以用二分法,我们先确定 num1 取 m1 个数的左半边,那么 num2 取 m2 = (m+n+1)/2 - m1 的左半边,找到合适的 m1,就用二分法找。
当 [ [a1],[b1,b2,b3] | [a2,..an],[b4,...bn] ]
我们只需要比较 b3 和 a2 的关系的大小,就可以知道这种分法是不是准确的!
例如:我们令:
nums1 = [-1,1,3,5,7,9]
nums2 =[2,4,6,8,10,12,14,16]
当 m1 = 4,m2 = 3 ,它的中位数就是median = (num1[m1] + num2[m2])/2
对于代码中边界情况,大家需要自己琢磨
function findMedianSortedArrays(nums1, nums2) {
let length1 = nums1.length,
length2 = nums2.length;
if (length1 > length2) return findMedianSortedArrays(nums2, nums1); // 对nums1和nums2中长度较小的二分
let left1 = 0,
right1 = length1; // 进行二分的开始和结束位置
let mid1, mid2;
while (left1 <= right1) {
mid1 = (left1 + right1) >> 1;
// 这个是核心,不知道为啥要+1
mid2 = ((length1 + length2 + 1) >> 1) - mid1;
// 如果左边没字符了,就定义成-Infinity,让所有数都大于它,否则就是nums1二分的位置左边一个
let L1 = mid1 === 0 ? -Infinity : nums1[mid1 - 1];
// 如果左边没字符了,就定义成-Infinity,让所有数都大于它,否则就是nums2二分的位置左边一个
let L2 = mid2 === 0 ? -Infinity : nums2[mid2 - 1];
// 如果右边没字符了,就定义成Infinity,让所有数都小于它,否则就是nums1二分的位置
let R1 = mid1 === length1 ? Infinity : nums1[mid1];
// 如果右边没字符了,就定义成Infinity,让所有数都小于它,否则就是nums1二分的位置
let R2 = mid2 === length2 ? Infinity : nums2[mid2];
if (L1 > R2) { // 不符合交叉小于等于 继续二分
right1 = mid1 - 1;
} else if (L2 > R1) { // 不符合交叉小于等于 继续二分
left1 = mid1 + 1;
} else { // L1 <= R2 && L2 <= R1 符合交叉小于等于
return (length1 + length2) % 2 === 0
? (Math.max(L1, L2) + Math.min(R1, R2)) / 2
: Math.max(L1, L2);
}
}
}
console.log(findMedianSortedArrays([-1,1,3,5,7,9], [2,4,6,8,10,12,14,16])) // 6.5
349. 两个数组的交集
方法1:两个集合
时间复杂度:O(m+n)
空间复杂度:O(m+n)
var intersection = function (arr1, arr2) {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
return handle(set1, set2);
};
function handle(set1, set2) {
if (set1.size > set2.size) {
return handle(set2, set1)
}
let result = new Set();
for (let item of set1) {
if (set2.has(item)) {
result.add(item);
}
}
return [...result];
}
// 空间换取时间
function test(arr1, arr2) {
const set1 = new Set(arr1);
const result = [];
for(var i=0;i<arr2.length;i++) {
if (set1.has(arr2[i])) {
result.push(arr2[i])
}
}
return result;
}
方法2:排序 + 双指针
时间复杂度 O(mlogm+nlogn)
空间复杂度 O(logm+logn)
var intersection = function (nums1, nums2) {
nums1.sort((x, y) => x - y);
nums2.sort((x, y) => x - y);
const length1 = nums1.length,
length2 = nums2.length;
let p1 = 0,
p2 = 0;
const res = [];
while (p1 < length1 && p2 < length2) {
const num1 = nums1[p1],
num2 = nums2[p2];
if (num1 === num2) {
// 保证加入元素的唯一性
if (res.length > 0 || num1 !== res[res.length - 1]) {
res.push(num1);
}
p1++;
p2++;
} else if (num1 < num2) {
p1++;
} else {
p2++;
}
}
return res;
};
console.log(intersection([2,3,5,7], [4,5,6]))
15. 三数之和
数组三数为flag的所有组合
---方法1 暴力3循环
---方法2 排序 + 双指针
时间复杂度:O(N^2) 空间复杂度O(N)
function test(nums, target) {
nums.sort((a, b) => a - b)
let result = []
for (let i = 0; i < nums.length - 2; i++) {
// 当遍历下一个target与前面的相同时,跳过
if (i > 0 && nums[i] == nums[i - 1]) continue
let left = i + 1, right = nums.length - 1;
const rest = target - nums[i];
while (left < right) {
if (nums[left] + nums[right] === rest) {
result.push([nums[i], nums[left], nums[right]])
// 准备夹逼前,将左右俩边移到相同数值最紧处
// 这里的两句其实不用看,用来处理重复的时候,一般测试用例不会重复
// while (left < right && nums[left + 1] == nums[left]) left++
// while (left < right && nums[right - 1] == nums[right]) right--
left++
right--
} else if (nums[left] + nums[right] > rest) {
right--
} else {
left++
}
}
}
return result
};
34. 在排序数组中查找元素的第一个和最后一个位置
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
--------答案
var searchRange = function(nums, target) {
let left = 0;
let right = nums.length - 1; // 定义target在左闭右闭的区间里,[left, right]
while(left<=right){
while(left<right&&nums[left]<target){
left++;
}
while(left<right&&nums[right]>target){
right--;
}
if(nums[left] == target&&nums[right]==target){
return [left,right];
}else{
break;
}
}
return [-1, -1];
};
每个数右边第一个比他大的数组成的数组
/*
example:
输入:[3, 2, 1, 2, 6, 2, 3]
输出:[6, 6, 2, 6,-1, 3, -1]
*/
function test(arr) {
const cache = [];
arr.forEach((item, itemIndex) => {
for(let j=cache.length-1;j>=0;j--) {
if(cache[j].data < item && cache[j].result !== -1) {
cache[j].result = item;
}
}
cache.push({
index: itemIndex,
data: item,
result: -1
});
});
return cache.map(item => item.result)
}
console.log(findFirstLarge([3, 2, 1, 2, 6, 2, 3]));
// 6,6,2,6,-1,3,-1
除了自己以外其他数的乘积
// 输入[1,2,3,4]
// 输出[24,12,8,6]
// 一定看这里的思路
// 思路第一步先用缓存数组,遍历cache每一步都要*item(如果不存在按1), caceh.push,是上一个没有撑item前的值,在*上一个他自己,如果不存在给1
function test(arr) {
const cache = [];
arr.forEach((item, itemIndex) => {
const prevHandleData = cache[cache.length-1]?.handleData;
cache.forEach(w => {
w.handleData = (w.handleData || 1) * item
});
cache.push({
index: itemIndex,
data: item,
handleData: (prevHandleData || 1) * (arr[itemIndex-1] || 1)
});
console.log('cache===', cache);
});
return cache.map(i => i.handleData)
}
console.log(test([1,2,3,4]))
53. 最大子数组和
方法一:动态规划
// 思路需要一个max 第一步
function test(arr1) {
let max = arr1[0]; // 或者arr1[0] 或者 -Infinity
arr1.forEach(item => {
max = Math.max(max, item); // 这样子无法计算+item和不加item的大小
})
}
function test(arr1) {
let prevMax = 0;
let max = arr1[0];
arr1.forEach(item => {
prevMax = Math.max(prevMax+item, item);
max = Math.max(max, prevMax);
});
return max;
}
方法二:分治
function Status(l, r, m, i) {
this.lSum = l;
this.rSum = r;
this.mSum = m;
this.iSum = i;
}
const pushUp = (l, r) => {
const iSum = l.iSum + r.iSum;
const lSum = Math.max(l.lSum, l.iSum + r.lSum);
const rSum = Math.max(r.rSum, r.iSum + l.rSum);
const mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);
return new Status(lSum, rSum, mSum, iSum);
}
const getInfo = (a, l, r) => {
if (l === r) {
return new Status(a[l], a[l], a[l], a[l]);
}
const m = (l + r) >> 1;
const lSub = getInfo(a, l, m);
const rSub = getInfo(a, m + 1, r);
return pushUp(lSub, rSub);
}
var maxSubArray = function(nums) {
return getInfo(nums, 0, nums.length - 1).mSum;
};
链表
876-链表的中间结点
var middleNode = function(head) {
slow = fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
};
141. 环形链表
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
var hasCycle = function(head) {
let slow = head, fast = head;
while(fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if(slow === fast) {
return true;
}
}
return false;
};
剑指 Offer II 024. 反转链表
方法一:迭代
function test(head) {
let prev = null;
let curr = head;
while (curr) {
// 保存下一个迭代的节点
const next = curr.next;
// 下一个节点指向前一个节点
curr.next = prev;
// 调整prev为当前curr
prev = curr;
// 调整curr为当前next
curr = next;
}
return prev;
}
方法二:递归
var reverseList = function(head) {
if (head == null || head.next == null) {
return head;
}
const newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
};
21. 合并两个有序链表
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
----生成的链表是新的链表,用while代替递归
// 合并两个有序链表
function merge(head1, head2) {
let newHead = new ListNode(0);
let cur = newHead;
while(head1 && head2) {
if(head1.val <= head2.val) {
cur.next = head1;
head1 = head1.next;
} else {
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
cur.next = head1 ? head1 : head2;
return newHead.next;
}
---------------------------
var mergeTwoLists = function(l1, l2) {
if (l1 === null) {
return l2;
} else if (l2 === null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
};
23. 合并K个升序链表
19. 删除链表的倒数第 N 个结点
function removeNthFromEnd(head, k){
const result = [];
let cur = head;
let count = 0;
while(cur) {
count++;
result.push(cur);
cur = cur.next;
}
if(count === k) {
return head.next;
}
result[count-k-1].next = result[count-k].next;
return head;
}
// 方法1:求链表长度
// function removeNthFromEnd(head, n) {
// var dummy = new ListNode(0, head); // 0是val, head是 next = head;
// var length = getLength(head);
// var cur = dummy;
// for (var i = 1; i < length - n + 1; ++i) {
// cur = cur.next;
// }
// cur.next = cur.next.next;
// var ans = dummy.next;
// return ans;
// }
function test(head, n) {
var prev = head;
var length = getLength(head);
var cur = prev;
for (var i = 0; i < length - n; ++i) {
cur = cur.next;
}
cur.next = cur.next.next;
return prev.next;
}
function getLength(head) {
var length = 0;
while (head != null) {
++length;
head = head.next;
}
return length;
}
// 方法2:栈
// function removeNthFromEnd(head, n) {
// var dummy = new ListNode(0, head);
// var stack = new Array();
// var cur = dummy;
// while (cur != null) {
// stack.push(cur);
// cur = cur.next;
// }
// for (var i = 0; i < n; ++i) {
// stack.pop();
// }
// var prev = stack.shift();
// prev.next = prev.next.next;
// var ans = dummy.next;
// return ans;
// }
function test(head, n) {
var prev = head;
var cur = prev;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
for (var i = 0; i < n; ++i) {
stack.pop();
}
var prev = stack.shift();
prev.next = prev.next.next;
return prev.next;
}
相交链表
function test(head1, head2) {
const cache1 = new Set();
let temp1 = head1;
while(temp1) {
cache1.add(temp1)
temp1= temp1.next;
}
let temp2 = head2;
while(temp2) {
if (cache1.has(temp2)) {
return temp2;
}
temp2= temp2.next;
}
return null;
}
148-排序链表
入:head = [4,2,1,3]
输出:[1,2,3,4]
----答案
var sortList = function(head) {
return mergeList(head, null); // 左闭右开区间,即右边界是最后一个元素的右边,即 null
};
function mergeList(start, end) {
if(!start) return null;
// 左闭右开区间
if(start.next === end) {
start.next = null; // 由于 end 属于右边那部分的,不关左边事,所以断开连接再返回
return start;
}
let slow = start, fast = start;
// 找中点
while(fast !== end) {
slow = slow.next;
fast = fast.next;
if(fast !== end) {
fast = fast.next;
}
}
let mid = slow;
return merge(mergeList(start, mid), mergeList(mid, end)); // 对中点的左右两部分继续递归,然后对递归返回的结果 执行合并两个有序链表的算法
}
// 合并两个有序链表
function merge(head1, head2) {
let newHead = new ListNode(0);
let cur = newHead;
while(head1 && head2) {
if(head1.val <= head2.val) {
cur.next = head1;
head1 = head1.next;
} else {
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
cur.next = head1 ? head1 : head2;
return newHead.next;
}
二叉搜索树学习
二叉树的最大深度
function test(tree) {
if(!tree) {
return 0;
}
return Math.max(test(tree.left), test(tree.right)) + 1;
}
二叉树的最小深度
function test(tree) {
if(!tree.root) {
return 0;
}
if(!tree.left) {
return test(tree.right) + 1;
}
if(!tree.right) {
return test(tree.left) + 1;
}
return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
}
剑指 Offer II 056. 二叉搜索树中两个节点之和
给定一个二叉搜索树的 根节点 root 和一个整数 k , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 k 。假设二叉搜索树中节点的值均唯一。
示例 1:
输入: root = [8,6,10,5,7,9,11], k = 12
输出: true
解释: 节点 5 和节点 7 之和等于 12
示例 2:
输入: root = [8,6,10,5,7,9,11], k = 22
输出: false
解释: 不存在两个节点值之和为 22 的节点
// ---- 答案
// 思路和算法
// 我们可以使用深度优先搜索的方式遍历整棵树,用哈希表记录遍历过的节点的值。
// 对于一个值为 xx 的节点,我们检查哈希表中是否存在 k - xk−x 即可。如果存在对应的元素,那么我们就可以在该树上找到两个节点的和为 kk;否则,我们将 xx 放入到哈希表中。
// 如果遍历完整棵树都不存在对应的元素,那么该树上不存在两个和为 kk 的节点。
var findTarget = function(root, k) {
const set = new Set();
// 因为二叉树遍历需要递归,而set只需要一次就行,所以这里需要内函数helper完成递归
const helper = (root, k) => {
if (!root) {
return false;
}
if (set.has(k - root.val)) {
return true;
}
set.add(root.val);
return helper(root.left, k) || helper(root.right, k);
}
return helper(root, k);
};
// 自己重写
function test(tree, target, set = new Set()) {
if(!root) {
return false;
}
if (set.has(target - root.val)) {
return true;
}
set.add(root.val);
return test(tree.left, target, set) || test(tree.right, target, set);
}
112. 路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
----思路二:根据targetSum求差
var hasPathSum = function(root, targetSum) {
if(!root) {
return false;
}
if(!root.left && !root.right) {
return targetSum === root.val;
}
return hasPathSum(root.left, targetSum - root.val) ||
hasPathSum(root.right, targetSum - root.val);
};
---- 思路一:直接求和
var hasPathSum = function(root, targetSum) {
return (function(root, sum){
if(!root) return false;
if(!root.left && !root.right) {
return sum + root.val === targetSum;
}
sum += root.val;
return arguments.callee(root.left, sum) ||
arguments.callee(root.right, sum);
})(root, 0);
};
// 如果要找到那些路径的值
const result = [];
var hasPathSum = function(root, targetSum, res = []) {
if(!root) {
return;
}
res.push(root.val);
if(!root.left && !root.right) {
if(targetSum === root.val) {
result.push(res);
}
return;
}
hasPathSum(root.left, targetSum - root.val, ...(res.map(item => [...item]))) // 简答的做个深拷贝
hasPathSum(root.right, targetSum - root.val, ...(res.map(item => [...item])))
};
700 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]
===== 答案
// 先序遍历
// BFS, 前序遍历
var searchBST = function(root, val) {
if (!root || val===undefined) return null;
if (root.val === val) return root;
return root.val > val ? searchBST(root.left, val) : searchBST(root.right, val);
};
// 方法2 迭代
var searchBST = function(root, val) {
if (!root || val===undefined) return null;
while(root) {
if (root.val === val) return root;
if (root.val > val) {
root = root.left;
} else {
root = root.right
}
}
return null;
};
中等难度:98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
------
const helper = (root, lower, upper) => {
if (root === null) {
return true;
}
if (root.val <= lower || root.val >= upper) {
return false;
}
return helper(root.left, lower, root.val) && helper(root.right, root.val, upper);
}
var isValidBST = function(root) {
return helper(root, -Infinity, Infinity);
};
剑指 Offer II 052. 展平二叉搜索树(二叉搜索树是有排序功能的)
注意最后还是转化为一颗树,非标准数
给你一棵二叉搜索树,请 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。
输入:root = [5,3,6,2,4,null,8,1,null,null,null,7,9]
输出:[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9]
// ==答案
// 先对输入的二叉搜索树执行中序遍历,将结果保存到一个列表中;
// 然后根据列表中的节点值,创建等价的只含有右节点的二叉搜索树,其过程等价于根据节点值创建一个链表
var increasingBST = function(root) {
const res = [];
inorder(root, res);
const init = new TreeNode(-1);
let current = init;
for (const value of res) {
current.right = new TreeNode(value);
current = current.right;
}
return init.right;
};
const inorder = (node, res) => {
if (!node) {
return;
}
inorder(node.left, res);
res.push(node.val);
inorder(node.right, res);
}
114. 二叉树展开为链表(非二叉搜索树是无标准排序功能的)
这里的二叉树并不是一颗真正的二叉树,左侧的子节点大于父节奏,下边那个题才是常规二叉树
示例 1:
输入:root = [1,2,5,3,4,null,6] (这里输入的树不是数组)
输出:[1,null,2,null,3,null,4,null,5,null,6] (这里最后输出的也是树不是数组)
// 思路这是一颗特殊的二叉树(顶节点是最小的值),用前序遍历最好
// 前序遍历
var flatten = function(root) {
const list = [];
preorderTraversal(root, list);
for (let i = 1; i < list.length; i++) {
// 这里利用了每一项curr其实还是一个tree node含有left,right
const prev = list[i - 1],
curr = list[i];
prev.left = null;
prev.right = curr;
}
};
const preorderTraversal = (root, list) => {
if (root != null) {
list.push(root);
preorderTraversal(root.left, list);
preorderTraversal(root.right, list);
}
}
702. 将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
// -思路
思路:根据上述理论知识,采用递归来进行高度平衡二叉搜索树的确立。
递归的步骤1:递归函数参数的确立。首先是数组必须传进来,我们需要根据数组获得二叉搜索树,其次,我们用递归的时候每次中间数组的下标位置就会发生变化,因此我们可以用中间数组的下标来当参数,但是,这会比较复杂,需要每次进行左-1,右+1操作。因此,我们采用的是用left指代数组的最左边,right指代数组的最右边。
递归步骤2:递归出口的确立:当left>right表明数组左边和右边已无元素,因此,二叉搜索树创建完成。
递归步骤3:递归相同过程的确立:在这里,首先是获得中间位置的数组下标,并将它传给根节点,然后确定根节点的左节点和右节点,然后返回根节点
// ===== 答案
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
function toTreeNode(arr, left, right) {
let mid = Math.floor((right + left) / 2);
let node = new TreeNode(arr[mid]);
if (left === right) return node;
node.right = toTreeNode(arr, mid + 1, right);
if (right - left === 1) return node;
node.left = toTreeNode(arr, left, mid - 1);
return node;
}
var sortedArrayToBST = function(nums) {
if (nums.length === 0) {
return null
}
return toTreeNode(nums, 0, nums.length - 1);
};
109. 有序链表转换二叉搜索树
输入: head = [-10,-3,0,5,9]
输出: [0,-3,9,-10,null,5]
解释: 一个可能的答案是[0,-3,9,-10,null,5],它表示所示的高度平衡的二叉搜索树。
// 快慢指针+中序遍历+递归
var sortedListToBST = function (head) {
function travese(head, tail) {
if (head === tail) {
return null
}
// 利用快慢指针找到中间的元素作为根节点
let slow = fast = head
while (fast !== tail && fast.next !== tail) {
slow = slow.next
fast = fast.next.next
}
let root = new TreeNode(slow.val)
root.left = travese(head, slow)
root.right = travese(slow.next, tail)
return root
}
return travese(head, null)
};
已知前序遍历序列和后序遍历序列,不能确定一棵二叉树
【结论】: 已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
但是已知前序遍历序列和后序遍历序列,是不能确定一棵二叉树的
即:没有中序遍历序列的情况下是无法确定一颗二叉树的
中等难度 96. 不同的二叉搜索树
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
输入:n = 3
输出:5
var numTrees = function(n) {
const G = new Array(n + 1).fill(0);
G[0] = 1;
G[1] = 1;
for (let i = 2; i <= n; ++i) {
for (let j = 1; j <= i; ++j) {
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
};
中等难度 95. 不同的二叉搜索树 II
给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。
输入:n = 3
输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]
// 不同的二叉搜索树
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
var generateTrees = function (n) {
// n 为 0 是返回[]
if (n === 0) return []
// 指定最大范围
return buildTree(1, n)
}
function buildTree(start, end) {
console.log('-1--', start, end)
let _result = []
// 指针交错递归终止
// 递归有时候很绕,你要看在递归前都做了啥,递归每次返回啥,递归截止的时候返回啥。
if (start > end) return [null]
// i指针滑动,枚举left和right分段的所有可能
for (let i = start; i <= end; i++) {
// 左侧和右侧生成树的集合 返回为数组
let left = buildTree(start, i - 1)
let right = buildTree(i + 1, end)
console.log('-2--', left, right)
// 循环左右两侧的树集合 分别拼接到新树上,并且存储到结果数组中
for (const leftNode of left) {
for (const rightNode of right) {
let node = new TreeNode(i)
node.left = leftNode
node.right = rightNode
_result.push(node)
}
}
}
console.log('-3--', _result)
// 返回指定范围生成的树集合
return _result
}
generateTrees(3);
========================================
-------进来-start-end- 1 3 undefined
-开始for大循环-start-end- 1 3 undefined
-开始for小循环-i- 1
-left开始 1
-------进来-start-end- 1 0 left进入递归
-提前结束-
-left形成-i-left- 1 [ null ]
-right开始 1
-------进来-start-end- 2 3 right进入递归
-开始for大循环-start-end- 2 3 right进入递归
-开始for小循环-i- 2
-left开始 2
-------进来-start-end- 2 1 left进入递归
-提前结束-
-left形成-i-left- 2 [ null ]
-right开始 2
-------进来-start-end- 3 3 right进入递归
-开始for大循环-start-end- 3 3 right进入递归
-开始for小循环-i- 3
-left开始 3
-------进来-start-end- 3 2 left进入递归
-提前结束-
-left形成-i-left- 3 [ null ]
-right开始 3
-------进来-start-end- 4 3 right进入递归
-提前结束-
-right形成-i-right- 3 [ null ]
-结果-result- [ TreeNode { val: 3, right: null, left: null } ]
----------------------------------------
-right形成-i-right- 2 [ TreeNode { val: 3, right: null, left: null } ]
-开始for小循环-i- 3
-left开始 3
-------进来-start-end- 2 2 left进入递归
-开始for大循环-start-end- 2 2 left进入递归
-开始for小循环-i- 2
-left开始 2
-------进来-start-end- 2 1 left进入递归
-提前结束-
-left形成-i-left- 2 [ null ]
-right开始 2
-------进来-start-end- 3 2 right进入递归
-提前结束-
-right形成-i-right- 2 [ null ]
-结果-result- [ TreeNode { val: 2, right: null, left: null } ]
----------------------------------------
-left形成-i-left- 3 [ TreeNode { val: 2, right: null, left: null } ]
-right开始 3
-------进来-start-end- 4 3 right进入递归
-提前结束-
-right形成-i-right- 3 [ null ]
-结果-result- [
TreeNode {
val: 2,
right: TreeNode { val: 3, right: null, left: null },
left: null
},
TreeNode {
val: 3,
right: null,
left: TreeNode { val: 2, right: null, left: null }
}
]
----------------------------------------
-right形成-i-right- 1 [
TreeNode {
val: 2,
right: TreeNode { val: 3, right: null, left: null },
left: null
},
TreeNode {
val: 3,
right: null,
left: TreeNode { val: 2, right: null, left: null }
}
]
-开始for小循环-i- 2
-left开始 2
-------进来-start-end- 1 1 left进入递归
-开始for大循环-start-end- 1 1 left进入递归
-开始for小循环-i- 1
-left开始 1
-------进来-start-end- 1 0 left进入递归
-提前结束-
-left形成-i-left- 1 [ null ]
-right开始 1
-------进来-start-end- 2 1 right进入递归
-提前结束-
-right形成-i-right- 1 [ null ]
-结果-result- [ TreeNode { val: 1, right: null, left: null } ]
----------------------------------------
-left形成-i-left- 2 [ TreeNode { val: 1, right: null, left: null } ]
-right开始 2
-------进来-start-end- 3 3 right进入递归
-开始for大循环-start-end- 3 3 right进入递归
-开始for小循环-i- 3
-left开始 3
-------进来-start-end- 3 2 left进入递归
-提前结束-
-left形成-i-left- 3 [ null ]
-right开始 3
-------进来-start-end- 4 3 right进入递归
-提前结束-
-right形成-i-right- 3 [ null ]
-结果-result- [ TreeNode { val: 3, right: null, left: null } ]
----------------------------------------
-right形成-i-right- 2 [ TreeNode { val: 3, right: null, left: null } ]
-开始for小循环-i- 3
-left开始 3
-------进来-start-end- 1 2 left进入递归
-开始for大循环-start-end- 1 2 left进入递归
-开始for小循环-i- 1
-left开始 1
-------进来-start-end- 1 0 left进入递归
-提前结束-
-left形成-i-left- 1 [ null ]
-right开始 1
-------进来-start-end- 2 2 right进入递归
-开始for大循环-start-end- 2 2 right进入递归
-开始for小循环-i- 2
-left开始 2
-------进来-start-end- 2 1 left进入递归
-提前结束-
-left形成-i-left- 2 [ null ]
-right开始 2
-------进来-start-end- 3 2 right进入递归
-提前结束-
-right形成-i-right- 2 [ null ]
-结果-result- [ TreeNode { val: 2, right: null, left: null } ]
----------------------------------------
-right形成-i-right- 1 [ TreeNode { val: 2, right: null, left: null } ]
-开始for小循环-i- 2
-left开始 2
-------进来-start-end- 1 1 left进入递归
-开始for大循环-start-end- 1 1 left进入递归
-开始for小循环-i- 1
-left开始 1
-------进来-start-end- 1 0 left进入递归
-提前结束-
-left形成-i-left- 1 [ null ]
-right开始 1
-------进来-start-end- 2 1 right进入递归
-提前结束-
-right形成-i-right- 1 [ null ]
-结果-result- [ TreeNode { val: 1, right: null, left: null } ]
----------------------------------------
-left形成-i-left- 2 [ TreeNode { val: 1, right: null, left: null } ]
-right开始 2
-------进来-start-end- 3 2 right进入递归
-提前结束-
-right形成-i-right- 2 [ null ]
-结果-result- [
TreeNode {
val: 1,
right: TreeNode { val: 2, right: null, left: null },
left: null
},
TreeNode {
val: 2,
right: null,
left: TreeNode { val: 1, right: null, left: null }
}
]
----------------------------------------
-left形成-i-left- 3 [
TreeNode {
val: 1,
right: TreeNode { val: 2, right: null, left: null },
left: null
},
TreeNode {
val: 2,
right: null,
left: TreeNode { val: 1, right: null, left: null }
}
]
-right开始 3
-------进来-start-end- 4 3 right进入递归
-提前结束-
-right形成-i-right- 3 [ null ]
-结果-result- [
TreeNode {
val: 1,
right: TreeNode { val: 2, right: [TreeNode], left: null },
left: null
},
TreeNode {
val: 1,
right: TreeNode { val: 3, right: null, left: [TreeNode] },
left: null
},
TreeNode {
val: 2,
right: TreeNode { val: 3, right: null, left: null },
left: TreeNode { val: 1, right: null, left: null }
},
TreeNode {
val: 3,
right: null,
left: TreeNode { val: 1, right: [TreeNode], left: null }
},
TreeNode {
val: 3,
right: null,
left: TreeNode { val: 2, right: null, left: [TreeNode] }
}
]
中等难度:449. 序列化和反序列化二叉搜索树
序列化是将数据结构或对象转换为一系列位的过程,以便它可以存储在文件或内存缓冲区中,或通过网络连接链路传输,以便稍后在同一个或另一个计算机环境中重建。
设计一个算法来序列化和反序列化 二叉搜索树 。 对序列化/反序列化算法的工作方式没有限制。 您只需确保二叉搜索树可以序列化为字符串,并且可以将该字符串反序列化为最初的二叉搜索树。
编码的字符串应尽可能紧凑。
示例 1:
输入:root = [2,1,3]
输出:[2,1,3]
示例 2:
输入:root = []
输出:[]
---思路
给定一棵二叉树的「先序遍历」和「中序遍历」可以恢复这颗二叉树。给定一棵二叉树的「后序遍历」和「中序遍历」也可以恢复这颗二叉树。而对于二叉搜索树,给定「先序遍历」或者「后序遍历」,对其经过排序即可得到「中序遍历」。因此,仅对二叉搜索树做「先序遍历」或者「后序遍历」,即可达到序列化和反序列化的要求。此题解采用「后序遍历」的方法。
序列化时,只需要对二叉搜索树进行后序遍历,再将数组编码成字符串即可。
反序列化时,需要先将字符串解码成后序遍历的数组。在将后序遍历的数组恢复成二叉搜索树时,不需要先排序得到中序遍历的数组再根据中序和后序遍历的数组来恢复二叉树,而可以根据有序性直接由后序遍历的数组恢复二叉搜索树。后序遍历得到的数组中,根结点的值位于数组末尾,左子树的节点均小于根节点的值,右子树的节点均大于根节点的值,可以根据这些性质设计递归函数恢复二叉搜索树。
-----答案
// 序列化
var serialize = function(root) {
const list = [];
const postOrder = (root, list) => {
if (!root) {
return;
}
postOrder(root.left, list);
postOrder(root.right, list);
list.push(root.val);
}
postOrder(root, list);
const str = list.join(',');
return str;
};
// 反序列化
var deserialize = function(data) {
if (data.length === 0) {
return null;
}
let arr = data.split(',');
const length = arr.length;
const stack = [];
for (let i = 0; i < length; i++) {
stack.push(parseInt(arr[i]));
}
const construct = (lower, upper, stack) => {
if (stack.length === 0 || stack[stack.length - 1] < lower || stack[stack.length - 1] > upper) {
return null;
}
const val = stack.pop();
const root = new TreeNode(val);
root.right = construct(val, upper, stack);
root.left = construct(lower, val, stack);
return root;
}
return construct(-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, stack);
};
中等难度173. 二叉搜索树迭代器
实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器:
BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指针应初始化为一个不存在于 BST 中的数字,且该数字小于 BST 中的任何元素。
boolean hasNext() 如果向指针右侧遍历存在数字,则返回 true ;否则返回 false 。
int next()将指针向右移动,然后返回指针处的数字。
注意,指针初始化为一个不存在于 BST 中的数字,所以对 next() 的首次调用将返回 BST 中的最小元素。
你可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 的中序遍历中至少存在一个下一个数字。
输入
[“BSTIterator”, “next”, “next”, “hasNext”, “next”, “hasNext”, “next”, “hasNext”, “next”, “hasNext”]
[[[7, 3, 15, null, null, 9, 20]], [], [], [], [], [], [], [], [], []]
输出
[null, 3, 7, true, 9, true, 15, true, 20, false]
解释
BSTIterator bSTIterator = new BSTIterator([7, 3, 15, null, null, 9, 20]);
bSTIterator.next(); // 返回 3
bSTIterator.next(); // 返回 7
bSTIterator.hasNext(); // 返回 True
bSTIterator.next(); // 返回 9
bSTIterator.hasNext(); // 返回 True
bSTIterator.next(); // 返回 15
bSTIterator.hasNext(); // 返回 True
bSTIterator.next(); // 返回 20
bSTIterator.hasNext(); // 返回 False
var BSTIterator = function(root) {
this.idx = 0;
this.arr = [];
this.inorderTraversal(root, this.arr);
};
BSTIterator.prototype.next = function() {
return this.arr[this.idx++];
};
BSTIterator.prototype.hasNext = function() {
return this.idx < this.arr.length;
};
BSTIterator.prototype.inorderTraversal = function(root, arr) {
if (!root) {
return;
}
this.inorderTraversal(root.left, arr);
arr.push(root.val);
this.inorderTraversal(root.right, arr);
};
其他算法
- LRU 缓存
map的keys()的next方法居然是有顺序的。
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
---------------------
var LRUCache = function(capacity) {
this.map = new Map()
this.capacity = capacity
};
LRUCache.prototype.get = function(key) {
if(this.map.has(key)){
let value = this.map.get(key)
this.map.delete(key)
this.map.set(key, value)
return value
}
return -1
};
LRUCache.prototype.put = function(key, value) {
if (this.map.has(key)) {
this.map.delete(key)
}
this.map.set(key, value)
if(this.map.size > this.capacity){
this.map.delete(this.map.keys().next().value)
}
};
mapIterator
数据结构KMP算法配图详解(超详细)
最长相等前后缀
子串移动的距离是前缀和父串的后缀的位置重合
红黑树
红黑树详解
为什么这么多关于红黑树的面试题呢?
硬核图解面试最怕的红黑树【建议反复摩擦】
漫画:什么是红黑树?(整合版)
---红黑规则
1-节点不是黑色,就是红色(非黑即红)
2-根节点为黑色
3-叶节点为黑色(叶节点是指末梢的空节点 Nil或Null)
4-一个节点为红色,则其两个子节点必须是黑色的(根到叶子的所有路径,不可能存在两个连续的红色节点)
5-每个节点到叶子节点的所有路径,都包含相同数目的黑色节点(相同的黑色高度)
---红黑树的应用
Java中,TreeMap、TreeSet都使用红黑树作为底层数据结构
JDK 1.8开始,HashMap也引入了红黑树:当冲突的链表长度超过8时,自动转为红黑树
Linux底层的CFS进程调度算法中,vruntime使用红黑树进行存储。
多路复用技术的Epoll,其核心结构是红黑树 + 双向链表。
---红黑树的左旋
---红黑树的右旋
动态规划
动态规划
64. 最小路径和
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
function test(grid) {
// 这里的fill(null)如果不用的话,不能用map方法。
const dp = new Array(grid.length).fill(null).map((item, itemIndex) => new Array(grid[itemIndex].length));
dp[0][0] = grid[0][0];
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[0].length; j++) {
if (i === 0 && j === 0) { continue; }
else if (i === 0) { dp[0][j] = dp[0][j - 1] + grid[0][j]; }
else if (j === 0) { dp[i][0] = dp[i - 1][0] + grid[i][0]; }
else { dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j] }
}
}
return dp.at(-1).at(-1)
};
console.log(test([[1,3,1],[1,5,1],[4,2,1]])) // 7
62. 不同路径
var uniquePaths = function(m, n) {
const f = new Array(m).fill(0).map(() => new Array(n).fill(0));
for (let i = 0; i < m; i++) {
f[i][0] = 1;
}
for (let j = 0; j < n; j++) {
f[0][j] = 1;
}
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
};
121. 买卖股票的最佳时机
function test(prices) {
let minprice = Infinity;
let maxprofit = 0;
for (let i = 0; i < prices.length; i++) {
// 这里做了两点1.初始化minprice, 2.初始化minprice的时候确实不需要考虑max
// else if 只能判断一次,所以不用担心统一天两次操作
if (prices[i] < minprice) {
minprice = prices[i];
} else if (prices[i] - minprice > maxprofit) {
maxprofit = prices[i] - minprice;
}
}
return maxprofit;
}
console.log(test([7,1,5,3,6,4])) // 5
198. 打家劫舍
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
// 可以只要隔了一天,可以偷多次
function rob(nums) {
if (nums == null || nums.length == 0) {
return 0;
}
let length = nums.length;
if (length == 1) {
return nums[0];
}
let cacheMax = nums[0], max = Math.max(nums[0], nums[1]);
for (let i = 2; i < length; i++) {
let temp = max;
max = Math.max(cacheMax + nums[i], max);
cacheMax = temp;
}
return max;
}
console.log(test([1,2,3,1])) // 4
200. 不同的子序列
输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
rabbbit
rabbbit
rabbbit
function test(s, t) {
const m = s.length, n = t.length;
if (m < n) {
return 0;
}
const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) {
dp[i][n] = 1;
}
for (let i = m - 1; i >= 0; i--) {
for (let j = n - 1; j >= 0; j--) {
if (s[i] == t[j]) {
// 相同的时候,(+1+1) 另外和后边沿用
dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j];
} else {
// 不同的时候,延用后边的一个(也可以说和后边的一个字母保持一致)
dp[i][j] = dp[i + 1][j];
}
}
}
return dp[0][0];
};
console.log(test("rabbbit", "rabbit")) // 3
贪心算法
买卖股票的最佳时机 II
方法二:贪心
var maxProfit = function(prices) {
let ans = 0;
let n = prices.length;
for (let i = 1; i < n; ++i) {
ans += Math.max(0, prices[i] - prices[i - 1]);
}
return ans;
};
方法一:动态规划
var maxProfit = function(prices) {
const n = prices.length;
let dp0 = 0, dp1 = -prices[0];
for (let i = 1; i < n; ++i) {
let newDp0 = Math.max(dp0, dp1 + prices[i]);
let newDp1 = Math.max(dp1, dp0 - prices[i]);
dp0 = newDp0;
dp1 = newDp1;
}
return dp0;
};
斐波那契数列
function Fibonacci(n) {
if (n < 2) {
return n;
}
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
console.log(Fibonacci(6)) // 8
LeetCode 热题 HOT 100
17. 电话号码的字母组合
---------------------
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
---------------------
function letterCombinations(digits) {
var result = new Array();
if (digits.length == 0) {
return result;
}
var phoneMap = new Map();
phoneMap.set('2', "abc");
phoneMap.set('3', "def");
phoneMap.set('4', "ghi");
phoneMap.set('5', "jkl");
phoneMap.set('6', "mno");
phoneMap.set('7', "pqrs");
phoneMap.set('8', "tuv");
phoneMap.set('9', "wxyz");
backtrack(result, phoneMap, digits, 0, '');
return result;
}
function backtrack(result, phoneMap, digits,index, combination) {
if (index == digits.length) {
result.push(combination);
} else {
var digit = digits.charAt(index);
var letters = phoneMap.get(digit);
var lettersCount = letters.length;
for (var i = 0; i < lettersCount; i++) {
backtrack(result, phoneMap, digits, index + 1, combination + letters.charAt(i));
}
}
}
console.log(letterCombinations("23"))
46. 全排列
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
function permute(nums) {
var res = new Array();
var output = new Array();
for (var num of nums) {
output.push(num);
}
var n = nums.length;
backtrack(n, output, res, 0);
return res;
}
function backtrack(n, output, res, first) {
// 所有数都填完了
if (first === n) {
res.push(output);
}
for (var i = first; i < n; i++) {
// 动态维护数组
swap(output, first, i);
// 继续递归填下一个数
backtrack(n, output, res, first + 1);
// 撤销操作
swap(output, first, i);
}
}
function swap(array, left, right) {
let tem = array[left];
array[left] = array[right];
array[right] = tem;
}
console.log('---', permute([1,2,3]))
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
var subsets = function(nums) {
const t = [];
const ans = [];
const dfs = (cur, type) => {
if(type === 'dfs1') {
console.log('---dfs1进入', cur)
} else if(type === 'dfs2') {
console.log('***dfs2进入', cur)
} else {
console.log('---------进入dfs', cur)
}
if (cur === nums.length) {
ans.push(t.slice());
return;
}
t.push(nums[cur]);
console.log('执行第一个dfs', t, '加入了', nums[cur])
dfs(cur + 1, 'dfs1');
t.pop(t.length - 1);
console.log('执行第二个dfs', t, '去除了', nums[cur])
dfs(cur + 1, 'dfs2');
if(type === 'dfs1') {
console.log('---dfs1结束', cur)
} else if(type === 'dfs2') {
console.log('***dfs2结束', cur)
} else {
console.log('---------结束dfs', cur)
}
}
dfs(0);
return ans;
};
console.log(subsets([1,2,3]))
---------进入dfs 0
执行第一个dfs [ 1 ] 加入了 1
---dfs1进入 1
执行第一个dfs [ 1, 2 ] 加入了 2
---dfs1进入 2
执行第一个dfs [ 1, 2, 3 ] 加入了 3
---dfs1进入 3
执行第二个dfs [ 1, 2 ] 去除了 3
***dfs2进入 3
---dfs1结束 2
执行第二个dfs [ 1 ] 去除了 2
***dfs2进入 2
执行第一个dfs [ 1, 3 ] 加入了 3
---dfs1进入 3
执行第二个dfs [ 1 ] 去除了 3
***dfs2进入 3
***dfs2结束 2
---dfs1结束 1
执行第二个dfs [] 去除了 1
***dfs2进入 1
执行第一个dfs [ 2 ] 加入了 2
---dfs1进入 2
执行第一个dfs [ 2, 3 ] 加入了 3
---dfs1进入 3
执行第二个dfs [ 2 ] 去除了 3
***dfs2进入 3
---dfs1结束 2
执行第二个dfs [] 去除了 2
***dfs2进入 2
执行第一个dfs [ 3 ] 加入了 3
---dfs1进入 3
执行第二个dfs [] 去除了 3
***dfs2进入 3
***dfs2结束 2
***dfs2结束 1
136. 只出现一次的数字
function singleNumber(nums) {
var single = 0;
for (var num of nums) {
single = single^num;
}
return single;
}
链表交集
var getIntersectionNode = function(headA, headB) {
const visited = new Set();
let temp = headA;
while (temp !== null) {
visited.add(temp);
temp = temp.next;
}
temp = headB;
while (temp !== null) {
if (visited.has(temp)) {
return temp;
}
temp = temp.next;
}
return null;
};
287. 寻找重复数
输入:nums = [1,3,4,2,2]
输出:2
var findDuplicate = function(nums) {
const n = nums.length;
let l = 1, r = n - 1, ans = -1;
while (l <= r) {
let mid = (l + r) >> 1;
let cnt = 0;
for (let i = 0; i < n; ++i) {
if(nums[i] <= mid) {
cnt++;
}
}
if (cnt <= mid) {
l = mid + 1;
} else {
r = mid - 1;
ans = mid;
}
}
return ans;
};
---
方法2
var findDuplicate = function(nums) {
const n = nums.length;
let ans = 0;
// 确定二进制下最高位是多少
let bit_max = 31;
while (!((n - 1) >> bit_max)) {
bit_max -= 1;
}
for (let bit = 0; bit <= bit_max; ++bit) {
let x = 0, y = 0;
for (let i = 0; i < n; ++i) {
if (nums[i] & (1 << bit)) {
x += 1;
}
if (i >= 1 && (i & (1 << bit))) {
y += 1;
}
}
if (x > y) {
ans |= 1 << bit;
}
}
return ans;
};
最长递增子序列
岛屿数量
i++和++i
var i = 1;
console.log(i++); // 1
var s = 5;
console.log(++s); // 6
#其他的锻炼思维的面试题
输入"abc",输出["abc", "acb", "bac", "bca", "cab", "cba"]
// 递归的方式,注意输入入参数和出参数
function test(str) {
const tem = str.split('');
const result = [];
function inner(param, res = []) {
if(param.length < 1) {
if(res.length === str.length) {
result.push([...res]);
}
return;
}
// 第一步遍历
param.forEach(item => {
const other = param.filter(i => i !== item);
const otherTem = [...res, item]
inner(other, otherTem);
});
}
inner(tem);
return result;
}
// 这里想用返回值的方式处理这个回调,发现不论怎么处理都是有问题的
// 递归返回数组,其实都是有一定规律的,要不都是返回字符串,可以拼接,返回数据肯定有问题
function test2(str) {
const tem = str.split('');
function inner(param, res) {
let resultT = [];
// 第一步遍历
for(let i = 0;i < param.length;i++){
let item = param[i];
const other = param.filter(i => i !== item);
if(other.length === 1) {
return other;
}
const resultTem = [...res, item, ...inner(other, res)];
// resultT = [...resultT].concat(...resultTem); // 这里出来所有的都是连着
resultT.push(resultTem) // 这个多层数组嵌套,不是我们需要的
}
return resultT;
}
const result = [];
// 以为把第一轮提出来就可以,其他的都符合预期了
for(let i = 0;i < tem.length;i++){
let item = tem[i];
const other = tem.filter(i => i !== item);
result.push(inner(other));
}
return result;
}
console.log(test2('abcde'))
另外一种解法
function pailie (arr) {
if (arr.length === 1) {
return arr;
}
let first = arr.slice(0,1)[0];
let restArr = arr.slice(1);
let tem = pailie(restArr);
let res = [];
tem.forEach((item) => {
res = res.concat(add(item, first))
})
return res;
}
function add(arr, value) {
let res = [];
for (let i = 0; i<= arr.length; i++) {
let tem = [...arr]
tem.splice(i, 0, value);
res.push(tem)
}
return res;
}
console.log(pailie('123'.split('')))
==========
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
// 输入:n = 3
// 输出:["((()))","(()())","(())()","()(())","()()()"]
// 输入:n = 1
// 输出:["()"]
// 1 <= n <= 8
function test(n) {
const result = [];
function inner(n, left, right, str) {
if (left == n && right == n) {
result.push(str)
}
else {
if (left < n) inner(n, left + 1, right, str + "(");
if (right < n && left > right) inner(n, left, right + 1, str + ")");
}
}
inner(n, 0, 0, "");
return result;
}
console.log(test(3));