开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
前言
在我们的生活中一般不会用到什么算法,算法是一种辅助方式,帮助我们更优的达到目的,对于前端开发而言,diff算法倒是经常被提起,diff算法算是对比算法类。 今天我们就讲下常见的算法种类(前四种)和一些简单的试题。
常见种类
1. 双指针
2. 二分法
3. 动态规划
4. 滑动窗口
5. 广度遍历
6. 深度遍历
7. 回溯法
双指针
双指针主要分为两大类:快慢指针和左右指针。
左右指针:(两指针可以相向而行,也可以是背向而行)
相向:指针1和指针2相向而行
背向:指针1和指针2向相反的向进行移动
经典面试题:给定一个数组,数组的长度是n(n为偶数),给定一个值target,找出数组中两数之和最接近target的值是多少,(这里用到双向指针,最左侧为最小值,最右侧为最大值(因为数组进行了正向排序)。当求得和偏大时,就将右指针向左边移一位,取个小值后再获取两数之和的大小与target进行比对。当求得值偏小时,就将左指针指向的最小数有右移一位,获取一个稍大点的值进行求和比对,依此类推,获取最接近的值)
举例:const arr= [1,2,3,4] ,target =3, 那么答案就是3 arr[0]+arr[1]=3
function test(array, target) {
array.sort((a, b) => {
return a - b
}); //首先是进行正向的排序,便于确定大小值
let sum=10000;
let result=0;
let left =0; // 左指针
let right=array.length-1; //右指针
while (left < right) { //双向进行时必须保证左指针小于右指针,目的是为了防止数据节点的重复使用
let n1 = array[left];
let n2 = array[right];
let total = n1 + n2;
if (total > target) {
right--; //最大数据要向右边位移一位
} else {
left++; // 最小数据要向左边位移一位
}
// 利用绝对值的最小获取最接近的数值
let res = Math.abs(total - target);
if (sum > res) {
sum = res;
result = total;
}
}
return result;
}
const arr=[1,4,6,3,9,7];
const target1= 17;
console.log(test(arr,target1));
二分法
首先是从字面意思上讲,二分查找就是一分为二进行查找,二分查找又叫对半查找,也叫分半查找。但是,二分查找要求线性表必须采用顺序存储结构,不宜用于链式结构,而且表中元素按关键字有序排列,(一定要有序)
举例:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
const arr1 =[1,2,3,4,5,6,7,8,9,10];
const target1 =11;
function test(arr,target){
let left = 0;
let right = arr.length;
let mid =0;
while (left < right){
mid = Math.floor((left + right)/2);
if(arr[mid]>target){
right = mid;
}else if(target >arr[mid]){
if(left == mid){
left = mid +1;
}else {
left = mid;
}
}else {
return mid
}
}
if (left => right){
console.log('数组中不存在该数据');
}
}
test(arr1,target1);
滑动窗口
首先从字面意思上讲:就是一个在一个数组或者队列中通过类似于窗口左右滑动的形式来求解问题.
一般的正式回答是这样的:维护一个窗口(队列/数组),不断滑动,然后更新答案
运用场景:一般是求最大/最小的连续值
场景:给定一个数组,求n次连续的最大子数组值
示例:const arr = [1,2,3,4]; n =2;
首先一眼看去就是3+4 =7,最大值就是7
直接上代码:
function test(arr, n) {
// 首先要保证n不能大于数组的长度
if (n > arr.length) {
return
}
// 定义初始和为0;
let total = 0;
let maxtotal = 0;
// n 代表我们窗口的总宽度,length-1是为了避免我们加的时候越位
for (let i = 0; i < arr.length - 1; i++) {
total = arr[i] + arr[i + 1];
//每次获得总和后我们就对最大值进行一次更新
maxtotal = Math.max(total, maxtotal)
}
return maxtotal;
}
const arr1 = [1, 2, 3, 4], n1 = 2;
console.log(test(arr1, n1))// 7
其实这样的写法是针对n=2来写的,如果是其他的长度是可以进行判断的,判断条件很简单,那就是加一个while.如果我们不运用算法的话,代买里完全可以写多个的for循环,为了学习还是加一个while好点。
具体如下:
const k = 0;// 定义初始化相加的长度
while (K < n) { // 保证相加的连续次数不能大于n
total = total + arr[i+k];
K++;
}
动态规划
动态规划算法的核心思想是:将大问题划分为小问题进行解决。其中的思路是进行合理的缓存利用,避免大量的重复计算。 所以很多人都说动态规划就是在一定程度上利用空间换时间。(宇宙法则:上帝为你开一扇窗,必将为你关闭一扇门。)
找出最大的连续递增数组:
示例:const arr =[10,9,2,5,3,7,101,18];
结果: 最大数组长度是4 =>[2,3,7,101]
```
const arr = [10, 9, 2, 5, 3, 7, 101, 18];
const m1 = []; // 用来存放每次循环时得到的数组
for (let i = 0; i < arr.length; i++) {
let val = '';
m1[i] = [];
m1[i].push(arr[i]) // 每次循环时把自身放在第一位
val = arr[i];// 缓存上次取得结果,后续用来比较
for (let j = i; j < arr.length; j++) {
if (arr[i] < arr[j]) {
if ((arr[j] > val)) {
m1[i].push(arr[j])
} else {
if (j < arr.length - 1) { // 确保因为最后一个比较漏掉最后一位递增数据
m1[i].pop();
m1[i].push(arr[j]);
}
}
val = arr[j];
}
}
}
for (let i = 0; i < m1.length; i++) {
console.log(m1[i]); // 找出最大连续递增数组
}
```
示例2:找出最大子数组的和:
示例:const arr =[-2,1,-3,4,-1,2,1,-5, 4]
结果:【4,-1,2,1】 =》 6
```
const arr2 = [-2, 1, -3, 4, -1, 2, 1, -5, 4, 2];
function long(arr) {
let total = 0;
let res = arr[0];
for (let i = 0; i < arr.length; i++) {
total = Math.max(total + arr[i], arr[i]);//记录循环到当前下标位置的最大和
res = Math.max(total, res);// 每循环依次都取出当前位置的最大值
}
return res;
}
console.log(long(arr2));
```
结语
当我们在初学算法时最好是配一些图进行参考,不要觉得算法就是像我们学习框架那样简单容易上手,只有长期的积累和练习才能达到一定的掌握程度,也不要因为刚开始的难度就轻言放弃,希望每一位学习算法的同学都能够学有所成。