常见算法整理

145 阅读6分钟

算法示例

  • 搜罗常见的算法及LeetCode题,记录手动实现的过程及思路

二维矩阵中指定数值搜索 示例如下表所示

1471115
2581219
3691622
1013141724
1821232630
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列

期望输入矩阵数据实现 :输入: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.png

  • 不考虑 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)

快速排序

  • 快排思想
  • 拿到传入数组的中间项为基准
  • 数组中比基准小的放左边数组,大的放右边数组
  • 递归调用自身,然后做数组拼接
  • 图示 Quick Sort.gif
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];
};
  • 未完,待补充...