フロントエンド - アルゴリズム

序文

データ構造と同様に、アルゴリズムのテスト問題もインタビュアーに好まれます.多くの場合、質問の特性に応じて適切なプログラミング方法をインタビュアーと話し合うことができます.通常、アルゴリズムテストの焦点はソートと検索です. 2つのポイント 検索、マージ、クイックソートはいつでも正確で、それらのコードを完全に記述できる必要があります。
コードの標準化に影響を与える要因: 記述、レイアウト、命名
コードの整合性: 境界の判断を追加する必要があります!

配列記事:

配列の基本:

let arr1 = [1, 2, 10, 30]
let arr2 = [3, 4, 50, 90]
let arr3 = ['banana', 'apple', 'peach']
// arr1.concat(arr2) // [ 1, 2, 10, 30, 3, 4, 50, 90 ] 合并数组
// const a = arr1.entries() // Object [Array Iterator] {} 返回一个数组迭代对象,可用for of遍历
// arr1.fill('bob')  // [ 'bob', 'bob', 'bob', 'bob' ] 使用一个固定值来填充数组,改变原数组
// const flag = arr1.every(item => { return item >= 1 }) // true 数组内每一个元素都满足>=1返回true,有一个不满足就返回flase,并不再遍历,不改变原数组
// const flag = arr1.some(item => { return item > 11 }) // true 数组内只要有一个元素满足条件就返回true,不再遍历数组,都不满足返回false,不改变原数组
// const newArr = arr1.filter(item => { return item > 6 }) // [ 10, 30 ] 返回满足判断条件的元素组成的数组,不改变原数组
// const target = arr1.find(item => { return item > 99 }) // 10 返回满足条件的第一个元素,找到目标元素便不再遍历,无就返回undefind,不改变原数组
// const targetIndex = arr1.findIndex(item => {return item >= 10}) // 2 返回满足条件的元素的索引,无就返回-1
// const newArr = arr1.forEach(item =>{ if(item===2) return ;console.log('执行')}) // 执行 执行 执行 将数组内的每个元素都执行一次函数,这里的return相当于continue,跳出循环要使用try catch实现,改变元素组,没有返回值
try{
    
    
        array.forEach(function(item,index){
    
    
                if(item == "third"){
    
    
                        throw new Error("ending");//报错,就跳出循环
                }else{
    
    
                        console.log(item);
                }
        })
}catch(e){
    
    
        if(e.message == "ending"){
    
    
                console.log("结束了") ;
        }
}
// const newArr2 = Array.from('HBGSV') // [ 'H', 'B', 'G', 'S', 'V' ]将一个字符串转化为数组
// const newArr = new Set(arr1)
// const newArr2 = Array.from(newArr, x => x * 10) // [ 10, 20, 100, 300 ]将map结构转化为数组结构,第二个参数是一个函数,数组内的每一个数都会执行它
// const flag = arr1.includes(2) // true 看数组内是否包含2这个元素,返回布尔值
// const flag = arr1.indexOf(10) // 2 返回指定元素在数组内的索引位置,无就返回-1
// const flag = Array.isArray(arr1) // true 判断一个对象是否是一个数组
// const string1 = arr1.join() // 1,2,10,30 将一个数组转化为一个字符串,参数不写默认以逗号分隔
// arr1.keys() // Object [Array Iterator] {}返回一个实现Iterator接口的对象,需要调用.next() // { value: 0, done: false } .value: // 0
// const index = arr1.lastIndexOf(10) // 2 返回目标元素在数组里出现的最后一个位置
// const newArr = arr1.map(item => { return item * 2 }) // [ 2, 4, 20, 60 ] 返回处理过的数组,不改变原数组
// const popRes = arr1.pop() // 30 删除数组最后一个元素,并返回被删除的元素
// const newArrLength = arr1.push(1,2) // 6 向数组的末尾添加元素,并返回新数组的长度
// const arrTotal = arr1.reduce((total, current) => { return total + current }, 0) // 43 返回数组内所有元素计算的结果(从左到右) 第一个参数是一个函数,函数有两个参数,分别是:上一次相加的结果和当前正要加的值,另一个参数是相加的初始值
// const arrTotal = arr1.reduceRight((total, current) => { return total - current }, 0) // -43 功能和上一个一样,不同之处是它(从右到左)
// const newArr = arr1.reverse() // [ 30, 10, 2, 1 ] 颠倒数组中的顺序,返回颠倒顺序的数组,并改变原数组!!
// const newArr = arr3.sort() // [ 'apple', 'banana', 'peach' ] 对数组内的元素(字母)进行升序排序,但是数字排序会出错(下一个),返回排好顺序的数组,并改变原数组!!
// const newArr = arr1.sort((a, b) => { return a - b }) // [ 1, 2, 10, 30 ] 对数组内的数字执行排序时要传入一个函数,a-b为升序,b-a为降序,返回排好顺序的数组,并改变原数组!!
// const shiftRes = arr1.shift() // 1 删除并返回数组的第一个元素 改变原数组
// const newArrLength = arr1.unshift(99) // 5 向数组的开头添加元素,返回数组的新长度 改变原数组
// const newArr = arr1.slice(0, 2) // [ 1, 2 ] 两个参数分别是切的开始位置和结束位置,返回由选定的元素组成的新数组,不改变原数组
// array.splice(index,howmany,item1,.....,itemX) // index:删除或添加函数的位置;howmany:从index位置删除howmany个元素(必须),不写就直接删除index之后的所有元素(非必须);item1,.....:为添加的元素,不写就不添加(非必须),函数返回被删除的元素
// const newArr = arr1.splice(2,0,99) // [] 返回值为删除的元素所组成的数组,这里我们从数组为2的位置插入(删除)元素,删除0个元素,添加99这个元素,改变原素组!!
// const newString = arr1.toString() // 1,2,10,30 将数组转化为字符串,以逗号分隔数组元素,不改变原数组
// const newString = arr1.valueOf() // 返回元素本身[ 1, 2, 10, 30 ],运算是先调用valueOf再调用toString,日期对象的话会返回1970 年 1 月 1 日到当前对象的毫秒数

1.配列内で繰り返される数字を見つける

問題の説明: [3,4,5,3,7,9,4]、 return 3 または 4 が正しいです。
アイデア: オブジェクトの属性名は一意です

function getRes() {
    
    
  const arr = [3, 4, 5, 3, 7, 9, 4]
  const len =  arr.length
  const obj = {
    
    }
  let resArr = []
  if (Array.isArray(arr) && len > 0) {
    
    
    for (let i = 0; i < len; i++) {
    
     
      if (obj[arr[i]] === undefined) {
    
    
        obj[arr[i]] = 1
      }
      else {
    
    
        obj[arr[i]]++
      }
    }

    for (let key in obj) {
    
    
      if (obj[key] >= 2) {
    
    
        resArr.push(key)
      }
    }

    return resArr.length === 0 ? -1 : resArr[0]
  }
  else {
    
    
    return -1
  }
}

console.log(getRes()) // 3

2.製品配列を構築する

問題の説明
: 配列 A[0,1,…,n-1] が与えられた場合、配列 B[0,1,…,n-1] を作成してください。ここで、B B[i] =A[0] Aの要素[1] ...*A[i-1] A[i+1] ... A[n-1] (率直に言うと、i を取り除いた後、配列 [i])。
分割は使用できません。
(注: B[0] = A[1] * A[2] * ... * A[n-1]、 B[n-1] = A[0] * A[1] * と規定されています。... * A[n -2];)
A の長さが 1 の場合、B は無意味で構成できないため、このケースは存在しません。

アイデア: まず最初の i 個の数の積 (接頭辞の乗算) を見つけ、次に最後の i 個の数の積 (接尾辞の積) を見つけ、最後に接頭辞 * 接尾辞を追加して B[i] を取得します。


function getRes() {
    
    
  let arr = [1, 2, 3, 4, 5] // 输入数组
  
  let preArr = []  // 前缀乘积
  preArr[0] = 1
  for (let i = 1; i < arr.length; i++) {
    
    
    preArr[i] = preArr[i - 1] * arr[i - 1]
  }

  let sufArr = [] // 后缀乘积
  sufArr[arr.length - 1] = 1
  for (let j = arr.length - 2; j >= 0; j--) {
    
    
    sufArr[j] = sufArr[j + 1] * arr[j + 1]
  }

  let resArr = [] // 结果数组
  for (let i = 0; i < arr.length; i++) {
    
    
    resArr[i] = preArr[i] * sufArr[i]
  }

  return resArr
}

console.log(getRes())

3. 2 次元配列で検索:

問題の説明: 2 次元配列 (各 1 次元配列は同じ長さ) で、各行は左から右に昇順で並べ替えられ、各列は上から下に昇順に並べ替えられます。関数を完成させ、このような二次元配列と整数を入力し、整数が配列に含まれているかどうかを判断してください。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
ターゲット = 7 の場合、true を返します。

target = 3 の場合、false を返します。
考え方: 右上隅から探し始める (左下隅も可能です。行または列を 1 ステップも実行せずに削除できるためです。ただし、1 ステップごとに行または列を削除するのは効率的ではありません)。右隅と左上隅)、最初に左に移動し、ターゲットよりも大きい数に遭遇した場合は引き続き左に移動し、ターゲットよりも小さい数に遭遇した場合に下に移動すると、ターゲットを見つけることができます. 最後に、ES6 には [[1, 2, 8, 9], [2, 4, 9, 12] のように、多次元配列を 1 次元配列 arr.flat (配列の次元) に変換するメソッドがあります。 , [1,1, 1]]]. flat( 2 ) // [1, 2, 8, 9, 2,4, 9, 12, 1, 1,1] 、二次元についてわからない場合、インタビュアーがこのように解くことができれば、一次元に変換するのは非常に簡単です。. .

function Find(target, array) {
    
    
  if (Array.isArray(array) && array.length > 0 && Array.isArray(array[0]) && array[0].length > 0) {
    
    
    let x = array.length // 获取二维数组的行数
    let y = array[0].length // 获取二维数组的列数
    // 从二维数组的右上角开始查找target
    let i = array[0].length - 1 // 列位置
    let j = 0 // 行位置
    while (i >= 0 && j <= x - 1) {
    
    
      if (target < array[i][j]) {
    
     // 如果目标值前一个数小,往左走
        i--
      }
      else if (target > array[i][j]) {
    
     // 如果目标值比下一个数大,往下走
        j++
      }
      else {
    
     // 不大也不小了,说明相等了,找到目标值,返回true
        return true
      }
    }
    return false // 循环完没找到,说明没有,return false
  }
}
const res = Find(15, [
  [1, 2, 8, 9],
  [2, 4, 9, 12],
  [4, 5, 10, 13],
  [6, 8, 11, 15]
])
console.log(res) // true

4. 2 つの数値の合計

問題の説明: 整数の配列が与えられた場合、目的の値に加算される配列内の 2 つの数値を見つけてください。指定した関数
twoSum は、これら 2 つの数値の添字 (index1、index2) を返す必要があり、それを満たす必要があります。 index1 は index2 より小さい.. 注: 添字は 1 から始まります.
与えられた配列 に一意の解しかないと仮定します
. 例:
与えられた配列は {20, 70, 110, 150} で、ターゲット値は 90 です.
出力 index1=1,インデックス2=2

アイデア: 最初は、力ずくの解決策が必ず使用されます. 2 つの for ループはすぐに解決されますが、最適な解決策を見て初めて、この問題はハッシュ テーブルを使用して解決できることがわかります.値とインデックス値をハッシュ テーブルに格納し、ターゲットは配列の数値を順番に減算します.結果がたまたまハッシュ テーブルに格納されていた場合 (つまり、6-2 = 4; 4 はまだ 2 を加算していません)ハッシュテーブルに保存しますが、次のポーリング 6-4 = 2, 2 は前のラウンドに入れられたばかりであり、結果の配列が取得されます)、それが見つかり、ハッシュテーブルのインデックス現在トラバースされているインデックスが返されます。それだけです。

function twoSum(numbers, target) {
    
    
let len =  numbers.length 
if (Array.isArray(numbers) && len > 0){
    
    
let resArr = new Map()
  let res = undefined
  for (let i = 0; i < len; i++) {
    
    
    res = target - numbers[i]
    if (resArr.has(res)) {
    
    
      return [resArr.get(res) + 1, i + 1]
    }
    resArr.set(numbers[i],i)
  }
  }
}
twoSum([3, 2, 4], 6) // [ 2, 3 ]

5. 配列の最後に 0 を入れます

問題の説明: 配列の最後に 0 を入れてください
例: 入力: [ 9, 10, 0, 1, 2, 2, 3, 0, 12] 出力: [ 9, 10, 1, 2, 2 , 3 , 12, 0, 0 ]
アイデア: ポインター A とポインター B などの 2 つのポインターを使用し、ポインター A は for ループをたどって、通常は配列内の各項目を指し、ポインター A が指していないときにポインター B が続きます。 0 下を指し、B が配列内の 0 を指すと移動を停止します. ポインター A が 0 以外の次の値を指している場合、ポインター B が指す値と交換されます. ポインター A には、 0、そして徐々に 0 でない値 値は配列の先頭に配置され、0 は配列の最後に配置されます

function twoSum(nums) {
    
    
  let tem = 0
  for (let i = 0; i < nums.length; i++) {
    
    
    if (nums[i] !== 0) {
    
     // 当tem指到0了tem就不移动,让i++
      if (nums[tem] == 0) {
    
    
        nums[tem] = nums[i]
        nums[i] = 0
      }
      tem++ // 当指到数组中不是0的数tem才继续移动
    }
  }
  return nums
}

twoSum([9, 10, 1, 0, 2, 2, 0, 3, 12])

6. フィボナッチ数列

問題の説明: 誰もがフィボナッチ数列を知っています (最初の 3 つの項目を除き、後続の項目は最初の 2 つの項目の合計に等しく、現在は整数 n が必要です。フィボナッチ数列の n 番目の項目を出力してください。アイデア:再帰的な実装を使用できます。利点: コードの可読性が高く、欠点: 時間の複雑さと空間の複雑さが高く、反復計算が多いため、ループと 3 つの数値を使用して達成します

function getRes(n) {
    
    
  if (n < 2) {
    
    
    return n
  }
  let first = 1;
  let second = 1;
  let res = 0;
  for (let i = 2; i < n; i++) {
    
    
    res = first + second
    first = second
    second = res
  }
  return res
}

6.1 カエルのジャンプ階段問題

問題の説明: カエルは一度に 1 歩または 2 歩までジャンプできます。カエルが n レベルのステップをジャンプするためのジャンプ方法の総数を見つけます。
答えはモジュロ 1e9+7 (1000000007) である必要があります. 最初の計算結果が 1000000008 の場合, 1 を返してください.
分析:
n=1 の場合、結果は 1
、n=2 の場合、結果は 2、
n=3 の場合、結果は 3
、n=4 の場合、結果は 5、
n=5 の場合、結果は8
...
n>= のときの法則が得られる 3 では f(3) = f(2) + f(1) つまり f(n) = f(n-1) + f( n-2); つまり、フィボナッチ数列

function qwJump(n) {
    
    
    if (n <= 1) {
    
    
        return 1
    }
    if (n === 2) {
    
    
        return 2
    }
    let res = 0
    let t1 = 1
    let t2 = 2
    for (let i = 3; i <= n; i++) {
    
    
        res = (t1 + t2) % 1000000007
        t1 = t2
        t2 = res
    }
    return res
}

Rectangle Covering Algorithm:
2 1 個の小さな四角形を使用して 2 n 個の大きな四角形を覆います。オーバーラップは必要ありません
f(8) = f(7)+f(6) は依然としてフィボナッチ数列です。

6.2 カエルの異常ジャンプの問題 (動的計画法)

問題の説明: カエルは 1 歩、2 歩、3 歩、または n 歩までジャンプできます。カエルが n レベルのステップをジャンプするためのジャンプ方法の総数を見つけます。
アイデア:

  • n=1 ジャンプする方法は 1 つあります
  • n=2 飛び方は2通り
  • n=3 飛び方は4通り
  • n=4 飛び方は8通り
  • n=5、飛び方は16通り
  • ...
    法則から、n = 2*(n-1) であるため、動的計画法を使用できます。
function jump2(n) {
    
    
    const dp = Array.from(new Array(n + 1), () => 0);
    dp[2] = 2;
    for (let i = 3; i < n + 1; i++) {
    
    
        dp[i] = 2 * dp[i - 1];
    }
    return dp[n];
}
console.log(jump2(4))  // 8

7.配列を最小数に並べます

問題の説明: 正の整数の配列を入力し、配列内のすべての数値を連結して数値を形成し、連結できるすべての数値の中で最小のものを出力します。たとえば、配列 [3, 32, 321] を入力し、これら 3 つの数値が形成できる最小の数値が 321323 であることを出力します。
アイデア: 2 つの for ループを使用します。1 つは配列の前の番号を指し、もう 1 つは配列の次の番号を指します。文字列は前の番号と次の番号で構成され、2 つの文字列のサイズは次のとおりです。後者の方が大きい場合は、3 番目の変数を使用して位置を変更し、ループを終了して結果を取得します。

function getRes(numbers) {
    
    
  let len = numbers.length
  let t = 0
  let str = ''
  for (let i = 0; i < len; i++) {
    
    
    for (let j = i + 1; j < len; j++) {
    
    
      let s1 = `${
      
      numbers[i]}${
      
      numbers[j]}`
      let s2 = `${
      
      numbers[j]}${
      
      numbers[i]}`
      if (parseInt(s1) > parseInt(s2)) {
    
    
        t = numbers[i]
        numbers[i] = numbers[j]
        numbers[j] = t
      }
    }
  }
  for (let i = 0; i < len; i++) {
    
    
    str += `${
      
      numbers[i]}`
  }
  return str
}

9. 3 つの数字の合計

問題の説明: n 個の整数を含む配列 nums が与えられた場合、nums に a、b、c の 3 つの要素があり、a + b + c = 0 になるかどうかを判断します。条件を満たし、繰り返されていないすべてのトリプルを見つけます。

注: 重複するトリプレットは、回答では許可されていません。
たとえば、配列 nums = [-1, 0, 1, 2, -1, -4] の場合、

要件を満たすトリプルのセットは次のとおり
です: [
[-1, 0, 1],
[-1, -1, 2]
] current sum Move the left pointer for <target, and move the right pointer for sum> taget. 3 つの数値の合計 (ソートとポインターを使用) の考え方は、2 つの数値の合計 (ハッシュを使用) と同じです。 a+b = c、c -b = a を使用して、配列内で a を見つけるだけです。

function threeSum(nums) {
    
    
  let num2 = nums.sort((a, b) => {
    
     return a - b })
  let resArr = []
  let len = nums.length
  for (let i = 0; i < len-2; i++) {
    
    
    let target = -num2[i]
    let left = i + 1
    let right = len - 1
    // let target = left + right
    if (num2[i] === num2[i - 1]) {
    
      // 前面出现过的数,那结果肯定有了,不用再去遍历了
      continue;
    }
    while (left < right) {
    
    
      let sum = num2[left] + num2[right]

      if (sum < target) {
    
    
        left++
      }
      else if (sum > target) {
    
    
        right--
      }
      else {
    
    
        resArr.push([num2[i], num2[left], num2[right]])
        while (num2[left] === num2[left + 1]) {
    
      // 排除重复的结果
          left++
        }
        left++
        while (num2[right] === num2[right - 1]) {
    
    
          right--
        }
        right--
      }
    }
  }
  return resArr
};

console.log(threeSum([-1, 0, 1, 2, -1, -4]))
输出:[ [ -1, -1, 2 ], [ -1, 0, 1 ] ]

10.回転した配列の最小要素を見つける

一般的な順序で検索します。時間の複雑さは O(n) です。二分探索の時間の複雑さは O(logn) です。

function minNumberInRotateArray(rotateArray){
    
    
    const length = rotateArray.length;
    if (!length) {
    
    
        return 0;
    }
 
    let left = 0, right = length - 1;
    while (left < right) {
    
    
        let mid = Math.floor((left + right) / 2);
 
        // 子数组有序
        if (rotateArray[left] < rotateArray[right]) {
    
    
            return rotateArray[left];
        }
 
        // 左子数组有序,最小值在右边
        // 那么mid肯定不可能是最小值(因为rotateArray[mid]大于rotateArray[left])
        if (rotateArray[left] < rotateArray[mid]) {
    
    
            left = mid + 1;
            // 右子数组有序,最小值在左边
            // 这里right=mid因为最小值可能就是rotateArray[mid]
        } else if (rotateArray[mid] < rotateArray[right]) {
    
    
            right = mid;
        } else {
    
    
            // 无法判断,缩小下范围
            ++left;
        }
    }
 
    return rotateArray[left];
}

console.log('minNumberInRotateArray: ', minNumberInRotateArray([3,4,5,1,2]));  // 1

11. ロープを切る (貪欲)

タイトルの説明: 長さ n のロープが与えられた場合、そのロープを整数の長さ (m と n はどちらも整数、n>1 かつ m>1、m<=n) の m セグメントに切断してください。各ロープの長さを k と表します[1],...,k[m]. k[1]x...xk[m] の可能な最大積は? たとえば、ロープの長さが 8 の場合、それぞれ 2、3、3 の長さの 3 つのセクションに切断し、このとき得られる最大積は 18 です。
アイデア: ロープを 3 の長さに切断すると、切断されたロープの長さを最大に乗算することができますが、ロープの長さが 7 の場合、3 4 を切断すると 3 3*1 よりも大きくなるため、4場合4つ切り出して、残りを3つに切るだけ

function cutRope(number){
    
    
    if(number===2){
    
    
        return 1
    }
    if(number===3){
    
    
        return 2
    }
    let x=number%3; // 取出余数
    let y=parseInt(number/3); // number有多少个3
    if(x===0){
    
     // 没有余数时
        return Math.pow(3,y) // 将取出来的所有3相乘
    }else if(x===1){
    
     // 余数为1时,说明可以拿出一个3构成一个4
        return 2*2*Math.pow(3,y-1)
    }else{
    
     // 当余数为2时,直接将2乘进来即可
        return 2*Math.pow(3,y)
    }
}

12.チャンク機能を実装する

問題の説明: 配列を複数の配列に分割します。各配列のセル数は長さによって決まります。最後の配列の要素は長さよりも少ない場合があります。
例:
const a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; chunk(a, 4); 結果
:
[
[ 'a', 'b', 'c', 'd'],['e', 'f', 'g', 'h']]

function chunk(arr,size){
    
    		
    var arr1=[];		
    for(var i=0;i<arr.length;i=i+size){
    
    		 
        var arr2=arr;          
        arr1.push(arr2.slice(i,i+size));		
    }		
    return arr1;	
}

文字列の記事:

文字列の基本:

let str = "HELLO " 
let str2 = "WORLD" 
// 字符串涉及到位置的,都是从0开始数
// const res = str.charAt(2)  // L ,返回指定位置的字符串,从0开始数
// const res = str.concat(str2)  // HELLO WORLD,返回连接好的字符串,不改变原字符串
// const res = str.indexOf('LO') // 3 返回某个字符串在原字符串中出现的第一个位置,没有就返回-1,从0开始数,区分大小写
// const res = str.includes('HEL') // true,返回布尔值,查看字符串中是否包含子串
// const res = str.replace('LO','lo') // HELlo ,将字符串替换成指定字符串,如果没找到相应字符串就返回原字符串(HELLO),返回已替换好的字符串,不改变原字符串
// 'qe.r'.replace(/\w/g, '@') //replace支持正则,将'qe.r'中的字母数字下划线都替换成@
// const res = str.slice(2,4) // LL ,将字符串按指定位置进行切片,前闭后开,slice(start, end) 
// const res = str.split('') //[ 'H', 'E', 'L', 'L', 'O', ' ' ], 将字符串分隔为字符串数组,空字符串 ("") 用作参数,那么 str中的每个字符之间都会被分割。参数的意思是从参数指定的地方分割
// const res = str.startsWith('HE') // true,查看字符串是否以指定字符串开头
// const res = str.toLowerCase(str) // hello  将字符串转为小写
// const res = str.toUpperCase(str) // HELLO 将字符串转为大写
// const res = str.trim(str) // 去除字符串两边的空格

1. 文字列が回文かどうかを判断する

問題の説明: 文字列が回文かどうかを判断する.
解決策 1. api を使用: 文字列を配列に変換 (分割) し、配列を使用して (逆) 配列の要素を逆順にリハーサルし、配列を逆に並べ替えます。 order 文字列に変換(結合)して元の文字と比較すると、equal は回文、not equal は回文ではありません。

function getRes(inputString) {
    
    
  if (typeof inputString !== 'string') {
    
    
    return false
  }
  else {
    
    
    return inputString.split('').reverse().join('') === inputString
  }
}

解決策 2、api を使用しない: 2 つのポインターを使用し、1 つは先頭から移動し、もう 1 つは末尾から移動し、移動するたびに現在位置が等しいかどうかを判断し、等しい限り false を返します。等しくなく、ループが終了すると false を返します。

function getRes(inputString) {
    
    
  if (typeof inputString !== 'string') {
    
    
    return false
  }
  let i = 0, j = inputString.length - 1
  while (i < j) {
    
    
    if (inputString[i] !== inputString[j]) {
    
    
      return false
    }
    i++
    j--
  }
  return true
}

2. 文字が繰り返されていない最長の部分文字列

説明
: 文字列を指定して、繰り返し文字を含まない最長の部分
文字列の長さを見つけます追加 、文字が配列に既に存在するかどうかを判断します。存在しない場合は直接追加します。この文字列がある場合は、配列の先頭から配列を削除し、現在の文字位置で終了し、現在の文字をに追加しますarray 、Math.max() を使用して最長の配列を保持し、最後に max に戻ります。

 if (typeof inputString !== 'string') {
    
    
    return false
  }
  let arr = [], max = 0
  for (let i = 0; i < inputString.length; i++) {
    
    
    let index = arr.indexOf(inputString[i])
    if (index !== -1) {
    
    
      arr.splice(0, index + 1)
    }
    arr.push(inputString.charAt(i))
    max = Math.max(arr.length, max)
  }
  return max
}

3. 文字ストリーム内の最初の非反復番号

問題の説明: 文字ストリームに一度だけ現れる最初の文字を見つける関数を実装してください。たとえば、最初の 2 文字「go」のみが文字ストリームから読み取られる場合、1 回だけ出現する最初の文字は「g」です。この文字ストリームから最初の 6 文字 "google" が読み取られるとき、1 回だけ出現する最初の文字は "l" です。
バックグラウンドは次の方法でInsert および FirstAppearingOnce
関数を呼び出します: 文字列を配列に変換し、オブジェクト キーの一意性を使用し、文字列の各項目を obj のキーとしてオブジェクトに配置し、値を 0 に割り当てます。初めて 、2 回目以降も表示される場合は、値 1 が割り当てられ、最後にオブジェクトを反復処理し、オブジェクト値 1 のキーを出力します。

function getRes(ch) {
    
    
  let strArr = ch.split('')
  let obj = {
    
    }
  for (let i = 0; i < strArr.length; i++) {
    
    
    if (obj[strArr[i]] === undefined) {
    
    
      obj[strArr[i]] = 0
    }
    else {
    
    
      obj[strArr[i]] = 1
    }
  }
  for (key in obj) {
    
    
    if (obj[key] === 0) {
    
    
      return key
    }
  }
  return '#'
}

4.スペースを置き換える

タイトルの説明: 文字列内の各スペースを「%20」に置き換える関数を実装してください。たとえば、文字列が We Are Happy の場合、置換後の文字列は We%20Are%20Happy になります。
アイデア: 1. 文字列をスペースで区切られた配列に変換してから、その配列を %20 で区切られた文字列に変換します。

function getRes(s) {
    
    
	return s.split(" ").join("%20")
}

2. 新しい文字列を作成し、ターゲット文字列を新しい文字列に 1 つずつ追加し、スペースが検出されたら %20 に置き換えます。

function getRes(s) {
    
    
  let newS = ''
  for (let i = 0; i < s.length; i++) {
    
    
    if (s[i] !== " ") {
    
    
      newS += s[i]
    }
    else {
    
    
      newS += '%20'
    }
  }
  return newS
}

5. バージョン番号比較

function toNum(a, b) {
    
    
    let aArr = a.split("")
    let bBrr = b.split("")
    let len = Math.max(a.length, b.length)
    bBrr.map((item, index, arr) => {
    
    
        if (item == '.') {
    
    
            arr.splice(index, 1)
        }
    })
    aArr.map((item, index, arr) => {
    
    
        if (item == '.') {
    
    
            arr.splice(index, 1)
        }
    })
    for (let i = 0; i < len; i++) {
    
    
        if (/[a-z]/.test(aArr[i]) && /[a-z]/.test(bBrr[i])) {
    
    
            if (aArr[i] == bBrr[i]) {
    
    
                continue
            }
            else if (aArr[i] > bBrr[i]) {
    
    
                console.log(`${
      
      a}是最新的版本`)
                return
            }
            else {
    
    
                console.log(`${
      
      b}是最新的版本`)
                return
            }
        }
        else {
    
    
            if (aArr[i] == bBrr[i]) {
    
    
                continue
            }
            else if (aArr[i] > bBrr[i]) {
    
    
                console.log(`${
      
      a}是最新的版本`)
                return
            }
            else {
    
    
                console.log(`${
      
      b}是最新的版本`)
                return
            }
        }
    }
}

var a = "v2.23a.3"; b = "v2.23b.8";

toNum(a, b);  // v2.23b.8是最新的版本

リンクされたリスト

1. リンクされたリストを最初から最後まで印刷する

問題解説:連結リストを入力し、連結リストの最後から順にArrayListを返す。
入力: {67,0,24,58}、戻り値: [58,24,0,67]
アイデア: while ループを使用して、連結リストの値を配列に 1 つずつ追加し、ポインターを許可します。 Move を 1 つ追加するたびに連結リストの値が下がり、最後に配列が reverse メソッドを呼び出して逆順で出力します。

function printListFromTailToHead(head)
{
    
    
	let node = head;
	let arr = []
	let i = 0
	while(node){
    
    
		arr[i] = node.val
		i++
		node = node.next
	}
	return arr.reverse()
	// 数组的倒序也可以如下:
	let resArr = []
	for(let j = 0; j < i; j++){
    
    
		resArr[j] = arr[i-j-1]
	}
}

2.逆リンクリスト

問題の説明: 連結リストを入力し、連結リストを反転した後、新しい連結リストのヘッダーを出力します。
アイデア:連結リストの反転を実現するには3 つのポインタを使用する必要があります.重要な点が 2 つあります. まず, pre は null を指し, curr と next は連結リストの最初の要素を指します. 第二に,の順序変換は変更できません. 1. curr の次の要素が前の要素を指し、次の要素との接続が切断され、次の要素が見つからなくなるため、最初に next を使用して次の要素を占有します。 2. curr の次の要素を pre (null ) にポイントし、そのポイントが完了したら、pre (pre = curr) を移動し、curr (curr = next) を移動し、最後に next と curr の両方が null を指すようにします。 、および pre はリンクされたリストの先頭を指すだけです

function ReverseList(pHead)
{
    
    
	let pre = null;
	let curr = pHead;
	let next = pHead
	while(curr){
    
    
		next = curr.next
		curr.next = pre
		pre = curr
		curr = next
	}
	return pre
}

3. 指定されたリンク リストにリングがあるかどうかを判断する

アイデア: 連結リストに輪があるかどうかを判断するには、連結リストが永遠に続くかどうかが鍵となります。
次に、高速ポインターと低速ポインターの 2 つのポインターを設定できます。1 つは 2 ステップ用で、もう 1 つは 1 ステップ用です。リンクされたリストにリングがある場合、それらは遅かれ早かれ出会い、複雑さは定数 O(1) になります。リンクされたリストが null、単一ノード、二重ノードの場合、リングはなく、直接 false を返します。

/*
 * function ListNode(x){
 *   this.val = x;
 *   this.next = null;
 * }
 */

/**
 * 
 * @param head ListNode类 
 * @return bool布尔型
 */
function hasCycle( head ) {
    
    
    // write code here
    let fast = head;
    let slow = head;
    while (fast!= null && fast.next != null) {
    
    
        slow = slow.next;
        fast = fast.next.next;
        if(fast === slow) {
    
    
            return true;
        }
    }
    return false;
}

木の記事

再帰

ツリー アルゴリズムは一般に再帰を必要とし, 再帰にはスタック サポートが必要です. 関数が呼び出されるたびに, 関数実行コンテキストを作成してスタックにプッシュする必要があります. 階乗を例に取ります: 再帰が戻り条件を満たしている場合, 実行する必要はありません
ここに画像の説明を挿入
, factorial(0) はスタックをポップし、結果 1 を次の実行コンテキストに渡します。次に factorial(1) を実行し、スタックをポップし、結果 1 を factorial(2) に渡し、 factorial(2) を実行し、スタックして、結果の 1 を factorial(3) に渡します...
ここに画像の説明を挿入
つまり、再帰とは、南の壁に当たらず、引き返さないことを意味します.南の壁に当たった場合は、最初のヒットから順番に引き返します。

0. アンダースコアへのこぶ

問題の説明:
let a = { aB: { aBc: 1 }, aBC: 2 }
let b = [{ aB: 1 }, { a: { aBcD: 1 } }] は次
のように変換されます:
let a = { a_b: { a_bc : 1 }, a_b_c: 2 }
let b = [{ a_b: 1 }, { a: { a_bc_d: 1 } }]

let a = {
    
     aB: {
    
     aBc: 1 }, aBC: 2 }
let b = [{
    
     aB: 1 }, {
    
     a: {
    
     aBcD: 1 } }]

function get_(a) {
    
    
    let newobj = {
    
    }
    for (key in a) {
    
    
        if (a[key] instanceof Object) {
    
    
            newobj[key.replace(/([A-Z])/g, "_$1").toLowerCase()] = get_(a[key]) // 递归这一定要重新赋值!
        }
        else {
    
    
            newobj[key.replace(/([A-Z])/g, "_$1").toLowerCase()] = a[key] // key.replace(/([A-Z])/g, "_$1" 将key中的大写字母前加上_
        }
    }
    return newobj
}

function getRes(a) {
    
    
    let newArr = []
    if (a instanceof Object && !(a instanceof Array)) {
    
     // 注意a instanceof Object包括数组
        return get_(a) // 对象直接返回
    }
    else if(a instanceof Array) {
    
    
        for (item of a) {
    
    
            newArr.push(get_(item))
        }
        return newArr
    }
}
console.log('getRes: ', getRes(b));

1. 2 進数を再構築する

問題の説明: 二分木の事前探索と順序探索の結果を入力し、二分木を再構築してください。入力の事前順序トラバーサルにも順序内トラバーサルの結果にも重複した数値が含まれていないと仮定します。たとえば、事前順序トラバーサル シーケンス {1,2,4,7,3,5,6,8} と順序内トラバーサル シーケンス {4,7,2,1,5,3,8,6} を入力してから、再構築します。二分木とリターン。
アイデア: 事前順序探索の最初の要素は、対象ツリーのルート ノードです. 順序探索におけるルート ノードのインデックスは、ルート ノードから取得され、左右のサブツリーが切り出されます。順トラバーサルのインデックス. 残りのアイデアは同じです, 再帰的

console.log(reConstructBinaryTree([1, 2, 4, 7, 3, 5, 6, 8], [4, 7, 2, 1, 5, 3, 8, 6]))

function TreeNode(x) {
    
    
    this.val = x
    this.left = null
    this.right = null
}

function reConstructBinaryTree(pre, vin) {
    
    
    if (pre.length === 0 || vin.length === 0) {
    
    
        return null
    }

    let index = vin.indexOf(pre[0])

    let vinLeft = vin.slice(0, index) // 中序遍历左子树
    let vinRight = vin.slice(index + 1) // 中序遍历右子树

    let preLeft = pre.slice(1,index+1) // 先序遍历左子树
    let preRight = pre.slice(index+1) // 先序遍历右子树

    let node = new TreeNode(pre[0])
    node.left = reConstructBinaryTree(preLeft, vinLeft)
    node.right = reConstructBinaryTree(preRight, vinRight)
    return node
}

2. 二分木の次のノード

問題の説明: 二分木のノードが与えられた場合、順不同の走査順序で次のノードを見つけて返します。ツリー内のノードには、左右の子ノードが含まれているだけでなく、親ノードを指す次のポインターも含まれていることに注意してください。
ここに画像の説明を挿入

アイデア: 1. 右のサブツリーがある場合、次のノードは右のサブツリーの最も左のポイントです (例: D、B、E、A、C、G) 2. 右のサブツリーがない場合は、 a) 親ノードの左の子 (例: N、I、L)、親ノードは次のノード、b) 親ノードの右の子 (例: H) 、J、K、M) 彼を見つけるために 親の親の親の親...現在のノードがその親の左の子になるまで。eg: M がない場合、彼はテール ノードです。

function TreeLinkNode(x) {
    
    
    this.val = x;
    this.left = null;
    this.right = null;
    this.next = null;
}
function GetNext(pNode) {
    
    
    // // write code here
    if(pNode == null) return pNode;
    if(pNode.right !== null){
    
    
        //如果有右子树
        pNode = pNode.right;
        while(pNode.left != null){
    
    
            pNode = pNode.left;
        }
        return pNode;
    }
    while(pNode.next !== null){
    
    
        if(pNode === pNode.next.left){
    
    
        // 2.1 该节点为父节点的左子节点,则下一个节点为其父节点
            return pNode.next;
        }
       // 2.2 该节点为父节点的右子节点,则沿着父节点向上遍历,知道找到一个节点的父节点的左子节点为该节点,则该节点的父节点下一个节点
        pNode = pNode.next;
    }
    return null;
    }

3. 二分探索木のポストオーダートラバーサル

タイトルの説明: 整数の配列を入力し、その配列が二分探索木の後順走査の結果であるかどうかを判定します。はいの場合は true を出力し、そうでない場合は false を出力します。入力配列内の任意の 2 つの数値が互いに異なると仮定します。例: 入力 [4,8,6,12,16,14,10] 出力: true; 入力 [7,4,6​​,5] 出力: false
アイデア: バイナリの後順トラバーサルの最後の要素検索ツリーはツリーのルート ノードである必要があります。配列内でそれよりも大きい最初の要素を見つけます。この要素の左側はその左側のサブツリーである必要があり、右側はその右側のサブツリーである必要があり、左側の各要素はsubtree はルート ノードよりも大きくなければなりません Small, right subtree の各要素は root ノードよりも大きくなければなりません. これら 2 つの条件のいずれかが満たされない場合, false を返します. 左の subtree についても同じです. の最後の要素左のサブツリーは左のサブツリーのルート ノードである必要があり、左のサブツリーでそれよりも大きい最初の要素を見つける必要があります。要素の左側はその左のサブツリーである必要があり、右側はその右のサブツリーです...右側のサブツリーについても同じことが当てはまるため、再帰を使用して を解決します。

function getRes(arr) {
    
    
    if (arr <= 1) {
    
    
        return true
    }

    let rootItem = arr[arr.length - 1] // 找到中序遍历的根节点
    let index = 0, leftRoot, rightRoot, leftTree, rightTree, res = true

    for (let i = 0; i < arr.length; i++) {
    
    
        if (arr[i] > rootItem) {
    
     // 找到第一个比根节点大的值,用它的切出左右子树
            index = arr[i] 
            break
        }
    }

    leftRoot = arr.indexOf(index) - 1 // 左子树的根节点
    rightRoot = arr[arr.length - 2] // 右子树的根节点
    leftTree = arr.slice(0, leftRoot + 1)  // 根据根节点切出左子树
    rightTree = arr.slice(leftRoot + 1, arr.length - 1) // 根据根节点切出右子树

    if (leftTree.length > 0) {
    
     
        for (let i = 0; i < leftTree.length; i++) {
    
    
            if (leftTree[i] > rootItem) {
    
     // 查看是否左子树中的每个值都比根节点小,比根节点大就不满足条件,return false
                res = false
            }
        }
    }
    if (rightTree.length > 0) {
    
    
        for (let i = 0; i < rightTree.length; i++) {
    
    
            if (rightTree[i] < rootItem) {
    
     // 查看是否右子树中的每个值都比根节点大,比根节点小就不满足条件,return false
                res = false
            }
        }
    }
    getRes(leftTree) // 递归切出来的左子树
    getRes(rightTree) // 递归切出来的右子树
    return res
}

console.log('getRes(ch): ', getRes([4, 8, 6, 12, 16, 14, 10]));  // true

二分木のレベルトラバーサル

問題の説明: 図に示すように、二分木の階層的なトラバーサル、つまり、矢印が指す方向に従って、1、2、3、4 の階層順序に従って、それぞれにアクセスします。二分木のノード. 再帰的な解決策
ここに画像の説明を挿入
:

var levelOrder = function(root) {
    
    
  if(!root)return []
  let res=[]
  let aux=[root]
  while(aux.length>0){
    
    
    let len=aux.length
    let vals=[]
    for(let i=0;i<len;i++){
    
    
      let node=aux.shift()
      vals.push(node.val)
      if(node.left)aux.push(node.left)
      if(node.right)aux.push(node.right)
    }
    res.push(vals)
  }
  return res
};

非再帰的な解決策:

var levelOrder = function(root) {
    
    
  if(!root)return []
  let res=[]
  let aux=[root]
  while(aux.length>0){
    
    
    let len=aux.length
    let vals=[]
    for(let i=0;i<len;i++){
    
    
      let node=aux.shift()
      vals.push(node.val)
      if(node.left)aux.push(node.left)
      if(node.right)aux.push(node.right)
    }
    res.push(vals)
  }
  return res
};

スタック

1. 2 つのスタックがキューを実装

タイトルの説明: 2 つのスタックを使用してキューを実装し、キューの末尾に整数を挿入 (プッシュ) し、キューの先頭に整数を削除 (ポップ) する機能をそれぞれ完了します。キュー内の要素は int 型です。操作が正当であることを確認してください。つまり、pop 操作の実行時にキューに要素が存在することを確認してください。
アイデア:スタック先入れ先出し、キュー先入れ先出し、2つのスタックstack1、stack2、stack1はスタックにプッシュするために使用され、stack2はスタックをポップアウトするために使用され、stack2の要素はstack1によってポップアップされた要素から取得されます、 stack2 に要素がある限り、 stack2 がスタックのポップを要求されるたびにスタックをポップさせ、要素がない場合、 stack2 は stack1 からスタックをポップします。

let stack1 =[]
let stack2 = []

function push(node) {
    
    
     // write code here
    stack1.push(node)
}
function pop() {
    
    
     // write code here
    if (stack1.length==0 && stack2.length==0) {
    
    
        return null
    }
    else if(stack2.length){
    
    
        return stack2.pop() 
    }
    else{
    
    
        while(stack1.length){
    
    
            stack2.push(stack1.pop())
        }
    }
    return stack2.pop()
}

記事を検索

1. 順番に探す

一般的な for ループ探索、時間計算量 O(n)

function sequenceSearch(arr, num) {
    
    
    const len = arr.length
    for (let i = 0; i < len; i++) {
    
    
        if(arr[i] == num){
    
    
            return i
        }
    }
    return null
}
console.log(sequenceSearch([1,4,7,9],7))  // 2

2. 二分探索

アイデア: left と right はそれぞれ配列の最初と最後の要素を指し、mid は中央の要素を指し、ターゲットが中央の要素より小さい場合は右 = mid+1、ターゲットが中央の要素より大きい場合は、左 = mid+1、中央の要素 = ターゲットの場合、ターゲットを返す、3 つの条件が満たされない場合、配列にそのような要素がないことを示し、undefined を返す 検索するたびに、範囲は前の半分に縮小され
ます1、時間計算量は O(logn)

function getRes(rotateArray, target) {
    
    
  let len = rotateArray.length
  let left = 0
  let right = len - 1
  while (left <= right) {
    
    
    let mid = Math.floor((left + right) / 2)
    if (target > rotateArray[mid]) {
    
    
      left = mid + 1
    }
    else if (target < rotateArray[mid]) {
    
    
      right = mid - 1
    }
    else if (target = rotateArray[mid]) {
    
    
      return mid
    }
    else {
    
    
      return undefined
    }
  }
}

3. ハッシュ テーブル ルックアップ

トピック: 文字ストリーム内の最初の非反復番号については、
詳細について上記の文字列の章の 3 番目の質問を参照してください。

4. 二分木探索

二分ソート木探索アルゴリズムに対応するデータ構造は二分探索木です. アルゴリズムの例については, 上記の木の 3 番目の質問を参照してください.

並べ替え

1. バブルソート

時間計算量: 平均時間計算量 O(n n)、最良の場合 O(n)、最悪の場合 O(n n)
空間計算量: O(1)
安定性: 安定

function swap(i,j,arr){
    
    
    let temp = 0
    temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
}

function bubbleSort(arr) {
    
    
    let len = arr.length
    for (let i = 0; i < len; i++) {
    
    
        for (let j = i + 1; j < len; j++) {
    
    
            if (arr[i] > arr[j]) {
    
    
                swap(i,j,arr)
            }
        }
    }
    return arr
}
console.log(bubbleSort([5, 2, 7, 9, 1])) // [ 1, 2, 5, 7, 9 ]

2.挿入ソート

挿入ソート(配列内の要素を順番に取り出し、取り出した要素と前の順番で並べられていた要素を1つずつ比較し、それよりも大きい要素に遭遇したらさらに比較し、遭遇するまで彼よりも小さい要素、次に現在の要素を彼よりも小さい最初の要素の前の
スペースに挿入します。レコード用の補助スペースのみが必要なので、キーは時間の複雑さに依存します。
最良のケース: つまり、並べ替えテーブル自体が順序付けられ、必要な比較は n-1 回だけです. arr[i] > arr[i+1] 毎回なので、レコードは移動されず、時間計算量はO ( n) .
最悪のケース: (n+2)(n-1)/2 回比較し、(n+4)(n-1)/2 回移動する必要がある、並べ替えテーブルの逆順です並べ替えレコードがランダムである場合、同じ確率の原則に従って、比較と移動の平均回数は約 O(n²)/4 回です。
時間計算量はO(n²)で、バブル ソートや単純な選択ソート アルゴリズムよりも優れています。

function insertSort(arr) {
    
    
    let len = arr.length
    for (let i = 1; i < len; i++) {
    
    
        let currentValue = arr[i] // 获取数组中的当前元素
        let j = i - 1 // 指到当前元素的前一个元素
        while (j >= 0 && arr[j] > currentValue) {
    
     // 当前一个元素大于当前元素时
            if (arr[j] >= currentValue) {
    
     // 如果前一个元素大于当前元素
                arr[j + 1] = arr[j] // 将前一个元素往后移一位,因为当前元素本来就被currentValue保存下来了,i的位置基本可以认为是空的
            }
            j--
        }
        arr[j + 1] = currentValue // while循环结束,j指到比当前元素小的位置,我们就把当前元素插到它的前面
    }
    return arr
}

console.log('insertSort(ch): ', insertSort([4, 8, 6, 12, 16, 4, 14, 10])); //  [  4,  4,  6,  8, 10, 12, 14, 16]

3.クイックソート

クイックソート (最悪の場合、つまり、配列が既に順序付けられているか、大雑把に順序付けられている場合、各分割は配列の最後の要素を基準として、各分割は 1 つの要素しか削減できず、クイックソートは残念ながら縮退バブルソートの場合、最悪のケースはO(n²)です。クイックソートはマージソートと同じです。各要素を1回トラバースする必要があるため、各レイヤーの合計時間の複雑さはO(n)です。実際のアプリケーションでは、クイック ソートの平均時間計算量は O(nlogn) です. スペースの計算量: クイック ソートの再帰アルゴリズムはスタックのサポートを必要とし, スタックのサイズは最大 n なので O(n)) です
.サブインターバルの長さは前のものの 2 倍であるため、merge_sort 再帰を logn 回実行する必要があります
キー ポイント 2: 各インターバルについて、処理時に、インターバル内の各要素を 1 回トラバースする必要があり、それぞれの合計時間レイヤーは複雑です度はすべてO(n)です

function quickSort(arr) {
    
    
    if (arr.length <= 1) {
    
     // 如果数组中只有一个值或者是空数组,直接将其返回,这是递归的条件
        return arr
    }
    let currentIndex = Math.floor(arr.length / 2) // 获取数组最中间的数
    let currentItem = arr.splice(currentIndex, 1) // 将最中间的数取出并在数组中将最中间的数删除
    let leftArr = [], rightArr = [] 
   // 关键点2:对于每一个区间,处理的时候,都需要遍历一次区间中的每一个元素,每一层的总时间复杂度都是O(n)
    arr.forEach(item => {
    
    
        if (item < currentItem) {
    
    
            leftArr.push(item) // 将数组中比中间值小的都放在中间值的左边
        }
        else {
    
    
            rightArr.push(item) // 将数组中比中间值大的都放在中间值的左边
        }
    })

    // 左数组和右数组操作一样,递归再进行上面的部分
    return quickSort(leftArr).concat(currentItem).concat(quickSort(rightArr))
    // 关键点1:划分子区间,每一次的子区间长度是上一次的两倍,所以merge_sort递归需要执行logn次
}

console.log(quickSort([5, 2, 7, 9, 1]))  // [ 1, 2, 5, 7, 9 ]

4.マージソート

最初に配列を 2 つに分割し、1 つを 2 つに分割します...配列の要素が 1 つになるまで、要素を比較した後に要素を配列に入れ、再帰機能を次の再帰関数に渡し、最後に平均をマージします時間の
複雑度: O(nlogn)
最高の時間の複雑さ: O(n)
最悪の時間の複雑さ: O(nlogn)
空間の複雑さ: O(n)
並べ替え方法: インプレース
安定性: 安定

function merge_sort(arr) {
    
     // 将数组由最中间的数作为基准一分为二,递归执行,最终将一个大数组分为由一个元素组成的数组
    if (arr.length == 1){
    
    
         return arr
    }
    
    var mid = Math.floor(arr.length / 2)
    var leftArr = arr.slice(0, mid)
    var rightArr = arr.slice(mid)
// 关键点1:划分子区间,每一次的子区间长度是上一次的两倍,所以merge_sort递归需要执行logn次
    return Merger(merge_sort(leftArr), merge_sort(rightArr)); //合并左右部分
    // 关键点2:Merger方法每次执行的时间复杂度为O(n),具体看下方
}

function Merger(leftArr, rightArr) {
    
    
    var leftLen = leftArr && leftArr.length;
    var rightLen = rightArr && rightArr.length;
    var resArr = [];
    var i = 0, j = 0;

    while (i < leftLen && j < rightLen) {
    
    
        if (leftArr[i] < rightArr[j]) {
    
    
            resArr.push(leftArr[i++]);
        }
        else {
    
    
            resArr.push(rightArr[j++]);
        }
    }
    while (i < leftLen) {
    
    
        resArr.push(leftArr[i++]);

    }
    while (j < rightLen) {
    
    
        resArr.push(rightArr[j++]);
    }
    console.log("将数组", leftArr, '和', rightArr, '合并为', resArr)
    return resArr;
    递归过程:
    // 将数组 [ 8 ] 和 [ 2 ] 合并为 [ 2, 8 ]
    // 将数组 [ 1 ] 和 [ 2, 8 ] 合并为 [ 1, 2, 8 ]
    // 将数组 [ 4 ] 和 [ 9 ] 合并为 [ 4, 9 ]
    // 将数组 [ 6 ] 和 [ 4, 9 ] 合并为 [ 4, 6, 9 ]
    // 将数组 [ 1, 2, 8 ] 和 [ 4, 6, 9 ] 合并为 [ 1, 2, 4, 6, 8, 9 ]
}
console.log('merge_sort(ch): ', merge_sort([1,8,2,6,4,9])); //  [  4,  4,  6,  8, 10, 12, 14, 16]

バックトラッキングアルゴリズム

1. マトリックス内のパス

問題の説明: 行列内の文字列のすべての文字を含むパスが存在するかどうかを判断する関数を設計してください。パスはマトリックス内の任意のグリッドから開始でき、各ステップでマトリックス内の上下左右に 1 つのグリッドを移動できます。パスがマトリックス内のグリッドを通過する場合、そのパスは再びグリッドに入ることができません。[[a,b,c,e],[s,f,c,s],[a,d,e,e]], "abcced" アイデア: パスの開始点として場所を選択し、
( 0 , 0), 以下の各ステップは, マトリックス内で上下左右に 1 スペース移動できます. 見つからない場合は false を返します. 見つかった場合は, ブール値の対応する位置を設定します.上、右、下、左に開始; 移動後、境界判定を行う必要があります: 1. 位置がまだ行列内にあるかどうかを判断します; 2. 通過したかどうか; 対応する数値が行列内にある場合文字列が見つかった場合、行列の中で 1 スペースを上下左右に移動して、目的の文字列に一致する次の文字を見つけることができます. 移動の操作方法は同じなので、ここでは再帰を使用して実装し、繰り返しパスはグリッドに繰り返し追加できないため、パスはスタックと見なすことができます。そのため、パスが各グリッドに入ったかどうかを識別するために、文字マトリックスと同じサイズのブール値マトリックスも定義する必要があります。

// [[a,b,c,e],[s,f,c,s],[a,d,e,e]],"abcced"
let m, n // m行 n列
let p = [[-1, 0], [1, 0], [0, 1], [0, -1]] // 向左 右 下 上 的方向走
let isUsed // 判断格子是否被走过,走过就为true,没走过为false
function hasPath(matrix, word) {
    
    
    m = matrix.length
    n = matrix[0].length
    isUsed = new Array(m).fill(false).map(p => new Array(n).fill(false)) // 创建一个m行 n列的矩阵
    // 从[0,0]开始找, 循环多次找到首个字母的初始位置
    for (let i = 0; i < m; i++) {
    
    
        for (let j = 0; j < n; j++) {
    
    
            if (findWords(matrix, word, 0, i, j)) {
    
    
                return true
            }
        }
    }
    return false;
}

function findWords(matrix, word, index, startX, startY) {
    
    
    if (index === word.length - 1) {
    
    
        return matrix[startX][startY]  === word[index]
    }

    if (matrix[startX][startY]  == word[index]) {
    
     // 在矩阵中找到了words中的字母
        isUsed[startX][startY] = true // 表明这个格子已经找过了
        for (let i = 0; i < 4; i++) {
    
      // 从矩阵中已经找到words中的字母的位置开始从左 右 上 下的方向又开始寻找words中的下一个字母
            let nextX = startX + p[i][0] 
            let nextY = startY + p[i][1]
            if (isInArea(nextX, nextY) && !isUsed[nextX][nextY]) {
    
     // 判断是否走超出了格子 && 判断这个格子是否被走过
                if(findWords(matrix, word, index+1, nextX, nextY)){
    
     // index+1 因为index的位置的words字母已经被找到了,相同的方法比较矩阵中值和words中的字母是否相同
                    return true
                }
            }
        }
    }
    isUsed[startX][startY] = false
    return false // 在矩阵中没找到与words中相同的值就return false
}

function isInArea(x, y) {
    
    
    return x >= 0 && y >= 0 && x < m && y < n
}

console.log('hasPath: ', hasPath([
    ['a', 'b', 'c', 'e'],
    ['s', 'f', 'c', 's'],
    ['a', 'd', 'e', 'e']], "abcced"));
// true

2. ロボットの可動範囲

地面には行と列のある正方形があります。座標は [0,0] から [rows-1,cols-1] までです。ロボットは座標 0 と 0 のグリッドから移動を開始し、上下左右の 4 方向に一度に 1 つのグリッドしか移動できませんが、座標の合計が 0 のグリッドには入ることができません。行座標と列座標がしきい値を超えています。たとえば、しきい値が 18 の場合、3+5+3+7 = 18 であるため、ロボットは正方形 [35,37] に入ることができます。ただし、正方形 [35,38] には 3+5+3+8 = 19 なので入りません。ロボットはいくつのグリッドに到達できますか?
アイデア: ロボットが 0,0 の位置から歩き始めると、その次のステップは上下左右に移動できますが、下降する前に判断する必要があります; 1. 現在の位置が行列 2 内にあるかどう かそれが配置されている行列グリッドは、行座標と列座標の数字の合計がしきい値よりも大きいことを満たします; 3. すでにグリッドを通過しているかどうか、行と列の行列を作成できる最後の条件、そして最初にそれを置くそれらはすべてfalseです。そのセルが渡されると、そのセルがtrueに設定されるため、trueは渡されたことを意味します。

function movingCount(threshold, rows, cols) {
    
    
    if (threshold < 0 || rows < 0 || cols < 0) {
    
    
        return 0
    }
    let isVisited = new Array(rows).fill(false).map(item => new Array(cols).fill(false))
    let count = movingCountCore(threshold, rows, cols, 0, 0, isVisited)
    return count
}

function movingCountCore(threshold, rows, cols, startX, startY, isVisited) {
    
    
    let count = 0
    if (checkBoard(rows, cols, startX, startY) && getDigitSum(startX, startY) <= threshold && !isVisited[startX][startY]) {
    
    
        isVisited[startX][startY] = true
        count = 1 + movingCountCore(threshold, rows, cols, startX - 1, startY, isVisited)
            + movingCountCore(threshold, rows, cols, startX, startY - 1, isVisited)
            + movingCountCore(threshold, rows, cols, startX + 1, startY, isVisited)
            + movingCountCore(threshold, rows, cols, startX, startY + 1, isVisited)
    }
    return count
}

function checkBoard(rows, cols, startX, startY) {
    
    
    if (startX >= 0 && startX < rows && startY >= 0 && startY < cols) {
    
    
        return true
    }
    return false
}

function getDigitSum(startX, startY) {
    
    
    let sum = 0
    let str = startX + '' + startY
    for (let i = 0; i < str.length; i++) {
    
    
        sum += str.charAt(i) / 1
    }
    return sum
}
console.log(movingCount(5, 10, 10)); // 21

高品質なコード記事

1. 値の整数乗

タイトルの説明: double 型の浮動小数点基数と int 型の整数指数が与えられます。基数のべき乗を求めます。基数と指数が同時に 0 にならないようにしてください。ライブラリ関数は使用できず、大きな数値や小数点以下の 0 の数を考慮する必要はありません。
: この問題は単純ですが, 軽視できない単純な問題であることがよくあります. 完全な境界判定を考慮する必要があります. 整数, 負の数とゼロを考慮する必要があります. 境界を考慮しない単純な問題には面接官に悪影響。

function Power(base, exponent) {
    
    
    if (base === 0) {
    
    
        return 0
    }
    else if (exponent === 0) {
    
    
        return 1
    }
    else {
    
    
        let index = 0
        let res = 1
        let numType = true
        if (exponent < 0) {
    
    
            numType = false
            exponent = -exponent
        }
        while (index < exponent) {
    
    
            res *= base
            index++
        }
        return numType ? res : 1 / res
    }
}
console.log('Power: ', Power(2, 3)); // 8 

コードワードは簡単ではありません。すべて自分でまとめたものです。気に入って励ましてください〜
継続的に更新しています...

おすすめ

転載: blog.csdn.net/xiaoguoyangguang/article/details/117334087