数据结构与算法笔记
数组常用的API
1.在数组末尾操作:
pop()在数组尾部删除元素
返回的是被删除的那个元素
原数组会发生改变
let myFish = ["angel", "clown", "mandarin", "surgeon"];
let popped = myFish.pop();
console.log(myFish);
// ["angel", "clown", "mandarin"]
console.log(popped);
// surgeon
push()在数组尾部增加元素
返回的是新数组的长度
原数组会发生改变
const animals = ['pigs', 'goats', 'sheep'];
const count = animals.push('cows');
console.log(count);
// expected output: 4
console.log(animals);
// expected output: Array ["pigs", "goats", "sheep", "cows"]
2.在数组头部操作:
unshift()在数组头部增加元素
返回的是新数组的长度
原数组会发生变化
const array1 = [1, 2, 3];
console.log(array1.unshift(4, 5));
// expected output: 5
console.log(array1);
// expected output: Array [4, 5, 1, 2, 3]
shift()在数组头部删除元素
返回的是删除的值
原数组会发生改变
const array1 = [1, 2, 3];
const firstElement = array1.shift();
console.log(array1);
// expected output: Array [2, 3]
console.log(firstElement);
// expected output: 1
3.filter()方法筛选数组中的元素获得新数组
返回的是一个新数组,原数组不会发生变化
filter为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或等价于 true 的值的元素创建一个新数组。
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
//回调里面一定要写成return形式啊 不然数组是空的
//const result = words.filter(word => {word.length > 6}); 这个数组里面就是空的
const result = words.filter(word => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]
filter()里面要写一个回调函数,回调函数return一个item的判断条件
4.数组的排序sort() !!!!!!!对数组排序不要直接array.sort()
sort()方法给数组进行排序的时候,会把数组里面的数字变成字符串再排序
const array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1);
// expected output: Array [1, 100000, 21, 30, 4]
如果是要用数字来进行排序的话:
var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
return a - b;
});
console.log(numbers);
也可以写成:
var numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b);
console.log(numbers);
// [1, 2, 3, 4, 5]
这个sort()里面可以写一个函数 (leetcode 剑指45题 把数组排成最小的数) 用来自定义排序顺序
如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。即 a 和 b 是两个将要被比较的元素:
如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
5.生成一个二维数组
let dp = new Array(len).fill(0).map(()=>new Array(n).fill(0))
6.splice()方法
通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
!!!此方法会改变原数组。
const months = ['Jan', 'March', 'April', 'June'];
months.splice(1, 0, 'Feb');
// inserts at index 1
console.log(months);
// expected output: Array ["Jan", "Feb", "March", "April", "June"]
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
start
指定修改的开始位置(从0计数)。
deleteCount 可选
整数,表示要移除的数组元素的个数。
item1, item2, ... 可选
要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素。
7.slice()方法
slice()方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。
!!!原始数组不会被改变。
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2));
// expected output: Array ["camel", "duck", "elephant"]
8.数组扁平化操作
//方法1 array.flat(n) n为扁平化的层次
const arr = [1,[2,[3,4]]]
console.log(arr.flat(2)) //[1,2,3,4]
//方法2 join(',')变成字符串 再split(',')变数组 但是要把字符串变成数字
const flatten = (arr) => arr.join(',').split(',').map(item=>Number(item));
const arr = [1, [2, [3, 4]]];
console.log(flatten(arr));
9.arr.reduce()方法
arr.reduce(函数,初始值)
arr.reduce((上次计算的结果,当前循环的item项)=>{},初始值)
arr.reduce((上次计算的结果,当前循环的item项)=>{return 上次的结果+当前循环的item项 },初始值)
Map
//构造一个实例
let mymap = new Map()
set()方法
为Map对象添加一个指定了健(key)和值(value)的新键值对
const map1 = new Map();
map1.set('bar', 'foo');
console.log(map1.get('bar'));
// expected output: "foo"
console.log(map1.get('baz'));
// expected output: undefined
如果是要更新map里面的值的话,只能用set方法重新插入key和value
如果对同一个键多次赋值, 后面的值将覆盖前面的值。
let map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) // "bbb"
get()方法
返回一个Map对象里面的某个健(key)的值(value)
const map1 = new Map();
map1.set('bar', 'foo');
console.log(map1.get('bar'));
// expected output: "foo"
console.log(map1.get('baz'));
// expected output: undefined
has()方法
判断Map对象里面是否存在某个健(key)
var myMap = new Map();
myMap.set("bar", "foo");
myMap.has("bar"); // returns true
myMap.has("baz"); // returns false
map的循环遍历方法
for of遍历
let myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (let [key, value] of myMap) {
console.log(key + " = " + value);
}
// 将会显示两个log。一个是"0 = zero"另一个是"1 = one"
for (let key of myMap.keys()) {
console.log(key);
}
// 将会显示两个log。 一个是 "0" 另一个是 "1"
for (let value of myMap.values()) {
console.log(value);
}
// 将会显示两个log。 一个是 "zero" 另一个是 "one"
for (let [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}
// 将会显示两个log。 一个是 "0 = zero" 另一个是 "1 = one"
字符串常用的API
随机生成一个字符串
Math.random().toString(36).substr(2);
indexOf()方法 注:在数组里面也有这个方法
返回在数组或者字符串中可以找到一个给定元素的第一个索引
str.indexOf(searchValue [, fromIndex])
lastindexOf()方法 注:在数组里面也有这个方法
遍历字符串
for in 一般用来遍历对象object 其他的引用类型一般不用
// i是字符串的索引值
for (let i in str) {
console.log(str[i]);
}
for of 一般用来遍历数组、map、字符串
// char 是每个字符串
for (let char of "Hello") {
console.log(char);
}
Slice()方法截取字符串
str.slice(a,b)
//a表示截取字符串的起始位置 b表示字符串截取的终止位置不包括b
join()方法把数组变成字符串返回
const elements = ['Fire', 'Air', 'Water'];
console.log(elements.join());
// expected output: "Fire,Air,Water"
console.log(elements.join(''));
// expected output: "FireAirWater"
console.log(elements.join('-'));
// expected output: "Fire-Air-Water"
arr.join([separator])
括号内部的参数表示分隔符
字符串匹配算法
leetcode:实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
BF算法:
注意点:
1.如果匹配不上的话主串的指针回退到最开始匹配的下一个字符串,模式串的指针回退到0
2.匹配完成后注意返回的是a-b 而不是 a
var strStr = function(haystack, needle) {
if(needle==='') return 0
var a = 0
var b = 0
while(a<haystack.length&&b<needle.length){
//如果字母匹配上了 就把两个指针都往后移
if(haystack[a]===needle[b]){
a++
b++
}
//如果没有匹配上 指针回退道最开始匹配的下一个位置 就是a-b+1 b指针回退到0
else{
a=a-b+1
b=0
}
//匹配完成后注意返回的是a-b 而不是 a
if(b===needle.length) return(a-b)
}
return -1
};
KMP算法
如果匹配不上的话,主串的指针不需要回退
链表
链表的创建 不带表头结点
//封装一个node类 里面有数据域和指针域 拿到外卖是因为后面的方法new node 访问不到
function Node(elem){
this.elem=elem
this.next=null
}
function Linklist(){
//链表的长度和表头结点
this.head = new Node()
this.length = 1
}
尾部追加数据
链表为空就直接把新节点添加到头指针上(head)
链表不为空 从头指针往后找 添加到最后一个节点的next上
Linklist.prototype.append = function(elem){
// 1.根据新元素创建一个新节点
var newnode = new Node(elem)
// 2.判断原来的链表是否为空
if(this.head.next===null) {
this.head.next=newnode
}else {
var current = this.head
while(current.next){
current = current.next
}
// 找到最后一项将其next赋值为node
current.next = newnode
}
this.length++
}
输出链表的数据域 toString()方法
Linklist.prototype.toString = function(){
//定义一个指针和结果变量
var current =this.head.next
var liststring= ""
//循环获取链表中的元素
while(current){
//字符串的拼接
liststring+=current.elem
current=current.next
}
return liststring
}
在任意位置插入insert()方法
这里如果用了表头结点的话就可以不需要单独判断是否 是在第一位置插入元素
其中的关键代码是:
newnode.next = current.next
current.next = newnode
Linklist.prototype.insert = function(pos,elem){
//1.检查插入位置是否合理
if(pos<1||pos>this.length) return false
//2.找到正确的位置 插入数据
var newnode = new Node(elem)
var current = this.head
var j=0 // j是一个循环的计数器
while(j<pos-1){
current=current.next
j++
}
newnode.next = current.next
current.next = newnode
this.length++; //注意这里是this.length
}
在指定的位置移除元素removeAt()方法
删除的关键代码是:
var q = current.next //用来存储被释放的结点
current.next = q.next
q=null //释放删除结点
Linklist.prototype.removeAt = function(pos){
//1.检查删除位置是否合理
if(pos<1||pos>this.length-1) return false
//2.找到正确的位置 删除数据
var j=0 // j是一个循环的计数器
var current = this.head
while(j<pos-1){
current=current.next
j++
}
var q = current.next //用来存储被释放的结点
current.next = q.next
q=null //释放删除结点
this.lenght--;
}
插入和删除都有一步关键的步骤: 就是将指针通过移动 指向被选元素的前一个元素 代码是:
var j=0 // j是一个循环的计数器
var current = this.head
while(j<pos-1){
current=current.next
j++
}
获取指定位置的元素
Linklist.prototype.indexOf = function(elem){
//1.定义移动指针和索引变量
var current = this.head
var index = 0;
//2. 找到指定的元素的位置
while(current){
if(current.elem===elem) return index;
index++;
current = current.next
}
return -1
}
set
里面的元素是没有顺序的,而且也不能重复
意味着:不能通过下标来进行访问
add()方法
在set的末尾添加一个指定的值
const set = new Set()
set.add(42)
set.add(42)
set.add(43)
for(const item of set){
console.log(item)
}
>42
>43
has()方法
判断对应的value是否存在set对象之中
var mySet = new Set();
mySet.add('foo');
mySet.has('foo'); // 返回 true
mySet.has('bar'); // 返回 false
树
创建二叉树
function Binary
A
/ \
B C
/ \ / | \
D E F G H
\
I
树的层次遍历(bfs)
对每一层的节点依次访问,访问完一层进入下一层,而且每个节点只访问一次。使用队列来完成
先往队列中插入左节点,再插入右节点。比如上面的那棵树,首先把A节点进队
将A节点弹出,A节点的左右节点依次入队,(B,C) 得到A节点
继续弹出队首元素,弹出B并将B的左右节点插入队列,(C,D,E) 得到B节点 依次类推
function traveseTree(root){
if(!root) return []
let queue= [root]
let res = []
while(queue.length!==0){
let temp = queue.unshift()
if(temp.left!==null) queue.push(temp.left)
if(temp.right!==null) queue.push(temp.right)
res.push(temp.val)
}
return res
}
树的深度遍历(dfs)
//递归实现:
let res = []
function defTraves(root){
if(!root) return
else{
res.push(root.val)
defTraves(root.left)
defTraves(root.right)
}
}
还有非递归实现:
非递归要用到栈:先往栈中压入右节点,再压入左节点
首先将A节点压入栈中,A节点弹出,将A节点的子节点C、B压入栈中 (先压入栈的后出来)
function traveseTree(root){
if(!root) return []
let stack= [root]
let res = []
while(stack.length!==0){
let temp = stack.pop()
if(temp.left!==null) stack.push(temp.left)
if(temp.right!==null) stack.push(temp.right)
res.push(temp.val)
}
return res
}
动态规划
动态规划的基本题型
背包问题 打家劫舍 股票问题 子序列问题
动态规划的解题步骤
1.dp数组以及下标的含义
2.动态规划的递推公式
3.dp数组如何初始化
4.遍历顺序 比如背包问题中的for循环先遍历背包还是先遍历物品
5.打印dp数组
递归三部曲
递归步骤:
1.确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里面加上这个参数,并且还要明确每次递归的返回值。
2.确定终止条件:写完了递归算法,如果终止条件有错误的话,会出现爆栈的情况。
3.单层递归的逻辑:确定每一层递归需要处理的信息。
回溯算法
排序算法
冒泡排序:
外层循环表示:一趟排序,一趟排序下来可以得到最大的数 第二趟 可以得到第二大的数 总共只需要len-1趟排序 内层循环表示:一趟中数字轮流进行比较 ,前面趟数排好的数字不需要再进行比较,所以是len-i躺
时间复杂度为:O(n^2) 空间复杂度为:O(1) 只要一个temp做记录
function BubbleSort(nums){
//1.获取数组的长度
let len = nums.length
//2.设置冒泡循环
for(let i=0;i<len-1;i++){
for(let j=0;j<len-i;j++){
if(nums[j]>nums[j+1]){
let temp
temp = nums[j+1]
nums[j+1] = nums[j]
nums[j] = temp
}
}
}
return nums
}
快速排序:
//找到枢纽元位置
function Partition(arr, low, high) {
var pio = arr[low]; //一定是arr[low] 不是arr[0]
while (low < high) { //一定是< 而不是<= 因为low=high的时候就找到pivoloc的位置了
while (low < high && arr[high] >= pio) { --high; } //这里也是小于
arr[low] = arr[high];
while (low < high && arr[low] <= pio) { ++low; }
arr[high] = arr[low];
}
arr[low] = pio; //把枢纽元素返回回去。放在low位置上
return low;
}
function QuickSort(arr, low, high) {
var pivotloc;
if (low < high) {//这里也是小于 写这个判断的原因是当对子排序进行缩小到只有一个的时候就不需要排序了然后这时候low = high 递归的结束条件
pivotloc = Partition(arr, low, high);
QuickSort(arr, low, pivotloc - 1); //-1是因为只需要把除pivoloc的位置处以外的进行再排序
QuickSort(arr, pivotloc + 1, high);//+1是因为只需要把除pivoloc的位置处以外的进行再排序
}
return arr;
}
直接插入排序
关键是找插入位置 从第i-1开始依次往前和temp比较,最后的插入位置是j+1
function InsertSort(arr){
let temp ,j
for(let i=1;i<arr.length;i++){ // 第一个元素直接插入进入 所以i从1开始
temp = arr[i] //先将待插入的元素放进哨兵中 免得往后移的时候被覆盖了
for(j=i-1;j>=0&&arr[j]>temp;j--){ //从前一个元素进行比较 如果小就往后移
arr[j+1] = arr[j]
} //在j+1的位置插入元素
arr[j+1] = temp
}
}
找插入位置的关键代码:
for(j=i-1;j>=0&&arr[j]>temp;j--){ //从前一个元素进行比较 如果小就往后移
arr[j+1] = arr[j]
} //在j+1的位置插入元素
arr[j+1] = temp
折半插入排序
用二分法查找方便,但是后面还需要挪动元素 最后时间复杂度还是O(n)
而且细节很多 很容易写错
function BinarySort(arr){
let temp
for(let i=1;i<arr.length;i++){
temp = arr[i]
let low = 1 ,high = i-1 ,mid //二分查找找位置的时候 其实细节很多 不好说 记住算了
while(low<=high){
mid = Math.floor((low+high)/2))
if(temp<arr[mid]) high = mid-1
else low = mid+1
}
for(let j=i-1;j>=high+1;j--) arr[j+1] = arr[j]
arr[high+1] = temp //最后插入的位置在high+1处
}
}
查找算法
二分查找
二分查找虽然思路简单,但是自己把代码写出来还是有问题的
var search = function(nums, target) {
let i = 0 ,j = nums.length-1 ,mid=Math.floor((i+j)/2)
while(i<=j){ //一个是这里必须是小于等于 开始没有写 有些测试用例通不过 书上也有解释
if(nums[mid] === target) return mid
else if(nums[mid]<target){
i = mid+1 //二个是这里的i要从mid+1开始
mid = Math.floor((i+j)/2)
}
else {
j = mid-1 // 不带mid+1 和mid-1的话容易现如死循环 比如 left=4 mid=4 right=5
mid = Math.floor((i+j)/2)
}
}
return -1
};
位运算
异或运算:
a^b :当a,b相同的时候结果为0,不同的时候结果为1
0^a=a
按位与&和逻辑与&&是不一样的
按位与&运算的优先级小于判断运算符===