算法示例
- 搜罗常见的算法及LeetCode题,记录手动实现的过程及思路
二维矩阵中指定数值搜索 示例如下表所示
1 | 4 | 7 | 11 | 15 |
2 | 5 | 8 | 12 | 19 |
3 | 6 | 9 | 16 | 22 |
10 | 13 | 14 | 17 | 24 |
18 | 21 | 23 | 26 | 30 |
- 每行的所有元素从左到右升序排列
- 每列的所有元素从上到下升序排列
期望输入矩阵数据实现 :
输入:matrix = [[1..15]..[18...30]],target = 5 输出:true
/**
* 暴力查找无意义,暂不考虑。
* 依据题意可知,数据从左往右增大,从上往下增大
* 首先取矩阵右上角数字
* 查找数字比它大就往下,查找数字比它小就往左
*/
var searchMatrix = function (matrix, target) {
var lengM = 0; //横坐标
var lengN = matrix[0].length - 1; //纵坐标
while (target !== initNum) {
var initNum = matrix[lengM][lengN];
// 查找成功返回
if (target == initNum) return true;
if (target > initNum) {
// 查找值比选取值大,则横坐标+1 下移查找
if (lengM < matrix.length - 1) lengM++;
//超出矩阵,查找失败
else return false;
} else {
//查找值比选取值小,则纵坐标-1 左移查找
if (lengN > 0) lengN--;
else return false;
}
}
};
合并两个有序数组
- 两个有序整数数组 nums1 和 nums2
- 将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
/**
* 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
* 输出:[1,2,2,3,5,6]
*/
var merge = function (nums1, m, nums2, n) {
// 通过改变数组长度去除冗余项
nums1.length = m;
// nums1=nums1.concat(nums2)
Array.prototype.push.apply(nums1, nums2);
nums1.sort(function (a, b) {
return a - b;
});
};
给定字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
- 回文串即:中心对称,可理解字符串反转后与原字符串相同
- 首先字符串进行过滤,只去字母和数字
- 拷贝当前字符串转数组然后反转与原数组比对;
var isPalindrome = function (initStr) {
initStr = initStr.replace(/[^a-zA-Z0-9]/g, "").toLocaleLowerCase();
let reverStr = String([...initStr].reverse()).replace(/,/g, "");
return initStr == reverStr;
};
反转后输出输入的数组
- 不考虑
reverse API
及暴力循环倒序
- 选定数组中间索引为标志
- 对称交换两方参数
var reverseString = function (arr) {
var len = arr.length;
for (let index = 0; index < len; index++) {
let flag = Math.floor(len / 2);
let lastParams = len - 1 - index;
if (index < flag)
[arr[index], arr[lastParams]] = [arr[lastParams], arr[index]];
else break;
}
return arr;
};
- 双指针一个从前开始,一个从后开始选中元素
- 利用 ES6 解构赋值交换双方数据
- 二者重逢即结束
var reverseString = function (arr) {
var left = 0,
right = arr.length - 1;
while (left <= right) {
[arr[left], arr[right]] = [arr[right], arr[left]];
left++;
right--;
}
return arr;
};
排序
冒泡排序
- 冒泡思想
- 拿当前项与后一项对比,比后项大交换位置否则不变
- 外轮循环 arr.length-1
- 这样每过一轮比较,当前数组中最大的就会放到末尾
- 内部需要循环 arr.lenth-1-第几次循环
- 图示,引用:blog.csdn.net/weixin_4119…
for (let i = 0; i < sort.length - 1; i++) {
for (let j = 0; j < sort.length - 1 - i; j++) {
if (sort[j] > sort[j + 1]) {
[sort[j], sort[j + 1]] = [sort[j + 1], sort[j]];
}
}
}
- 冒泡排序时间复杂度最坏
O(n²)
最好O(n)
平均O(n²)
空间复杂度O(1)
快速排序
- 快排思想
- 拿到传入数组的中间项为基准
- 数组中比基准小的放左边数组,大的放右边数组
- 递归调用自身,然后做数组拼接
- 图示
var nums = [2, 4, 1, 6, 3, 8, 3, 9, 4];
function quick(arr) {
// 如果传进来的数组中只有一项,那么无需再排
if (arr.length <= 1) {
return arr;
}
// 第一步拿到传入数组的中间项序列;
let flag = Math.floor(arr.length / 2);
// 在原数组中移除并保存;
let key = arr.splice(flag, 1)[0];
let arrLeft = []; //存放比中间项小的数组
let arrRight = []; //存放比中间项大的数组
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
// 比中间项大的放右边数组,小的放左边数组
item >= key ? arrRight.push(item) : arrLeft.push(item);
}
// 利用递归,左+中间项+右输出
return quick(arrLeft).concat(key, quick(arrRight));
}
var newArr = quick(nums);
console.log(newArr); //[ 1, 2, 3, 3, 4, 4, 6, 8, 9 ]
- 快排时间复杂度最坏
O(n²)
最好O(nlog₂n)
平均O(nlog₂n)
空间复杂度O(nlog₂n)
去重
let array = [2, 6, 8, 10, 12, 8, 6, 9, 18, 15];
利用 ES6 里的 Set
和 ...
展开运算符去重
- 利用 Set 成员具有唯一性去除重复项,
- 其返回的是 Set 类的实例,不是数组类型
- 利用
...
展开运算符或者Array.from
转化为数组
let arr = new Set(array);
let arr1 = [...new Set(array)]; //去除重复项且展开运算符转化为数组
let arr2 = Array.from(new Set(array)); //或者利用Array转化为数组
console.log(arr); //Set { 2, 6, 8, 10, 12, 9, 18, 15 }
console.log("展开运算符", arr1); // [ 2, 6, 8, 10, 12, 9, 18, 15 ]
console.log("Array方法", arr2); // [ 2, 6, 8, 10, 12, 9, 18, 15 ]
利用 indexOf()
去重
- 这个.. 没啥好说的
let newArray = [];
for (let i = 0; i < array.length; i++) {
if (newArray.indexOf(array[i]) == -1) {
newArray.push(array[i]);
}
}
console.log(newArray); //[ 2, 6, 8, 10, 12, 9, 18, 15 ]
哈希表思想去重(性能最高)
- 定义一个 hash 对象
- 将数组每个元素通过
hash[array[i]]
在 hash 表 中查询, - 如果数组元素在 hash 中,则
hash[array[i]]
结果为 true, - 如果不存在,则
hash[array[i]]
结果为 undefined(布尔值为 false)
let result = [];
let hash = {};
for (let i = 0; i < array.length; i++) {
if (!hash[array[i]]) {
result.push(array[i]);
hash[array[i]] = true;
}
}
console.log(result); //[ 2, 6, 8, 10, 12, 9, 18, 15 ]
数组扁平化
- 指将多次镶嵌的数组转化为只有一层
let arr = [[1, 2], [3, 5], [6, 7, [11, [12, 13, [14]]]], 10];
利用 ES6 里的 flat
let arr1 = arr;
arr1 = arr1.flat(Infinity); //这里是全部展开
// [1, 2, 3, 5, 6, 7, 11, 12, 13, 14, 10]
利用 toString
let arr2 = arr;
arr2 = arr2.toString().split(","); //展开为数组,但每项为字符串类型
//["1", "2", "3", "5", "6", "7", "11", "12", "13", "14", "10"]
arr2 = arr2.map((item) => parseFloat(item));
// [1, 2, 3, 5, 6, 7, 11, 12, 13, 14, 10]
利用递归循环实现
let arr3 = arr;
function myFlat() {
let result = [];
let that = this;
let fn = (arr) => {
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) {
fn(item);
continue;
}
result.push(item);
}
};
fn(that);
return result;
}
Array.prototype.myFlat = myFlat;
arr3 = arr3.myFlat();
//[1, 2, 3, 5, 6, 7, 11, 12, 13, 14, 10]
斐波那契数列
- 从第 3 项开始,每一项都等于前两项之和
输出指定长度数列
- 先排除长度为 1 和 2 时的数列
- 每次循环时数组添加得项为前两项之和
function fbnq(len) {
var nums = len;
var fbArr = [1, 1];
if (nums < 1) return [];
if (nums == 1) return [1];
if (nums == 2) return [1, 1];
for (let i = 1; i <= nums - 2; i++) {
fbArr.push(fbArr[i - 1] + fbArr[i]);
}
console.log(fbArr);
}
fbnq(6); //[ 1, 1, 2, 3, 5, 8 ]
输出斐波那契数列中指定项
- 直接利用递归
function fb(n) {
// 判定指定项若大于2则回调计算值,小于0则直接输出0
return n - 2 > 0 ? fb(n - 2) + fb(n - 1) : n > 0 ? 1 : 0;
}
/**
* flag为想查询数列中第几项
* curr为一项 next为二项 二者相加为第三项
*/
function fbnq(flag) {
function fn(flag, curr = 1, next = 1) {
return flag == 1 ? curr : fn(flag - 1, next, curr + next);
}
return fn(flag);
}
随机打乱数组
- v8 在处理
sort
方法时,使用了插入排序和快排两种方案。 - 当目标数组长度小于 10 时,使用插入排序;反之,使用快排。
- 使用
Fisher–Yates shuffle
function shuffle(array) {
var arrLen = array.length, index;
while (arrLen) {
//获取数组范围内的随机下标
index = Math.floor(Math.random() * arrLen--);
//解构交换双方的值
[array[index], array[arrLen]] = [array[arrLen], array[index]]
}
return array;
}
let oldArr = [1, 2, 3, 4, 5, 6, 7, 8]
var newArr = shuffle(oldArr);
字符串中出现频率最高的字符
- 先利用
split
将字符串转化为数组 - 定义一个 json 对象用来装载字符及映射的值
- 循环判定当前字符是否存在 json 对象中
- 不存在则设置值为 1,存在则次数+1
let str = "abcdffsdascdsacacsdacawcec";
function getMax(str) {
var arr = str.split("");
var json = [];
var char = "";
var max = 0;
for (let i = 0; i < arr.length; i++) {
json[arr[i]] ? (json[arr[i]] += 1) : (json[arr[i]] = 1);
}
for (const key in json) {
json[key] > max ? ((max = json[key]), (char = key)) : null;
}
console.log(`频率最高的字符是${char},它出现了${max}次`);
//频率最高的字符是c,它出现了7次
}
getMax(str);
找出 1~100 的指定规则数字,比如七的倍数和带有七的数字。
- 分为两种情况
- 当前值为七的倍数
- 当前值首或尾包含七
- 利用
Set
特性 去重数组中重复添加的项 - 或者添加值后,
continue
跳出循环都可以
function pickNum() {
var pickArray = [];
for (let index = 0; index < 100; index++) {
var element = index;
if (element % 7 == 0 && element !== 0) pickArray.push(element);
elementArray = String(element).split("");
elementArray.forEach((ele) => {
if (ele == 7) pickArray.push(element);
});
}
return pickArray;
}
var getNum = pickNum();
getNum = Array.from(new Set(getNum));
console.log(getNum);
/** [7,14,17,21,56,57,63...67,70,71,72,73,74,,98]*/
快速创建数字为 1-100 的数组
MDN:
Array.from()
方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
- Array.from 还可以接受第二个参数,
- 第一个是数组或类数组(伪数组对象)
- 第二个作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
var createArr = [];
createArr.length = 100;
let newArray = Array.from(createArr, (value, index) => index + 1);
console.log(newArray); //[1,2,3.....98,99,100]
非空整数数组,某个元素只出现一次,找出那个只出现了一次的元素。
/**
* 只出现一次的元素,则无论正反索引值都是一样的
*/
var singleNumber = function (nums) {
for (let i = 0; i < nums.length; i++) {
if (nums.indexOf(nums[i]) === nums.lastIndexOf(nums[i])) return nums[i];
}
};
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
- 简洁的代码,如果这个元素占了整个数组的一半多,那么排序之后中间的数,一定就是这个数
/**
* 新建一个对象存储空间,将数组中每项添加进去
* 不存在则赋值为1,存在则使其值+1
* 每次添加时根据值的比较确定是否更新频率最高的那个值
*/
var majorityElement = function (nums) {
var obj = [];
var pick = nums[0];
var flag = 1;
for (let index = 0; index < nums.length; index++) {
const ele = nums[index];
obj[ele] ? (obj[ele] += 1) : (obj[ele] = 1);
if (obj[ele] > flag) {
flag = obj[ele];
pick = nums[index];
}
}
return pick;
};
/**
* 题意:占一半,则不论怎么排序,中间位置的值一定是符合要求的
*/
var majorityElement = function (nums) {
nums=nums.sort();
return nums[nums.length/2];
};
随机生成字符串
function randomString(n) {
let str = "0123456789abcdefghijklmnopqrstuvwxyz";
let tmp = "",
i = 0,
l = str.length;
for (i = 0; i < n; i++) {
tmp += str.charAt(Math.floor(Math.random() * l));
}
return tmp;
}
打家劫舍
- 如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
- 给定一个代表每个房屋存放金额的非负整数数组,在不触动警报装置的情况下,能够偷窃到的最高金额
- 单纯取奇数号或者偶数号并不是最优解
- 比如
[6,1,2,6]
的最优解应该是6+6
,而不是6+2
或者1+6
var rob = function (nums) {
//初始化创建两个元素,取最大值
let dp = [
// 偷了第0户
nums[0] || 0,
// 第0和1户只能选一户
Math.max(nums[0] || 0, nums[1] || 0),
];
// 从第2户开始递推
for (let i = 2; i < nums.length; i++) {
dp[i] = Math.max(
// 如果不偷第i户,只需考虑i-1户的情况
dp[i - 1],
// 如果偷第i户,需要考虑i-2到0户的情况,而i-2必然大于之前所有结果,此处只需要考虑i-2即可
nums[i] + dp[i - 2]
);
}
// 当前数组最后值,就是获取最大金额
return dp[dp.length - 1];
};
- 未完,待补充...