开启掘金成长之旅!这是我参与「掘金日新计划 · 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);
};