动态规划属于是分治算法的一种,而不同的是分治算法在大部分情况下是自顶向下进行求解,将问题分解为各个小问题再合并,而动态规划大部分是定义dp数组,求解小问题后进一步自底向上进行求解大问题。
分治算法步骤:
1>分解:将原问题分解为各个小问题。
2>求解:如果可以简单的进行求解则解出来,否则再次进行1>分解。
3>合并:将各个子问题合并为原问题最终解。
动态规划步骤:
1>状态定义2>状态转移方程3>初始状态4>返回值
动态规划的三个特征:1最优子结构(利用子问题最优解推导出原问题最优解)2无后效性(定义状态转移方程之后,只关心它前面的状态是什么,不考虑它是怎么推导出来的,并且后续的决策对当前状态不会有任何的影响)3重复子问题(不同的决策阶段,可能有重复的状态)
题目会输入一个二叉搜索树的后序遍历数组。
根据二叉搜索树的性质,知道左结点一定小于根结点,右结点一定大于根结点。
根据后续遍历的性质,知道数组的最后一个结点必为根结点。
问题分解:
先取出数组最后一个值,得到根结点的值
循环遍历数组,赋值一个变量i,来寻找数组当中第一个大于根结点的值
根据上面得到的下标i,数组分割为两部分:0,i与i,len-1。由于二叉搜索树的性质,前部分必全部小于根结点的值,后部分必大于根结点的值,利用数组的every方法来判断,如果不满足则return false
递归求解各个小问题:
在递归时永远先写回溯条件,在函数第一行写上判断如果数组元素小于等于一则return true进行回溯。
在判断完左右列表满足后,用&&对左右列表进行一个递归,如果全部满足则最终会return true;
/**
* @param {number[]} postorder
* @return {boolean}
*/
var verifyPostorder = function(postorder) {
var len = postorder.length;
if (len<=1){
return true;
}
var root = postorder[len-1];
var i = 0;
while(i<len-1){
if(postorder[i]<root)i++;
else break;
}
var left = postorder.slice(0,i);
var right = postorder.slice(i,len-1);
if (left.every(function(x){return x<root})&&right.every(function(x){return x>root}))return verifyPostorder(left)&&verifyPostorder(right);
else return false;
};
该题最简单的办法是利用循环来一下一下的减少n的值并且一直执行x*x。
进阶一点可以利用递归来进行求解,并且加上对n奇数偶数的判断(此处运用位运算来判断,如果n为奇数其与1做和运算则必不为0),如果为偶数则将x*x并且n/2(利用x^4 == (x^2)^2 )
其终止条件是n==0 return1;
/**
* @param {number} x
* @param {number} n
* @return {number}
*/
var myPow = function(x, n) {
if (n<0)return 1/myPow(x,-n);
if (n==0) return 1;
if(n&1)return myPow(x,n-1)*x;//位运算与,末尾为0则不是偶数,为1则是偶数
else return myPow(x*x,n/2)
};
后来才知道这其实是一种思想:快速幂
其时间复杂度可以实现对数复杂度(logn)相当于二分查找 1/2 ^ k *n = 1
写成循环的方式将空间复杂度降到1:
/**
* @param {number} x
* @param {number} n
* @return {number}
*/
var myPow = function(x, n) {
if(x==1)return 1;
var res = 1;
if(n<0){
x=1/x;
n=-n;
}
while(n){
if(n&1)res*=x;
x*=x;
n>>>=1;
}
return res;
};
分解问题:
利用二叉树前序遍历和中序遍历的规律(根左右,左根右),可以得出该图:
分解问题:
先获取根结点记录为root
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
var buildTree = function(preorder, inorder) {
if(!preorder.length||!inorder.length)return null;
var root = preorder[0];
var i=0;
for(i;i<inorder.length;i++){
if(inorder[i] == root) break;
}
var left_in_list = inorder.slice(0,i);
var right_in_list = inorder.slice(i+1,inorder.length);
var left_pre_list = preorder.slice(1,i+1);
var right_pre_list = preorder.slice(i+1,preorder.length);
var node = new TreeNode(root);
node.left = buildTree(left_pre_list,left_in_list);
node.right = buildTree(right_pre_list,right_in_list);
return node;
};
寻找在中序遍历当中的root值,将两个数组划分为左右两块。
定义var node = new treenode(root);
对node.left 与right进行定义
合并问题:
在函数开始写上递归回溯条件,当数组为空时return null;
在定义node.left 与 right的时候直接传递数组的左右两块
code: