JavaScript算法 + leetcode - 排序和搜索

105 阅读3分钟

本文记录JavaScript中常见的排序和搜索算法。

排序

  • 把乱序数组排成升序或者降序数组
  • JS中的排序是通过sort方法

搜索

  • 找到数组中某个元素的下标
  • JS中的搜索是通过indexOf方法

冒泡排序

  • 比较所有相邻元素,如果第一个比第二个大,则交换它们。
  • 一轮下来,可以保证最后一个元素是最大的。
  • 执行n - 1轮,就可以完成排序。

代码展示:

const arr = [5,3,2,1,4];

function bubbleSort(arr) {
    for (let i = 0; i < arr.length - 1; i++) {
        for (let j = 0; j < arr.length - 1 - i; i++) {
            if (arr[j] > arr[j + 1]) {
                [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
            }
        }
    }
    return arr;
}
bubbleSort(arr);

复杂度分析:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

选择排序

  • 找到数组中的最小值,选中它并将其放在第一位
  • 接着找到第二小的值,选中它并将其放在第二位
  • 以此类推,执行n-1轮

代码展示:

const arr = [5,3,2,1,4];

function selectionSort(arr) {
    for (let i = 0; i < arr.length; i++) {
        let minIndex = i;
        for (let j = i; j < arr.length; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex != i) [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
    }
    return arr;
}
selectionSort(arr);

复杂度分析:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

插入排序

  • 从第二个数开始往前比
  • 比它大就往后排
  • 以此类推进行到最后一个数。

代码展示:

const arr = [5,3,2,1,4];

function insertionSort(arr) {
    for (let i = 1; i < arr.length; i++) {
        const temp = arr[i];
        let j = i;
        while (j > 0 && arr[j - 1] > temp) {
             arr[j] = arr[j - 1];
             j--;
        }
        arr[j] = temp;
    }
    return arr;
}
insertionSort(arr);

复杂度分析:

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

归并排序

  • 将数组分为两半,再递归对子数组进行“分”操作,直到分成一个个单独的数。

  • 把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组。

代码展示:

const arr = [5,3,2,1,4];
function mergeSort(arr) {
    const res = (arr) => {
        if (arr.length === 1) return arr;
        const mid = Math.floor(arr.length / 2);
        const left = arr.slice(0, mid);
        const right = arr.slice(mid, arr.length);
        const orderLeft = res(left);
        const orderRight = res(right);
        const res = [];
        while (orderLeft.length || orderRight.length) {
            if (orderLeft.length && orderRight.length) {
                res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift());
            } else if (orderLeft.length) {
                res.push(orderLeft.shift());
            } else if (orderRight.length) {
                res.push(orderRight.shift());
            }
        }
        return res;
    }
    const res = res(arr);
    res.forEach((n, i) => {
        arr[i] = n;
    });
    return arr;
    
}
mergeSort(arr);

复杂度分析:

  • 分的时间复杂度是O(logN),合的时间复杂度是O(n),时间复杂度: O(nlogN)
  • 空间复杂度:O(n)

快速排序

  • 分区:从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素放在基准的后面。

  • 合并:递归地对基准前后的子数组进行分区。

const arr = [5,3,2,1,4];
function quickSort(arr) {
    const rec = (arr) => {
        if (arr.length === 1) return arr;
        const mid = arr[0];
        const left = [];
        const right = [];
        for (let i = 1; i < arr.length; i++) {
            if (arr[i] < mid) {
                left.push(arr[i]);
            } else {
                right.push(arr[i]);
            }
        }
        return [...rec(left), mid, ...rec(right)];
    }
    const res = rec(arr);
    res.forEach((n, i) => {
        arr[i] = n;
    });
    return arr;
}
quickSort(arr);

复杂度分析:

  • 递归的时间复杂度O(logN),分区的时间复杂度为O(n),总的时间复杂度O(nlogN)
  • 空间复杂度:O(n)。

顺序搜索

const arr = [5, 2, 1, 4, 3];
function sequentialSearch(arr, item) {
    for (let i = 0; i < arr.length; i += 1) {
        if (arr[i] === item) {
            return i;
        }
    }
    return -1;
}
sequentialSearch(arr, 3)

复杂度分析:

  • 时间复杂度:O(n)

二分搜索

  • 前提:数组是有序的
const arr = [1, 2, 3, 4, 5];

function binarySearch(arr, item) {
    let low = 0;
    let high = arr.length - 1;
    while (low <= high) {
        const mid = Math.floor((low + high) / 2);
        if (arr[mid] < item) {
            low = mid + 1;
        } else if (arr[mid] > item) {
            high = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}
binarySearch(arr, 4);

复杂度分析:

  • 时间复杂度:O(logn)

leetcode题目

179. 最大数

56. 合并区间

/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
  if (intervals.length === 0 || intervals.length === 1) return intervals;
  intervals.sort((a, b) => a[0] - b[0]);
  for (let i = 0; i < intervals.length - 1; i++) {
    const b0 = intervals[i][0];
    const b1 = intervals[i][1];
    const p0 = intervals[i + 1][0];
    const p1 = intervals[i + 1][1];
    if (b1 >= p0) intervals.splice(i--, 2, [Math.min(b0, p0), Math.max(b1, p1)]);
  }
  return intervals;
};

148. 排序链表

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var sortList = function(head) {
  if (!head || !head.next) return head;
  let slow = head;
  let fast = head;
  let preSlow = null;
  while (fast && fast.next) {
    preSlow = slow;
    slow = slow.next;
    fast = fast.next.next;
  }
  preSlow.next = null;
  let l = sortList(head);
  let r = sortList(slow);
  return mergeSort(l, r);
};

var mergeSort = (l1, l2) => {
  const dummy = new ListNode(0);
  let p1 = l1;
  let p2 = l2;
  let p3 = dummy;
  while (p1 && p2) {
    if (p1.val < p2.val) {
      p3.next = p1;
      p1 = p1.next;
    } else {
      p3.next = p2;
      p2 = p2.next;
    }
    p3 = p3.next;
  }
  if (p1) p3.next = p1;
  if (p2) p3.next = p2;
  return dummy.next;
}

147. 对链表进行插入排序

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var insertionSortList = function(head) {
    if (!head) return head;
    const dummyHead = new ListNode(0);
    dummyHead.next = head;
    let lastSorted = head, curr = head.next;
    while (curr) {
          if (lastSorted.val <= curr.val) {
              lastSorted = lastSorted.next;
          } else {
              let prev = dummyHead;
              while (prev.next.val <= curr.val) {
                  prev = prev.next;
              }
              lastSorted.next = curr.next;
              curr.next = prev.next;
              prev.next = curr;
          }
          curr = lastSorted.next;
    }
    return dummyHead.next;
}

总结

熟悉几种常见算法的原理,以及对链表进行排序,可以更好的理解排序和链表。