JS算法合集

127 阅读11分钟

一、二分法

  • 方法介绍: 该方法为二分法查找,实现通过二分法快速找到元目标素在数组中的位置。
  • 思路: 将数组从中间分为两部分,用中间元素和目标元素比较,如果比目标元素小,则再把数组后半部分分为两部分....,从而避免挨个遍历比较。
  • 输入: paraArr 有序数组,paraFind 要找的目标元素。
  • 输出: 要找的元素在数组中的位置,如果没找到返回null。
  • 时间复杂度: O(logn)。
const Bisection = (paraArr, paraFind) => {
  if (!paraArr.length || paraArr.length <= 1) {
    return null
  }
  let low = 0
  let high = paraArr.length - 1
  while (low <= high) {
    let mid = parseInt((low + high) / 2)
    if (paraArr[mid] == paraFind) {
      return mid
    } else if (paraArr[mid] > paraFind) {
      high = mid - 1
    } else {
      low = mid + 1
    }
  }
  return null
}
let arr = [1, 2, 3, 4, 5]
let find = 8
let index = Bisection(arr, find)
console.log(index)

二、选择排序

  • 方法介绍: 该方法为选择排序,实现数组由小到大排序。
  • 思路: 遍历数组,找到最小的元素,放到新的数组,再遍历去除最小元素后的数组,找到最小的元素...
  • 输入: paraArr 数组。
  • 输出: 由小到大排序的数组。
  • 时间复杂度: O(n^2)。
//深拷贝
import { deepCopy } from './utils'
//获取数组中最小元素
const getMin = (arr) => {
  let min = 0
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < arr[min]) {
      min = i
    }
  }
  return min
}
const SelectionSort = (paraArr) => {
  if (!paraArr.length || paraArr.length <= 1) {
    return null
  }
  let resArr = []
  let arr = deepCopy(paraArr)
  for (let i = 0; i < paraArr.length; i++) {
    let index = getMin(arr)
    resArr.push(arr[index])
    arr.splice(index, 1)
  }
  return resArr
}
let arr = [7, 10, 3, 6, 5]
let resArr = SelectionSort(arr)
console.log(resArr)

三、求两个正整数的最大公约数

欧几里得定理(辗转相除法): 两个正整数a和b(a>b),它们的最大公约数等于a除以b的余数c与b之间的最大公约数。
公式: gcb(a,b)=gcb(b,a%b)

**证明过程:**
d=gcb(a,b);  //d为a,b的最大公约数
a=bk+r;       
r=a-bk;   
r/d=a/d-b*k/d;   //两边同时除以d,a/d是整数,b/d是整数
d | r  //因此r/d是整数,因此d是整除于r的
所以d是a,b,r的公约数,d是a,b的最大公约数,且r<b,因此d是b和r的最大公约数d=gcb(b,r)=>d=gcb(b,a%b)
gcb(a,b)=gcb(b,a%b)
  • 方法介绍: 该方法为通过递归、欧几里得算法获取最大公约数。
  • 思路: 欧几里得算法。
  • 输入: paraA paraB 为正整数,且paraA>paraB。
  • 输出: paraA paraB的最大公约数。
const MaxCommonDivisor = (paraA, paraB) => {
  return paraB == 0 ? paraA : MaxCommonDivisor(paraB, paraA % paraB)
}
let res = MaxCommonDivisor(377, 319)
console.log(res)

四、快速排序

  • 方法介绍: 该方法为快速排序,实现数组由小到大排序。
  • 思路: 选取一个基准值base,遍历数组;
    找到比基准值小的元素,放到新的数组,并排序,得到minArr;
    找到比基准值大的元素,放到新的数组,并排序maxArr
    拼起来:得出minArr+base+maxArr
  • 输入: paraArr 数组。
  • 输出: 由小到大排序的数组。
  • 时间复杂度: 最遭情况O(n^2) 平均情况O(n log n)。
//深拷贝
import { deepCopy } from './utils'
const QuickSort = (paraArr) => {
  if (!paraArr.length || paraArr.length < 2) {
    return paraArr
  }
  let base = paraArr[0]
  let minArr = []
  for (let i = 0; i < paraArr.length; i++) {
    let item = paraArr[i]
    if (item < base) {
      minArr.push(item)
    }
  }
  minArr = QuickSort(minArr)
  let maxArr = []
  for (let i = 0; i < paraArr.length; i++) {
    let item = paraArr[i]
    if (item > base) {
      maxArr.push(item)
    }
  }
  maxArr = QuickSort(maxArr)

  let resArr = deepCopy(minArr)
  resArr.push(base)
  resArr = resArr.concat(maxArr)
  return resArr
}
let arr = [7, 10, 3, 6, 5]
let resArr = QuickSort(arr)
console.log(resArr)

五、递归数组求和

  • 方法介绍: 该方法为通过递归求和。
  • 思路: 递归取出第一项与数组剩下的总数求和。
  • 输入: paraArr 为数组,每项为整数。
  • 输出: paraArr各项求和后的数。
const Sum = (paraArr) => {
  if (!paraArr.length) {
    return 0
  } else {
    return paraArr[0] + Sum(paraArr.slice(1))
  }
}
let res = Sum([1, 2, 3])
console.log(res)

六、数字字符串格式化

  • 方法介绍: 将用户输入的不带千分位逗号的数字字符串转换为带千分位逗号的格式,并且保留小数部分,并删除数字字符串前面无用的 0数字字符串格式化 - MarsCode
  • 思路:
    将数字字符串通过小数点分割成两部分,只需要处理前半部分strArr[0],后半部分strArr[1]直接拼接即可;
    首先去除无意义的0,遍历前半部分strArr[0],如果是0且不在小数点前一位(小数点前一位的0有意义)就去掉,如果不是0跳出循环,最终获取数字字符串newStr;此时我们已经完成了其中一个需求,接下来实现千分位逗号分隔;
    newStr大于3位才需要分割,newStr大于3位且不能被3整除时,将strArr[0]%3得出的otherStr拼接在前面即可,有点绕口,比如1294512.12412最终结果应该是1,294,512.12412,1直接拼在前面就行;
    最后实现千分位逗号分隔,获取splitStr,即newStr剩下的,数量能被3整除的,也就是294512,将他们分割成3个3个的块[294,512],最后用,合并; 最后记得将各部分拼接起来return
  • 输入: 原始数字字符串s
  • 输出: 转换好的字符串。
function solution(s) {
  const strArr = s.split('.');
  let newStr = strArr[0]; // 用于存储小数点之前的字符串
  // 去除无意义的0
  for (let i = 0; i < strArr[0].length; i++) {
    if (strArr[0][i] == 0 && i !== strArr[0].length - 1) {
      // 判断为0且不是小数点前1位,小数点前一位的0有意义
      newStr = strArr[0].slice(i + 1);
    } else {
      // 遇到不是0的退出循环
      break;
    }
  }
  // 大于3才需要分割
  if (newStr && newStr.length > 3) {
    const num = newStr.length % 3;
    const otherStr = newStr.slice(0, num); // 前面不足3个的,直接拼在前面
    const splitStr = newStr.slice(num, newStr.length); // 剩下的,需要三个三个拆分
    const count = splitStr.length / 3; // 逗号数量,也就是剩下的有几组
    const arr = []; // 三个三个切割剩下的字符串生成的数组
    for (let i = 0; i < count; i++) {
      arr.push(splitStr.slice(i * 3, i * 3 + 3));
    }
    const restStr = arr.join(',');
    newStr = otherStr ? otherStr + ',' + restStr : restStr;
  }
  if (strArr[1]) {
    // 存在小数的拼在后面
    return newStr + '.' + strArr[1];
  } else {
    return newStr;
  }
}
function main() {
  console.log(solution("1294512.12412") === '1,294,512.12412');
  console.log(solution("0000123456789.99") === '123,456,789.99');
  console.log(solution("987654321") === '987,654,321');
}
main();

七、寻找最大葫芦

  • 方法介绍: 在德州扑克游戏中,有一种牌型叫做“葫芦”。“葫芦”由五张牌组成,其中包括三张相同牌面值的牌a和另外两张相同牌面值的牌b。如果两个人同时拥有“葫芦”,我们会优先比较牌a的大小,若牌a相同则再比较牌b 的大小,牌面值的大小规则为:1 (A) > K > Q > J > 10 > 9 > ... > 2,其中 1 (A) 的牌面值为1,K 为13,依此类推。 在这个问题中,我们对“葫芦”增加了一个限制:组成“葫芦”的五张牌牌面值之和不能超过给定的最大值max。 给定一组牌,你需要找到符合规则的最大的“葫芦”组合,并输出其中三张相同的牌面和两张相同的牌面。如果找不到符合条件的“葫芦”,则输出 “0, 0”。寻找最大葫芦 - MarsCode
    总结来说,要求如下:
    (1)要找到5张牌,前三张一样,后两张一样;
    (2)这五张牌的总和要小于等于max
    (3)如果存在多个这样的组合,则比较。比较规则:先比前三位那个值,取最大,如果有多个最大的,就比后两位那个值,1特殊最大;
    (4)如果没有符合条件的返回[0,0]。
  • 思路:
    首先需要找到所有牌里面出现次数大于2的牌arrLt2和大于3的的牌arrLt3,大于2就可以放在后两位,大于3就可以放在前三位;
    然后把可能的组合arr列出来,并且需要满足如下条件(1)前面的牌不能和后面相等;(2)总数不能大于max;(3)不能重复,这里如果arr为空就可以直接返回[0,0],最后得出的arr是一个二维数组,每一项存储两个值,第一个值为前3位的值,第二个值位后2位的值,比如[[1,2],[3,4]];
    接下来将arr按照第一项从小到大排序,排序后需要对1进行单独处理。如果arr中某一项的第一个值为1的情况,取arr每项中第一个值为1的生成数组arr1,然后比较arr1中每项的第二个值,最后输出;如果arr中某一项的第一个为1的情况,取arr中第一项为1的生成数组arr1,然后比较arr1中的第二项,最后输出;如果arr中没有项的第一个值为1的情况,取arr中第一个值最大的那一项(也就是排序后的arr的最后一项)生成数组arr4,然后比较arr4中的第二项,最后输出。有点绕..看代码吧..
  • 输入: 数组个数n,最大值max,数组array
  • 输出: 满足要求的一个数组,第一个值表示前3位的值,第二个值表示前2位的值。
function solution(n, max, array) {
  // 遍历数组,使用map记录每项出现的次数量,键为数组的项,值为该项出现的次数。
  const map = {}
  // 找到数量大于2的数字以及数量大于3的数字,因为大于2就可以选两个放葫芦后两个,大于3就可以选三个放前面
  const arrLt2 = []
  const arrLt3 = []
  for (let i = 0; i < array.length; i++) {
    if (map[array[i]]) {
      // 该项存在时,数量+1
      map[array[i]]++
      if (map[array[i]] >= 2) {
        arrLt2.push(array[i])
      }
      if (map[array[i]] >= 3) {
        arrLt3.push(array[i])
      }
    } else {
      map[array[i]] = 1 //不存在时,初始化为1
    }
  }
  // 把可能的组合列出来
  let arr = [] // arr为二维数组,存储所有可能的葫芦组合。arr里面的每一项为一个组合,每一项有两个值,第1个位为葫芦前3位的值,第2个为葫芦后2位的值。
  for (let j = 0; j < arrLt3.length; j++) {
    for (let x = 0; x < arrLt2.length; x++) {
      // 前面3个不能和后面两个相等
      if (arrLt2[x] !== arrLt3[j]) {
        // 葫芦总数不能大于max
        const tempSum = arrLt3[j] * 3 + arrLt2[x] * 2
        if (tempSum <= max) {
          let tempArr = JSON.stringify([arrLt3[j], arrLt2[x]]) // 去重
          if (arr.indexOf(tempArr) == -1) {
            arr.push(tempArr)
          }
        }
      }
    }
  }
  // 没有合适的葫芦直接返回[0,0]
  if (!arr.length) {
    return [0, 0]
  }
  for (let i = 0; i < arr.length; i++) {
    arr[i] = JSON.parse(arr[i])
  }
  arr.sort((a, b) => {
    return a[0] - b[0]
  })
  const arr1 = [] // 存储arr中第一项为1的葫芦
  let maxIndex = arr.length - 1
  var arr4 = [arr[maxIndex]] // 存储arr中第一项为最大值的葫芦
  for (let i = 0; i < arr.length; i++) {
    if (arr[i][0] == 1) {
      arr1.push(arr[i])
    } else {
      let maxIndex = arr.length - 1
      if (arr[i][0] == arr[maxIndex][0]) {
        arr4.push(arr[i])
      }
    }
  }
  if (arr1.length) {
    arr1.sort((a, b) => {
      return a[1] - b[1]
    })
    if (arr1[0][1] == 1) {
      return [1, 1]
    } else {
      return [1, Number(arr1[arr1.length - 1][1])]
    }
  } else {
    // 第一项没有1,找所有第一项最大的
    arr4.sort((a, b) => {
      return a[1] - b[1]
    })
    if (arr4[0][1] == 1) {
      return [Number(arr4[0][0]), 1]
    } else {
      return [Number(arr4[0][0]), Number(arr4[arr4.length - 1][1])]
    }
  }
}
function main() {
  console.log(
    JSON.stringify(solution(9, 34, [6, 6, 6, 8, 8, 8, 5, 5, 1])) === JSON.stringify([8, 5]),
  )
  console.log(
    JSON.stringify(solution(9, 37, [9, 9, 9, 9, 6, 6, 6, 6, 13])) === JSON.stringify([6, 9]),
  )
  console.log(
    JSON.stringify(solution(9, 40, [1, 11, 13, 12, 7, 8, 11, 5, 6])) === JSON.stringify([0, 0]),
  )
}
main()
// 嗯...代码也挺绕的...肯定有更简洁的方法,我再去学习一下...

八、创意标题匹配问题

image.png

  • 思路:
    我一顿分析、一顿判断那,我寻思这得先把{}及里面得内容替换为逗号,然后根据逗号分割出一个数组,然后去那个字符串里找这个数组里的每一项,还得看看位置是不是匹配...然后写下了这一堆**代码,而且我觉得判断位置匹配那里有问题,我越想越麻烦,然后我就搜了一下,妈呀!直接用正则啊😢,白忙活了。这是我之前忙活的代码,大家看一乐子,没有任何参考价值..居然还提交通过了..

image.png

  • 输入: 数组个数n,创意标题template_,要判断的值titles
  • 输出: 要判断的值是否从该创意标题创意生成的,是为True,否为False
function solution(n, template_, titles) {
  // Please write your code here
  let str = template_.replace(/\{.*?\}/g, '.*') // 把{}及{}里面的内容替换位.*
  var getBool = (title) => {
    return new RegExp(`^${str}$`).test(title) ? 'True' : 'False' //用生成的正则匹配传过来的字符串
  }
  let boolArr = []
  for (let i = 0; i < titles.length; i++) {
    let tempBool = getBool(titles[i])
    boolArr.push(tempBool)
  }
  boolArr = boolArr.join(',')
  return boolArr
}
function main() {
  const testTitles2 = [
    'CLSomGhcQNvFuzENTAMLCqxBdj',
    'CLSomNvFuXTASzENTAMLCqxBdj',
    'CLSomFuXTASzExBdj',
    'CLSoQNvFuMLCqxBdj',
    'SovFuXTASzENTAMLCq',
    'mGhcQNvFuXTASzENTAMLCqx',
  ]
  const testTitles3 = ['abcdefg', 'abefg', 'efg']
  const testTitles1 = ['adcdcefdfeffe', 'adcdcefdfeff', 'dcdcefdfeffe', 'adcdcfe']
  console.log(solution(4, 'ad{xyz}cdc{y}f{x}e', testTitles1)) //'True,False,False,True'
  console.log(solution(6, '{xxx}h{cQ}N{vF}u{XTA}S{NTA}MLCq{yyy}', testTitles2)) // 'False,False,False,False,False,True'
  console.log(solution(3, 'a{bdc}efg', testTitles3)) //'True,True,False'
}
main()
// 嗯...我下次一定好好学习正则😵

感谢阅读,将持续完善!
欢迎补充、提问题、提建议~