Divide and Conquer : 分治法

  • 知识点预习:
  1. 分治法: 先让左右子树去解决同样的问题, 然后得到结果之后, 再整合为整棵树的结果。
  2. 遍历法: 通过 前序/ 中序/ 后序 的某种遍历, 游走整棵树, 通过一个全局变量或者传递的参数来记录这个过程中所遇到的点 和 需要计算的结果。
  3. 两种方法的区别: 从实现分治法的角度递归函数, 通常有一个返回值, 遍历法通常没有。
  • pre order
private void traversal( TreeNode root , List result ) {
    if( root == null) {
          return ;
    }  
   result.add( root.val);
    traversal( root.left , result);
    traversal( root.right , result);
}
  • in order
private void traversal( TreeNode root , List result ) {
      if( root == null) {
            return ;
      }
     traversal( root.left , result) ;
    result.add(root.val);
    traversal( root.right , result);
}
  • post order
private void traversal( TreeNode root , List result ) {
      if( root == null) {
            return ;
      } 
      traversal( root.left);
      traversal(root.right);
      result.add(root.val);
}
  1. 二叉树上的分治法
  • 二叉树最大深度
  • 判断平衡二叉树
  • 判断排序二叉树
  1. 用遍历法求 tree 的最大深度 : 遍历法 ; 通常没有 return value
int depth ;
public int maxDepth( TreeNode node) {
      depth = 0;
      travesal( root , 1);
      return depth;
}

private void traversal( TreeNode node , int curDepth) {
      if( node == null) {
            return ;
      }
    depth = depth > curDepth ? depth : curDepth;
    traversal( root.left , curDepth + 1);
    traversal(root.right , curDepth + 1);
}
  1. 用 分治法 通常有 return value
  • 这道题是 max depth 就是 左子树 和 右子树 中较大的depth + 1
public int maxDepth( TreeNode node) {
   return  helper( root);
}

private int helper(TreeNode root ) {
      if(root == null) {
            return 0;
      }
      int left = helper( root.left) + 1 ;
      int right = helper(root.right )+ 1;
      return Math.max(left , right);
}
  • 判断平衡二叉树 : 分治法
  1. 平衡就 return 高度, 不平衡就return 不平衡
  • 一边返回高度一边 判断是否是 平衡二叉树
private int NOT_BALANCED = -1;
public boolean isBalanced( TreeNode root) {
    return  maxDepth(root) != NOT_BALANCED;
}
private int maxDepth( TreeNode root ) {
    if( root == null) {
        return 0;
    }
    int left = maxDepth( root.left);
    int right = maxDepth( root.right);
    if( left == NOT_BALANCED || right == NOT_BALANCED) {
          return NOT_BALANCED;
    }
    if( Math.abs( left - right) > 1) {
          return NOT_BALANCED;
    }
    return Math.max( left , right) + 1;
}
  • 判断二叉搜索树: 遍历法 vs 分治法
    1 . 遍历法: 因为 BST 树的特性, inorder 的遍历法 就是一个 上升序列, 所以可以用 遍历法解决 。 遍历法 没有 return value, 但是有golbal 参数
private TreeNode lastNode;
private boolean isValid;
public boolean isValidBST( TreeNode root) {
      isValid = true;
      lasNode = null;
      inorderTraverse(root);
      return isValid;
}
private void inorderTraverse( TreeNode root) {
      if( root == null ) {
            return ;
      }
      inorderTraverse( root.left);
      if( lastNode != null &&  lastNode.val >= root.val ) {
              isValid = false;
              return ;
       }
      lastNode = root;
      inorderTraverse( root.right);
} 

  1. 分治法 :
    ❌做法: 遍历到每个节点, 然后比较左右孩子
    正确做法: current root 要比 左子树最大的值 ,还要大; 比右子树 的最小值还要小, 所以这道题 要return 2 个结果, min 和 max
class ResultType{
    boolean isValid;
    TreeNode minNode , maxNode;
    public ResultType(boolean default) {
          this.isValid = default;
          this.minNode = null;
          this.maxNode = null
    }
}

public boolean isValid( TreeNode root) {
        return divideConquer(  root).isValid;
}

private ResultType divideConquer( TreeNode root ) {
    if(root == null ) {
          return new ResultType(true);
    }
    ResultType left = divideConquer( root.left);
    ResultType right = divideConquer( root.right);
    // 判断错误
    if( ! left.isValid || !right.isValid) {
          return new ResultType(false);
    }
   if( left.maxNode != null && root.val  <= left.maxNode) {
         return new ResultType(false);
    }
    if( right.minNode != null  && root.val >=right.minNode ) {
          return new ResultType(false);
    }
    // bst
    ResultType res = new ResultType(true);
    res.minNode = left.minNode == null ? root : left.minNode;
    res.maxNode = right.maxNode == null ? root : right.maxNode;
    return res; 
}
  • 知识点: 递归, 回溯 和 搜索

  • 递归 : 1 . 是一种由大化小, 由小化无的解决问题的算法。类似的算法还有动态规划。
    2. 自己调用自己

  • 非递归:迭代法

    1. 什么是搜索(search)
    • 搜索分为 深度优先搜索( DFS)和 宽度优先搜索(BFS); 搜索通常是一种 类似于枚举的算法。
  • 回溯法 就是 深度优先搜索。 回溯是 深度优先搜索过程中的一个步骤。
    例如: 我们在进行全子集问题的搜索时 ,假如 当前的集合 是{1, 2} 代表我正在 寻找以{
    1, 2} 开头的所有集合。 那么下一步 回去寻找 { 1, 2, 3} 开头的所有集合 , 当然当我们找完所有以{1,2,3} 开头的集合时, 我们需要把3 从集合中 删除, 回到{1,2} . 然后 再把 4 放进去, 寻找以{1,2,4} 开头的集合。 这个把 3 删掉回到 {1, 2} 的过程, 就是回溯。

  • bst定义: (BST)

  1. 若左子树 不为空, 则左子树上所有的节点值均小于 或等于它的根结点
  2. 若右子树不为空, 则右子树上所有的节点值均大于根节点值
  3. 左右子树也为二叉搜索树
  • 平衡搜索树

    1. 空树可以是 balanced binary tree ; 2. 左右子树高度差不超过 <=1
  • 特性 :高度为 o(long n)

  • 最大作用: 保证查找的最坏时间复杂度为 o(log n)

** 额外拓展:

  1. morris 算法实现 o(1) 额外空间对二叉树进行 先序遍历
  2. 用非递归(non-recusrion/ iteratioin)的方式实现二叉树的 前序遍历, 中序遍历 和 后序遍历
  3. BST 的增删改查
  4. 平衡排序二叉树(balanced binary search tree)以及 TreeSet / TreeMap 的使用
  • 2 非递归的方式实现二叉树遍历
  1. 先序遍历 preorder
public List preorderTraversal(TreeNode root) {
    Stack stack = new Stack<>();
     List result = new ArrayList<>();
    TreeNode p = root;
    stack.push( root);
    while( !stack.empty()  || p != null ) {
          if( p != null ) {
                stack.push( p);
                result.add( p.val );
                p = p.left;
          } else {
              TreeNode node = stack.pop();
              p = p.right;
          }
    }
    return result;
}
  1. 中序遍历
public List  inorderTraversal(TreeNode root) { 
      Stack stack = new Stack<>();
      List  result  = new ArrayList<>();
      TreeNode p = root;
      while( p != null ||  !stack.isEmpty() ) {
              while( p != null) {
                    stack.push(p );
                    p = p.left;
              }
             TreeNode node  = stack.pop();
             result.add( node.val);
             p = node.right
      }
      return result;
}
  1. 后序遍历
public List postorderTraversal(TreeNode root) { 
    Stack< TreeNode> stack = new Stack<>();
    List result = new LinkedList<>();
    TreeNode p = root ;
    while( p != null || !stack.isEmpty() ) {
        if( p != null) {
                stack.add( p );
                result.add( 0 , p);
                p = p.left;
         } else {
              TreeNode node = stack.pop();
               p = node.right;
          }
    }
return result;
}
  1. morris 算法实现 o(1) 额外空间对二叉树进行 先序遍历
    preorder
public List preorderTraversal(TreeNode root) {
    List nums = new ArrayList<>();
    TreeNode cur = null;
    while( root != null) {
          if( root.left != null) {
              cur = root.left;
          }
          while( cur.right != null && cur.right != root) {
                  cur = cur.right ;  
          }
          if( cur.right == root) { // 撤销钩子
                  cur.right = null;
                  root = root.right;
          } else { // cur.right == null
                 nums.add( root.val);
                 cur.right = root;
                cur = root.left ;
          }
    } else {// root.left == null
            nums.add(root.val);
            root = root.right;
    }
    return nums;
}
  1. inorder
public List inorderTraversal(TreeNode root) { 
       List nums = new ArrayList<>();
       TreeNode cur = null;
        while( root != null) {
                if( root.left != null ) {
                    cur = root.left ;
                    while( cur.right != null && cur.right != root) {
                            cur = cur.right ;
                    } 
                    if( cur.right == root) {
                            nums.add(root);  
                            root = root.right;
                            cur.right = null;
                      } else { // cur.right == null
                              cur.right = root;
                              root = root.left;
                      }
                    
                } else {
                        nums.add(root.val);
                        root = root.right;
                 }
        }    
}
  1. postorder
public List inorderTraversal(TreeNode root) { 
      TreeNode cur = null;
      List nums = new ArrayList<>();
      while( root != null) {
          if( root.right != null ) {
                cur =root.right;
                while( cur.left != null && cur.left != root) {
                        cur = cur.left;
                }
                if( cur.left == root) {
                      cur.left = null;
                      root = root.left;
                } esle {
                      cur.left = root;
                      nums.add(root.val);
                      root = root.right;
                }
          } else {
              nums.add(root.val);
              root = root.left; 
          }
      }
Collections.reverse( nums);
 return nums;
}

BST 的增删改查

  • 在求 BST 的 时间复杂度追求 : O(log N)
  1. closestValue : 找到 pre , suc 然后比较

  2. two sum: 用 pre , suc + 双指针

  3. BST 里面要知道如何求 predecessor 和 successor
    predecessor:

private TreeNode inorderPredecesor (TreeNode root , TreeNode node  ) {
    TreeNode res  = null;
    while( root != null) {
         if( root.val  > node.val) {
                root = root.left;
          } else { // root.val <= node.val 
               res = root;
                root = root.right;
          }
    }
    return res; 
}

successor

private TreeNode inorderSuccessor ( TreeNode root , TreeNode node ) {
    TreeNode res = null;
     while( root != null) {
        if( root.val < node.val ) {
               root = root.right ;
        } else { // root.val >= node.val
              res = root;
              root = root.left ;
      }
    }
    return res;
}

BST 删除

    • http://www.lintcode.com/en/problem/remove-node-in-binary-search-tree/
    • http://www.lintcode.com/en/problem/trim-binary-search-tree/

BST 插入

  • http://www.lintcode.com/en/problem/insert-node-in-a-binary-search-tree/

BST 修改

  • http://www.lintcode.com/en/problem/bst-swapped-nodes/

BST 查找

  • http://www.lintcode.com/en/problem/search-range-in-binary-search-tree/
  • http://www.lintcode.com/en/problem/two-sum-bst-edtion/
  • http://www.lintcode.com/en/problem/closest-binary-search-tree-value/
  • http://www.lintcode.com/en/problem/closest-binary-search-tree-value-ii/
  • http://www.lintcode.com/en/problem/trim-binary-search-tree/
  • http://www.lintcode.com/en/problem/bst-swapped-nodes/

======================================================================

1.Kth Largest Element in an Array(215. leetcode)
public int findKthLargest( int [] nums, int k){
    if( nums == null || nums.length == 0)
        return -1;
    return quickSelect( nums, 0, nums.length - 1, nums.length - k + 1);
}

private int quickSelect(int [] nums , int start , int end , int k ) {
    if(start == end) return nums[ start];
    int mid = start + (end - start )/2 ; 
    int pivot = nums[ mid];
    int i =start , j = end;
    while( i <= j ){
        while( i <= j && nums[ i ] < pivot) i ++;
        while( i <= j && nums[ j] < pivot ) j -- ;
        if( i <= j) {
            int tmp = nums[i];
            nums[ i] = nums[ j];
            nums[ j ] = tmp;
            i ++;
            j -- ;
        } / * if */
    } / * while */
   if ( start + k - 1 <= j ){
        return quickSelect( nums, start, j , k );
   } else if ( start + k - >=i ) {
        return quickSelect( nums, i , end , k - i + start);
   }
   return nums[ j + 1]; 
}
2. Closest Binary Search Tree Value II (google )
  • getStack() : 到达离 target 最近的 node 时, 所经过的点
  • moveUpper( stack) : 根据 stack , 挪动到 next node
  • moveLower( stack) : 根据 stack , 挪动到 prev node
    有了这些函数之后, 就可以把整棵树当做一个数组一样来处理, 只不过每次 i++ 的时候要用
    moveUpper , i -- 的时候要用 moveLower
public List closestKValues(TreeNode root, double target, int k) {
      List< Integer> values = new ArrayList<>();
      if( k == 0 || root == null) {
            return values;
      }      
      Stack lowerStack = getStack();
      Stack upperStack = new Stack<>();
      upperStack.addAll( lowerStack) ;
      if( target < lowerStack.peek().val ) {
            moveLower( lowerStack);
      }  else {
          moveUpper( upperStack);
      }
      for( int i =0 ; i < k ; i++) {
            if(  lowerStack.isEmpty()  || target - lowerStack.peek().val  > upperStack().peek().val - target) {    values.add(upperStack.peek().val )
                  moveUpper( upperStack);
            } else {
                  values.add( lowerStack.peek().val );
                  moveLower( lowerStack);
            }
      }
    return values;
}

private Stack  getStack( TreeNode root , double target ) {
      Stack res = new Stack <>();
      while( root != null) {
             res.push( root );
             if( root.val >  target) {
                  root = root.left ;
              } else { // root.val <= target
                     root = root.right;
              }
      }
  return res;
}

private void moveUpper( Stack stack) {
       TreeNode node = stack.peek();
        if( node.right == null) {
              node = stack.pop();
              while( !stack.isEmpty() || stack.peek().right == node ) {
                      node = stack.pop();
              }
              return ;
        }
        node = node.right;
        while( node != null) {
              stack.add(node);
              node = node.left;
        }
}

private void moveLower( Stack stack ) {
    TreeNode node = stack.peek();
    if( node.left  == null ) {
          node = stack.pop();
          while( stack.isEmpty() ||  stack.peek().left != node ) {
                    node = stack.pop() ;
          }
          return;
    }
    // node.left != null 说明有prev node
    node = node.left;
    while( node != null) {
        stack.push( node);
        node = node.right;
    }
}

你可能感兴趣的:(Divide and Conquer : 分治法)