1.开篇介绍
2.时间空间复杂度
3.动态规划
4.贪心
5.二分查找
6.深度优先&广度优先
7.双指针
8.滑动窗口
9.位运算
10.递归&分治
11剪枝&回溯
12.堆
13.单调栈
14.排序算法
15.链表
16.set&map
17.栈
18.队列
19.数组
20.字符串
21.树
22.字典树
23.并查集
24.其他类型题
递归伪代码模版:
function recursion(level, param1, param2, ...) { //递归终止条件 if (level > MAX_LEVEL) { // output result return; } //处理当前层 process_data(level, data, ...); //进入下一层 recursion(level + 1, p1, ...); //重置状态 reverse_state(level); }
分治会将大问题拆解成小问题,拆解到最小问题之后,开始不断合并结果,递归是分治实现的一种形式或者是分治实现的一部分,分治包括三分部分,分解、计算、合并。分治的场景很多,例如快速排序,归并排序。
分治伪代码模版:
function divide_conquer(problem, param1, param2, ...){ if(problem === null){ // return result } //分割问题 subproblem = split_problem(problem, data) //计算子问题 subResult1 = divide_conquer(subproblem[0], p1, ...) subResult2 = divide_conquer(subproblem[1], p1, ...) subResult3 = divide_conquer(subproblem[2], p1, ...) ... result = process_resule(subResult1, subResult2, subResult3,...) }
计算n! n! = 1 * 2 * 3... * n
function factorial(n) { if (n <= 1) return 1; return n * factorial(n - 1); } factorial(6); 6 * factorial(5); 6 * 5 * factorial(4); //... 6 * 5 * 4 * 3 * 2 * factorial(1); 6 * 5 * 4 * 3 * 2 * 1; 6 * 5 * 4 * 3 * 2; //... 6 * 120; 720;
斐波那契数列,F(n)=F(n-1)+F(n+2)
function fib(n) { if (n === 0 || n === 1) { return n; } return fib(n - 1) + fib(n - 2); }
x*x
的n/2
的次方,当n为奇数的时候拆分成x * myPow(x,n-1)
,注意当n是负数或者是0的特殊情况O(logn)
, n是进行二进制拆分的时间复杂度。空间复杂度:O(logn)
, n为递归深度js:
var myPow = function (x, n) { if (n === 0) return 1 // n=0直接返回1 if (n < 0) { //n<0时 x的n次方等于1除以x的-n次方分 return 1 / myPow(x, -n) } if (n % 2) { //n是奇数时 x的n次方 = x*x的n-1次方 return x * myPow(x, n - 1) } return myPow(x * x, n / 2) //n是偶数,使用分治,一分为二,等于x*x的n/2次方 }
Java:
class Solution { public double myPow(double x, int n) { long N = n; return N >= 0 ? pow(x, N) : 1.0 / pow(x, -N); } public double pow(double x, long y) { if (y == 0) { return 1.0; } double ret = pow(x, y / 2); return y % 2 == 0 ? ret * ret : ret * ret * x; } }
O(logn)
: n为对 n 进行二进制拆分的时间复杂度,空间复杂度O(1)
js:
var myPow = function (x, n) { if (n < 0) { x = 1 / x; n = -n; } let result = 1; while (n) { if (n & 1) result *= x; //判断n的二进制最后一位是否是1, 如果是1则将结果乘以x x *= x; n >>>= 1; //进行无符号右移1位,此处不能使用有符号右移(>>) //当n为-2^31转换成正数时的二进制位“10000000000000000000000000000000” , //如果采用有符号右移时会取最左侧的数当符号即(1),所以返回的结果是 -1073741824 /* C++ 中只有一种右移运算符——>>。它的定义如下:移出的低位舍弃; 如果是无符号数,高位补0;如果是有符号数,高位补符号位。 而JavaScript中有两种右移运算符——>>和>>>。其中>>是有符号右移, 即高位补符号位(可能会出现负数变正数,正数变负数的异常情况);>>>是无符号右移,高位无脑补0。 */ } return result; }
Java:
class Solution { public double myPow(double x, int n) { if(x == 0.0f) return 0.0d; long b = n; double result = 1.0; if(b < 0) { x = 1 / x; b = -b; } while(b > 0) { if((b & 1) == 1) result *= x; x *= x; b >>= 1; } return result; } }
n/2
,则在数组nums.length / 2
的位置就是这个数O(nlogn)
,快排的时间复杂度。空间复杂度:O(logn)
,排序需要logn
的空间复杂度js:
var majorityElement = function (nums) { nums.sort((a, b) => a - b); return nums[Math.floor(nums.length / 2)]; };
Java:
class Solution { public int majorityElement(int[] nums) { Arrays.sort(nums); return nums[nums.length / 2]; } }
n/2
则返回这个数O(n)
,n为nums数组的长度。空间复杂度:O(n)
,哈希表需要的空间js:
var majorityElement = function (nums) { let half = nums.length / 2; let obj = {}; for (let num of nums) { obj[num] = (obj[num] || 0) + 1; if (obj[num] > half) return num; } };
Java:
class Solution { public int majorityElement(int[] nums) { Map<Integer,Integer> obj = new HashMap<>(); for(int num : nums){ obj.put(num, obj.getOrDefault(num, 0) + 1); if(obj.get(num) > nums.length / 2) return num; } return 0; } }
js:
//[1,1,2,2,2] const majorityElement = nums => { let count = 1; let majority = nums[0]; for (let i = 1; i < nums.length; i++) { if (count === 0) { majority = nums[i]; } if (nums[i] === majority) { count++; } else { count--; } } return majority; };
java:
class Solution { public int majorityElement(int[] num) { int majority = num[0]; int count = 1; for (int i = 1; i < num.length; i++) { if (count == 0) { count++; majority = num[i]; } else if (majority == num[i]) { count++; } else { count--; } } return majority; } }
O(nlogn)
,不断二分,复杂度是logn
,二分之后每个区间需要线性统计left与right的个数,复杂度是n。空间复杂度:O(logn)
,递归栈的消耗,不断二分。Js:
var majorityElement = function (nums) { const getCount = (num, lo, hi) => {//统计lo到hi之间num的数量 let count = 0; for (let i = lo; i <= hi; i++) { if (nums[i] === num) count++; } return count; }; const getMode = (lo, hi) => { if (lo === hi) return nums[lo]; //拆分成更小的区间 let mid = Math.floor((lo + hi) / 2); let left = getMode(lo, mid); let right = getMode(mid + 1, hi); if (left === right) return left; let leftCount = getCount(left, lo, hi);//统计区间内left的个数 let rightCount = getCount(right, lo, hi);//统计区间内right的个数 return leftCount > rightCount ? left : right;//返回left和right中个数多的那个 }; return getMode(0, nums.length - 1); };
Java:
class Solution { private int getCount(int[] nums, int num, int lo, int hi) { int count = 0; for (int i = lo; i <= hi; i++) { if (nums[i] == num) { count++; } } return count; } private int getMode(int[] nums, int lo, int hi) { if (lo == hi) { return nums[lo]; } int mid = (hi - lo) / 2 + lo; int left = getMode(nums, lo, mid); int right = getMode(nums, mid + 1, hi); if (left == right) { return left; } int leftCount = getCount(nums, left, lo, hi); int rightCount = getCount(nums, right, lo, hi); return leftCount > rightCount ? left : right; } public int majorityElement(int[] nums) { return getMode(nums, 0, nums.length - 1); } }
O(n)
,n为树的节点个数。空间复杂度O(n)
,递归深度,最差情况下为数的节点数js:
const maxPathSum = (root) => { let maxSum = Number.MIN_SAFE_INTEGER;//初始化最大路径和 const dfs = (root) => { if (root == null) {//遍历节点是null 返回0 return 0; } const left = dfs(root.left); //递归左子树最大路径和 const right = dfs(root.right); //递归右子树最大路径和 maxSum = Math.max(maxSum, left + root.val + right); //更新最大值 //返回当前子树的路径和 分为走左边、右边、不动 3种情况 const pathSum = root.val + Math.max(0, left, right); return pathSum < 0 ? 0 : pathSum; }; dfs(root); return maxSum; };
java:
class Solution { int maxSum = Integer.MIN_VALUE; public int dfs(TreeNode root){ if (root == null) { return 0; } int left = dfs(root.left); int right = dfs(root.right); maxSum = Math.max(maxSum, left + root.val + right); int pathSum = root.val + Math.max(Math.max(0, left), right); return pathSum < 0 ? 0 : pathSum; } public int maxPathSum(TreeNode root) { dfs(root); return maxSum; } }
O(n)
,空间复杂度O(1)
js:
var maxSubArray = function(nums) { const dp = []; let res = (dp[0] = nums[0]);//初始化状态 for (let i = 1; i < nums.length; ++i) { dp[i] = nums[i]; if (dp[i - 1] > 0) {//前面的状态是正数 则加上 dp[i] += dp[i - 1]; } res = Math.max(res, dp[i]);//更新最大值 } return res; }; //状态压缩 var maxSubArray = function(nums) { let pre = 0, maxAns = nums[0]; nums.forEach((x) => { pre = Math.max(pre + x, x); maxAns = Math.max(maxAns, pre); }); return maxAns; };
java:
class Solution { public int maxSubArray(int[] nums) { int pre = 0, maxAns = nums[0]; for (int x : nums) { pre = Math.max(pre + x, x); maxAns = Math.max(maxAns, pre); } return maxAns; } }
O(nlogn)
,二分复杂度O(logn)
,二分之后每一层统计左右和合并之后的最大子序和复杂度是O(n)
,所以之后的复杂度是O(nlogn)
。空间复杂度O(logn)
,递归的栈空间,因为是二分,每次数据规模减半js:
function crossSum(nums, left, right, mid) { if (left === right) {//左右相等 返回左边的值 return nums[left]; } let leftMaxSum = Number.MIN_SAFE_INTEGER;//左边最大值初始化 let leftSum = 0; for (let i = mid; i >= left; --i) { leftSum += nums[i]; leftMaxSum = Math.max(leftMaxSum, leftSum);//更新左边最大子序和 } let rightMaxSum = Number.MIN_SAFE_INTEGER; let rightSum = 0; for (let i = mid + 1; i <= right; ++i) { rightSum += nums[i]; rightMaxSum = Math.max(rightMaxSum, rightSum);//更新右边最大子序和 } return leftMaxSum + rightMaxSum;//返回左右合并之后的最大子序和 } function _maxSubArray(nums, left, right) { if (left === right) {//递归终止条件 return nums[left]; } const mid = Math.floor((left + right) / 2); const lsum = _maxSubArray(nums, left, mid);//左边最大子序和 const rsum = _maxSubArray(nums, mid + 1, right);//右边最大子序和 const cross = crossSum(nums, left, right, mid);//合并左右的之后的最大子序和 return Math.max(lsum, rsum, cross);//返回3中子序和中最大的 } var maxSubArray = function(nums) { return _maxSubArray(nums, 0, nums.length - 1); };
java:
public class Solution { public int maxSubArray(int[] nums) { int len = nums.length; if (len == 0) { return 0; } return _maxSubArray(nums, 0, len - 1); } private int crossSum(int[] nums, int left, int mid, int right) { int sum = 0; int leftSum = Integer.MIN_VALUE; for (int i = mid; i >= left; i--) { sum += nums[i]; if (sum > leftSum) { leftSum = sum; } } sum = 0; int rightSum = Integer.MIN_VALUE; for (int i = mid + 1; i <= right; i++) { sum += nums[i]; if (sum > rightSum) { rightSum = sum; } } return leftSum + rightSum; } private int _maxSubArray(int[] nums, int left, int right) { if (left == right) { return nums[left]; } int mid = left + (right - left) / 2; return max3(_maxSubArray(nums, left, mid), _maxSubArray(nums, mid + 1, right), crossSum(nums, left, mid, right)); } private int max3(int num1, int num2, int num3) { return Math.max(num1, Math.max(num2, num3)); } }
O(n)
,空间复杂度O(n)
js:
var rangeSumBST = function(root, low, high) { if (!root) { return 0; } if (root.val > high) { return rangeSumBST(root.left, low, high); } if (root.val < low) { return rangeSumBST(root.right, low, high); } return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high); };
java:
class Solution { public int rangeSumBST(TreeNode root, int low, int high) { if (root == null) { return 0; } if (root.val > high) { return rangeSumBST(root.left, low, high); } if (root.val < low) { return rangeSumBST(root.right, low, high); } return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high); } }
O(n)
,空间复杂度O(n)
js:
var rangeSumBST = function(root, low, high) { let sum = 0; const q = [root]; while (q.length) { const node = q.shift(); if (!node) { continue; } if (node.val > high) { q.push(node.left); } else if (node.val < low) { q.push(node.right); } else { sum += node.val; q.push(node.left); q.push(node.right); } } return sum; };
java:
class Solution { public int rangeSumBST(TreeNode root, int low, int high) { int sum = 0; Queue<TreeNode> q = new LinkedList<TreeNode>(); q.offer(root); while (!q.isEmpty()) { TreeNode node = q.poll(); if (node == null) { continue; } if (node.val > high) { q.offer(node.left); } else if (node.val < low) { q.offer(node.right); } else { sum += node.val; q.offer(node.left); q.offer(node.right); } } return sum; } }