一、分治
分治就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
二分查找
// 二分查找
function binarySearch (array, value) {
let left = 0
let right = array.length
let mid
while (left < right) {
mid = (left + right) >> 1
if (array[mid] === value) return mid
if (array[mid] > value) right--
if (array[mid] < value) left++
}
return 'findError'
}
快速幂
// 快速幂,n>0且为整数,m的n次方可以看成是m的n/2次方,递归调用
function fastPow (m, n) {
if (n === 0) return 1
if (n % 2 === 1) { // n为奇数
return fastPow(m, n - 1) * m
} else { // n为偶数
return fastPow(m * m, n / 2)
}
}
二、贪心算法
贪心算法解决的一类问题就是将复杂的问题拆解成小问题,通过局部最优解组合成为最终解!
// 输入:nums = [10,9,2,5,3,7,101,18]
// 输出:4
// 解释:最长递增子序列是 [2,3,7,101],因此长度为 4
// 贪心+二分,时间复杂度O(n*logn)
let lengthOfLIS = function (nums) {
let len = 1; let n = nums.length
if (n === 0) return 0
let d = [] // d为最优解的栈
d[len] = nums[0]
for (let i = 1; i < n; ++i) {
if (nums[i] > d[len]) { // 下个数比栈顶数大就入栈
d[++len] = nums[i]
} else {
let left = 1; let right = len; let index = 0 // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 index 设为 0
while (left <= right) { // 二分法,取中位数mid,然后迭代更新左右边界left和right
let mid = (left + right) >> 1
if (d[mid] < nums[i]) { // 找到最适合塞当前数的index
index = mid
left = mid + 1
} else {
right = mid - 1
}
}
d[index + 1] = nums[i]
}
}
return len
}
三、动态规划
动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。
斐波那契数列问题
function fibonacci3(n) {
let n1 = 1
let n2 = 1
let sum = 1
for(let i = 3; i <= n; i += 1) {
sum = n1 + n2
n1 = n2
n2 = sum
}
return sum
}
二分查找会话时间下标
// const sessionList = [{sessionId: 123, svrTime: 1622615330243, ...},{sessionId: 321, svrTime: 1622615349837, ...},...]
// const searchRes = {sessionId: 123, svrTime: 1622615349837, ...}
function search(searchKey, sessionList){
let midNum = sessionList.length >> 1
let mid = sessionList[midNum]
if(mid.svrTime === searchKey.svrTime) return mid
if(midNum === 0) return null
if(mid.svrTime > searchKey.svrTime){
search(searchKey,sessionList.slice(0,midNum))
}else{
search(searchKey,sessionList.slice(midNum))
}
}
动态规划求解最长子序列
// 输入:nums = [10,9,2,5,3,7,101,18]
// 输出:4
// 解释:最长递增子序列是 [2,3,7,101],因此长度为 4
// 动态规划,时间复杂度O(n^2)
let lengthOfLIS = function(nums, dp = [1]) {
for (let i = 1; i < nums.length; i++){
dp[i] = 1
for (let j = 0; j < i; j++) {
nums[i] > nums[j] && (dp[i] = Math.max(dp[i], dp[j] + 1)) //计算当前元素节点存在最长递增子序列的长度
}
}
return Math.max(...dp)
}
背包问题用动态规划求解
// weight 物品重量集合
// value 物品价值集合
// maxW 背包重量
// n 物品数量
function packing (weight, value, maxW, n) {
let dp = [] // 行放重量,列放价值
for (let i = 0; i <= maxW; i++) {
dp[i] = []
for (let j = 0; j < n; j++) {
// 初始状态,背包为0
if (i === 0) {
dp[i][j] = 0
continue
}
// 当前物品重量大于当前背包重量
if (weight[j] > i) {
dp[i][j] = dp[i][j - 1] || 0
continue
}
// 递推公式
let curValueMax = dp[i - weight[j]][j - 1] || 0 + value[j]
dp[i][j] = Math.max(curValueMax, dp[i][j - 1] || 0)
}
}
return dp
}