面试算法-分治算法

204 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

前言

有时候思路特别清晰,有时候又忘了,干脆再整理记录下常用的算法类型····<*)) >>=<

1、定义

分治算法(divide and conquer)的核心思想其实就是四个字,分而治之

也就是将原问题成 n 个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,子问题之间没有相关性,可以独立求解。

就是合并子问题的结果,就得到原问题的解。

2、基本步骤

分治算法是一种处理问题的思想、或者说是一种策略,而递归是一种编程技巧。

最常用的算法就是归并排序,归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略。

排序数组

给你一个整数数组 nums,请你将该数组升序排列。

示例 1:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

思路

“分” 就是将原始数组逐次二分两个子数字,直到每个子数组只剩一个元素,一个元素的数组自然是有序的。

“治”实际上是将已经有序的数组合并为更大的有序数组。

// 归并排序
// 时间复杂度:平均 O(nlogN)、最好 O(nlogN)、最坏 O(nlogN)
// 空间复杂度:O(N) 空间换时间

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function (nums) {
  if (nums.length < 2) {
    return nums;
  }

  let mid = Math.floor(nums.length / 2);
  // 拆分成两个子数组
  let left = nums.slice(0, mid);
  let right = nums.slice(mid);
  // 递归拆分,合并
  return mergeArray(sortArray(left), sortArray(right));
};

var mergeArray = function (nums1, nums2) {
  const result = [];

  while (nums1.length && nums2.length) {
    // 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
    if (nums1[0] <= nums2[0]) {
      result.push(nums1.shift()); //每次都要删除left或者right的第一个元素,将其加入result中
    } else {
      result.push(nums2.shift());
    }
  }

  while (nums1.length) result.push(nums1.shift());

  while (nums2.length) result.push(nums2.shift());
  // result = nums1.length ? [...result, ...nums1] : [...result, ...nums2];
  return result;
};

3、实例

148. 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

// 对链表自顶向下归并排序的过程如下。

// 找到链表的中点,以中点为分界,将链表拆分成两个子链表。
// 寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。

// 对两个子链表又可以拆分更多子链表,到一个节点的时候就是有序的,然后利用「合并两个有序链表」合并成一个新的有序链表。
// 将两个排序后的子链表合并,得到完整的排序后的链表。

// 自顶向下归并排序
// 时间复杂度:O(nlogn) 空间复杂度:O(logn)

function ListNode(val, next) {
  this.val = val === undefined ? 0 : val;
  this.next = next === undefined ? null : next;
}

// 合并两个有序链表
const merge = (list1, list2) => {
  const prehead = new ListNode(-1);
  let prev = prehead;
  while (list1 != null && list2 != null) {
    if (list1.val >= list2.val) {
      prev.next = list2;
      list2 = list2.next;
    } else {
      prev.next = list1;
      list1 = list1.next;
    }
    prev = prev.next;
  }
  prev.next = list1 === null ? list2 : list1;
  return prehead.next;
};

const toSortList = (head, tail) => {
  if (head === null) {
    return head;
  }
  if (head.next === tail) {
    head.next = null;
    return head;
  }
  let slow = head,
    fast = head;
  while (fast !== tail) {
    slow = slow.next;
    fast = fast.next;
    if (fast !== tail) {
      fast = fast.next;
    }
  }
  const mid = slow;
  return merge(toSortList(head, mid), toSortList(mid, tail));
};

var sortList = function (head) {
  return toSortList(head, null);
};