程序员面试金典题解

3.栈与队列

3.4 经典汉诺塔问题,递归实现

public class Num3_4 {
    public static void main(String[] args) {
        
    }
    /*
     * 汉诺塔问题
     * 当盘子只有一个时,则直接从柱子A移动到柱子C
     * 当盘子多余一个时,将盘子分为两部分,前n-1个和第n个,分三步进行
     * 第一步将前n-1个盘子从柱子A移动到柱子B
     * 第二步将第n个盘子从柱子A移动到柱子C
     * 第三步将前n-1个盘子从柱子B移动到柱子C
     */
    public static void TowerOfHanoi(int n, char A, char B,char C) {
        if (n < 1)
            return;
        TowerOfHanoi(n-1, A, C, B);
        System.out.println(A + "->" + C);
        TowerOfHanoi(n-1, B, A, C);
    }
}
View Code

3.5 两个栈实现一个队列的两种解法

import java.util.Stack;

/*
 * 两个栈实现一个队列
 */
public class Num3_5 {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
/*    
    public void push(int node) {
        int size = stack1.size();
        for (int i = 0; i < size; i++) {
            stack2.push(stack1.pop());
        }
        stack1.push(node);
        for (int i = 0; i < size; i++) {
            stack1.push(stack2.pop());
        }
    }
    
    public int pop() {
        return stack1.pop();
    }
*/
    /*
     * 更好的方法,不用每次pop()操作都要移动元素
     * stack1最顶元素为最新,stack2最顶元素为最旧元素
     * 如果stack2不为空,则直接从stack2执行pop(),反之,将stack1的元素按序压入stack2,并pop()栈底元素
     * push()则直接在stack1上操作
     */
    public void push(int node) {
        stack1.push(node);
    }
    public int pop() {
        if (!stack2.isEmpty())
        {
            return stack2.pop();
        } else {
            int size = stack1.size();
            for (int i = 0; i < size - 1; i++) {
                stack2.push(stack1.pop());
            }
            return stack1.pop();
        }
    }
    public int peek() {
        if (!stack2.isEmpty())
        {
            return stack2.peek();
        } else {
            int size = stack1.size();
            for (int i = 0; i < size - 1; i++) {
                stack2.push(stack1.pop());
            }
            return stack1.peek();
        }
    }
}
View Code

 4.树与图

4.1 

实现一个函数,检查二叉树是否平衡,平衡的定义如下,对于树中的任意一个结点,其两颗子树的高度差不超过1。

给定指向树根结点的指针TreeNode* root,请返回一个bool,代表这棵树是否平衡。

public class Num4_1 {
/*
 * 时间复杂度为N*logN,N为树的节点数
    public boolean isBalance(TreeNode root) {
        // write code here
        if (root == null || (root.left == null && root.right == null))
            return true;
        if (Math.abs((getDepth(root.left) - getDepth(root.right))) > 1)
            return false;
        else
            return isBalance(root.left) && isBalance(root.right);        
    }
    //计算当前节点的高度
    public int getDepth(TreeNode node) {
        if (node == null)
            return 0;
        else 
            return Math.max(getDepth(node.left),getDepth(node.right)) + 1; 
    }
*/
    /*
     * 减少getDepth的调用次数,在计算高度的同时判断是否为平衡二叉树
     * 时间复杂度为O(N),还不是最优
     */
/*    
    boolean balance = true;
    public boolean isBalance(TreeNode root) {
        // write code here
        if (root == null || (root.left == null && root.right == null))
            return true;
        getDepth(root);
        return balance;        
    }
    public int getDepth(TreeNode node) {
        if (node == null)
            return 0;
        else {
            int leftDepth = getDepth(node.left);
            int rightDepth = getDepth(node.right);
            if (Math.abs(leftDepth - rightDepth) > 1)
                balance = false;
            return Math.max(leftDepth, rightDepth) + 1; 
        }
            
    }
*/    
    /*
     * 最优解法,当发现子树不是平衡二叉树时,立即中断getDepth递归
     * 时间复杂度是O(N),但是平均时间复杂度要比上一种解法更优
     */
    public boolean isBalance(TreeNode root) {
        // write code here
        if (getDepth(root) == -1) {
            return false;
        } else
            return true;    
    }
    
    public int getDepth(TreeNode node) {
        if (node == null)
            return 0;
        else {
            int leftDepth = getDepth(node.left);
            if (leftDepth == -1)
                return -1;
            int rightDepth = getDepth(node.right);
            if (rightDepth == -1)
                return -1;            
            if (Math.abs(leftDepth - rightDepth) > 1) {
                return -1;
            } else {
                return Math.max(leftDepth, rightDepth) + 1;
            }  
        }            
    }
}
View Code

 4.2

对于一个有向图,请实现一个算法,找出两点之间是否存在一条路径。

给定图中的两个结点的指针UndirectedGraphNode*a,UndirectedGraphNode* b(请不要在意数据类型,图是有向图),请返回一个bool,代表两点之间是否存在一条路径(a到b或b到a)。

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
/*
import java.util.ArrayList;

public class UndirectedGraphNode {
    int label = 0;
    UndirectedGraphNode left = null;
    UndirectedGraphNode right = null;
    ArrayList<UndirectedGraphNode> neighbors = new ArrayList<UndirectedGraphNode>();

    public UndirectedGraphNode(int label) {
        this.label = label;
    }
}

*/
/*
 * 结果显示DFS比BFS稍微快一点
 * 注意两个方向都要遍历
 */
public class Num4_2 {
    Set<UndirectedGraphNode> set = new HashSet<UndirectedGraphNode>();  
    public boolean checkPath(UndirectedGraphNode a, UndirectedGraphNode b) {
        // write code here
        //BFS
        //return check3(a, b) || check3(b, a);
        //DFS
        if (check(a, b))
            return true;
        else {
            set.clear();
            return check(b, a);
        }           
    }
    //DFS
    public boolean check(UndirectedGraphNode a, UndirectedGraphNode b){  
        if (a == b)
            return true;
        set.add(a);       
        for (UndirectedGraphNode u : a.neighbors) {
            if (!set.contains(u)) {           
                if (check(u, b))
                    return true;               
            }
        }           
        return false;       
    }     
    //广度优先遍历BFS
    public boolean check3(UndirectedGraphNode a, UndirectedGraphNode b){
        Set<UndirectedGraphNode> set3 = new HashSet<UndirectedGraphNode>();
        Queue<UndirectedGraphNode> qu = new LinkedList<UndirectedGraphNode>();
        qu.add(a);
        while (!qu.isEmpty()) {
            UndirectedGraphNode u = qu.poll();         
            if (u == b)
                return true;
            set3.add(u);           
            for (UndirectedGraphNode uu : u.neighbors) {
                if (!set3.contains(uu)) {
                    qu.add(uu);
                }
            }      
        }
        return false;
    }
}
View Code

 4.3

对于一个元素各不相同且按升序排列的有序序列,请编写一个算法,创建一棵高度最小的二叉查找树。

给定一个有序序列int[] vals,请返回创建的二叉查找树的高度。

注:题意描述与书本原题是有出入的。原题是重在创建这颗二叉树,而本题只要返回高度即可,并不用创建二叉树。在代码中也给出创建二叉树的部分。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Num4_3 {
    /*
     * 对于一个元素各不相同且按升序排列的有序序列,请编写一个算法,创建一棵高度最小的二叉查找树。
     * 并返回树高度
     */
    public int buildMinimalBST(int[] vals) {
        // write code here
        build(vals, 0, vals.length - 1);
        //树高度等于log2(n+1)取上整 ,用到换底公式 log2(n+1) = loge(n+1)/loge(2)
        return (int)Math.ceil(Math.log(vals.length + 1) / Math.log(2));
    }
    public TreeNode build(int[] vals, int a, int b) {
        if (a > b) {            
            return null;
        }
        TreeNode t = new TreeNode(vals[(a + b)/2]);
        t.left = build(vals, a, (a + b) / 2 - 1);
        t.right = build(vals, (a + b) / 2 + 1, b);
        return t;
    }
}
View Code

 4.4

对于一棵二叉树,请设计一个算法,创建含有某一深度上所有结点的链表。

给定二叉树的根结点指针TreeNode* root,以及链表上结点的深度,请返回一个链表ListNode,代表该深度上所有结点的值,请按树上从左往右的顺序链接,保证深度不超过树的高度,树上结点的值为非负整数且不超过100000。

注:书中的返回是前dep层的所有节点构成的链表集合,每一层一个链表,这里返回的是第dep层的节点构成的链表

public class Num4_4 {
    ListNode ans = null;
    ListNode last = null;
    public ListNode getTreeLevel(TreeNode root, int dep) {
        // write code here
        //前两种方法都是广度优先遍历,第一种判断每层结束的方法比较复杂,是根据上一层空节点的个数,来pandaun下一层总结点数
        //在遍历的时候再计数,如果每一层节点计数大于了该层节点数,则层数增一
/*
        Queue<TreeNode> qt = new LinkedList<TreeNode>();
        ListNode start = null;
        ListNode last = null;
        qt.add(root);
        int count = 0;
        int depth = 1;
        int nCount = 0;
        while (!qt.isEmpty()) {  
            count++;          
            if (count > (Math.pow(2, depth - 1) - 2 * nCount)) {
                depth++;
                if (depth > dep)
                    return start;
                count = 1;
                nCount = 0;
            }                
            TreeNode temp = qt.poll();
            if (temp != null) {
                if (depth == dep) {
                    ListNode l = new ListNode(temp.val);
                    if (start == null)
                        start = l;
                    else
                        last.next = l;
                    last = l;
                }
                qt.add(temp.left);
                qt.add(temp.right);
            } else {
                nCount++;
            }
        }
        return start;
*/
        /*
        对每一层都新建一个队列,把该层所有节点添加进去
        直到到达指定层
        */
/*        
        Queue<TreeNode> current = new LinkedList<TreeNode>();
        ListNode ans = null;
        ListNode last = null;
        current.add(root); 
        int depth = 1;
        while (!current.isEmpty()) {
            if (depth == dep)
                break;
            Queue<TreeNode> parents = current;
            current = new LinkedList<TreeNode>();
            for (TreeNode parent : parents) {
                if (parent.left != null)
                    current.add(parent.left);
                if (parent.right != null)
                    current.add(parent.right);
            } 
            depth++;
        }      
        for (TreeNode cur : current) {
            ListNode l = new ListNode(cur.val);
            if (ans == null)
                ans = l;
            else
                last.next = l;
            last = l;
        }
        return ans;
*/       
        get(root, dep, 1);
        return ans;
    }
    //深度优先遍历
    public void get(TreeNode root, int dep, int currentDepth) {
        if (root == null)
            return ;
        if (currentDepth == dep) {
            ListNode l = new ListNode(root.val);
            if (ans == null)
                ans = l;
            else
                last.next = l;
            last = l;
            return ;
        }
        get(root.left, dep, currentDepth + 1);
        get(root.right, dep, currentDepth + 1);
    }
}
View Code

 4.5

请实现一个函数,检查一棵二叉树是否为二叉查找树。

给定树的根结点指针TreeNode* root,请返回一个bool,代表该树是否为二叉查找树。

public class Num4_5 {
/*
    //结果显示第一种解法较好,但是其实复杂度是相当的
    int pre = Integer.MIN_VALUE;
    public boolean checkBST(TreeNode root) {
        // wurite code here
        //中序遍历
        if (root == null)
            return true;
        if (!checkBST(root.left))
            return false;
        if (root.val < pre)
            return false;
        pre = root.val;
        return checkBST(root.right);
    }
*/
    //最小最大值法(我称为夹逼法)
    public boolean checkBST(TreeNode root) {
        return check(root, Integer.MIN_VALUE, Integer.MAX_VALUE);
    }
    public boolean check(TreeNode root, int min, int max) {
        if (root == null)
            return true;
        if (root.val < min || root.val > max) {
            return false;
        }        
        if (!check(root.left, min, root.val))
            return false;
        return check(root.right, root.val, max);
    }
}
View Code

 4.6

请设计一个算法,寻找二叉树中指定结点的下一个结点(即中序遍历的后继)。

给定树的根结点指针TreeNode* root和结点的值int p,请返回值为p的结点的后继结点的值。保证结点的值大于等于零小于等于100000且没有重复值,若不存在后继返回-1。

import java.util.ArrayList;
import java.util.List;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    TreeNode parent = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
 */
public class Num4_6 {
/*    
 * 不使用父节点的办法,给出根节点,返回下一节点的值,没有后继结点则返回-1
 * 利用中序遍历递归找到该节点,并在递归时几下当前节点的递归次数,则下一次就是后继节点
    List<Integer> vals = new ArrayList<Integer>();
    static int count = 0;
    static int pos = -2;
    
    public static int findSucc(TreeNode root, int p) {
        // write code here
        if (root == null)
            return -1;
        int temp = findSucc(root.left, p);
        if (temp == -1) {
            count++;
            if (count == pos + 1)
                return root.val;            
            if (root.val == p)
                pos = count;
            return findSucc(root.right, p);  
        } 
        return temp;              
    }
*/    
    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        TreeNode r1 = new TreeNode(2);
        TreeNode r2 = new TreeNode(3);
        TreeNode r3 = new TreeNode(4);
        TreeNode r4 = new TreeNode(5);
        TreeNode r5 = new TreeNode(6);
        TreeNode r6 = new TreeNode(7);
        root.left = r1;
        root.right = r2;
        r1.left = r3;
        r1.right = r4;
        r4.right = r5;
        r2.right = r6;
//        System.out.println(findSucc(root, 3));
    }
    /* 可以连接到父节点的做法,给出当前节点,返回下一节点(书上给出的做法)
     * 若当前节点有右子树,则返回右子树的最左节点
     * 若当前节点没有右子树,则回溯到父节点,直到当前节点不再是右子节点,返回当前节点的父节点
     * 如果一直找不到一个父节点使当前节点为左子节点,说明已到最右节点,没有后继结点,返回null
     */
    public static TreeNode findSucc(TreeNode node) {
        if(node == null)
            return null;
        if(node.right != null) {
            TreeNode temp = node.right;
            while (temp.left != null) {
                temp = temp.left;
            }
            return temp;
        }
        TreeNode parent = node.parent;
        while (parent != null && node == parent.right) {
            node = parent;
            parent = node.parent;
        }
        return parent;    
    }
    
}
View Code

4.7

 有一棵无穷大的满二叉树,其结点按根结点一层一层地从左往右依次编号,根结点编号为1。现在有两个结点a,b。请设计一个算法,求出a和b点的最近公共祖先的编号。

给定两个int a,b。为给定结点的编号。请返回ab的最近公共祖先的编号。注意这里结点本身也可认为是其祖先。

测试样例:
2,3
返回:1
import java.util.*;

public class LCA {
    public int getLCA(int a, int b) {
        // write code here
        while (a != b) {
            if (a > b)
                a = a / 2;
            else
                b = b / 2;
        }
        return a;
    }
}
View Code

 当一般情况时,任意二叉树的任意节点,返回最近公共祖先

//二叉树中两个节点的最近共同祖先
public class Num4_7 {
/*
 * 第一种情况满二叉树,并且每个节点的值是按照层序遍历1,2,3,4。。。依次排列,给出两个节点值,求共同祖先的节点值
 */
    public int findCommonAncestor(int a, int b) {
        while (a != b) {
            if (a > b)
                a /= 2;
            if (b > a)
                b /= 2;
        }
        return a;
    } 
    /*
     * 第二种一般情况下的二叉树,给出根节点和任意两个节点,返回祖先节点
     */
    public TreeNode find(TreeNode root, TreeNode a, TreeNode b) {
        //节点不在树中
        if (!isCoverNode(root, a) || !isCoverNode(root, b))
            return null;
        return findCommonAncestor(root, a, b);
    }
    public TreeNode findCommonAncestor(TreeNode root, TreeNode a, TreeNode b) {
        if (root == null)
            return null;
        if (root == a || root == b)
            return root;
        boolean aCoverLeft = isCoverNode(root.left, a);
        boolean bCoverleft = isCoverNode(root.left, b);
        //不在同一子树
        if (aCoverLeft != bCoverleft) {
            return root;
        } 
        //在同一子树,则继续遍历那棵子树
        return findCommonAncestor(aCoverLeft == false ? root.right : root.left, a, b);

    }
    //判断节点是否在以root为根节点所在的子树
    public boolean isCoverNode(TreeNode root, TreeNode node) {
        if (root == null)
            return false;
        if (root == node)
            return true;
        return isCoverNode(root.left, node) || isCoverNode(root.right, node);
    }
    public static void main(String[] args) {
        Num4_7 num4_7 = new Num4_7();
        TreeNode root = new TreeNode(1);
        TreeNode r1 = new TreeNode(2);
        TreeNode r2 = new TreeNode(3);
        TreeNode r3 = new TreeNode(4);
        TreeNode r4 = new TreeNode(5);
        TreeNode r5 = new TreeNode(6);
        TreeNode r6 = new TreeNode(7);
        TreeNode r7 = new TreeNode(7);
        root.left = r1;
        root.right = r2;
        r1.left = r3;
        r1.right = r4;
        r4.right = r5;
        r2.right = r6;
        TreeNode t = num4_7.find(root, root, r5);
        if (t != null)
            System.out.println(t.val);
        else
            System.out.println("null");
    }
}
View Code

一般情况的算法的时间复杂度为O(n),因为在检查节点是否在树中的时候isCoverNode()执行了2n次,左边n次,右边n次。然后在判断节点在哪一边时,第一次是2*n/2,第二次2*n/4,以此类推,相加后求和渐进于O(4n),也就是O(n);

4.8

判断一棵二叉树是否为另一棵二叉树的子树

解法一:用两个字符串分别来表示树的前序遍历和中序遍历,如果树A的中序遍历是树B的中序遍历的字串,树A的前序遍历是树B的前序遍历的字串,则树A是树B的子树,注意要用特殊字符来表示空节点。此时空间复杂度为O(n+m),时间复杂度为O(n+m)(忽略常数系数,n和m分别为两棵树的节点数),如果树的节点数较多时,这个方法就不太适用,所占用内存会过大。

解法二:遍历搜索较大的那棵树,如果找到某个节点与另棵树相同,则从此节点开始对比余下节点是否都相同,每遇到与另一棵树根据诶点相同的节点都要搜索一次,直到判断出是否为子树。使用递归来遍历,所以空间复杂度为O(log(n)+log(m)),最坏时间复杂度为O(nm)。但是假设相同的节点只会出现k次,时间复杂度就变为O(n+km),所以这个解法总体来说比较好。

public class Num4_8 {
    public static boolean isSubTree(TreeNode rootA, TreeNode rootB) {
        if (rootB == null)
            return true;
        if (rootA == null)
            return false;
        if (rootA.val == rootB.val) {
            if (isMatch(rootA, rootB))
                return true;
        }
        return isSubTree(rootA.left, rootB) || isSubTree(rootA.right, rootB);    
    }
    public static boolean isMatch(TreeNode nodeA, TreeNode nodeB) {
        if (nodeA == null && nodeB == null)
            return true;
        if (nodeA == null || nodeB == null || nodeA.val != nodeB.val) {
            return false;
        } 
        return isMatch(nodeA.left, nodeB.left) && isMatch(nodeA.right, nodeB.right);
    }
}
View Code

 4.9

给定一棵二叉树和一个定值,找出二叉树中节点和等于这个定值的所有路径。

import java.util.ArrayList;
/*
 * 起始节点和结束节点为树种的任意节点,只要构成路径即可
 * 如果没有规定节点值都为正整数,则在找到满足的路径之后还要继续往下遍历,直到叶子节点
 * 如果值都为正整数,则找到之后就可以结束当前路径的遍历
 * 如果规定路径必须从根节点到叶子节点,更加简化了题目,把findhelper的递归注释即可,见注释部分
 */
public class Num4_9 {
    ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        findHelper(root, target);
        return ans;        
    }
    public void findHelper(TreeNode root, int target) {
        if (root == null)
            return ;
        //值都为正整数
//        find(root, target, 0, new ArrayList<Integer>());
        //值为正负零三种
        find2(root, target, 0, new ArrayList<Integer>());
        findHelper(root.left, target);
        findHelper(root.right, target);
    }
    //如果规定为正整数值
    public void find(TreeNode node, int target, int sum, ArrayList<Integer> path) {
        if (node == null)
            return ;
        int curSum = sum + node.val;
        if (curSum <= target) {
            path.add(node.val);
            if (curSum == target) {
                ans.add(path);
            } else {
                find(node.left, target, curSum, new ArrayList<Integer>(path));
                find(node.right, target, curSum, path);
            }                
        }
    }
    //如果节点值可能为零或负数
    public void find2(TreeNode node, int target, int sum, ArrayList<Integer> path) {
        if (node == null)
            return ;
        int curSum = sum + node.val;
        path.add(node.val);
        if (curSum == target) {
            ans.add(path);
            //注意new的使用,两边都要new
            find2(node.left, target, curSum, new ArrayList<Integer>(path));
            find2(node.right, target, curSum, new ArrayList<Integer>(path));
        } else {
            //有一边用new就行,而且这两句不能调换顺序
            find2(node.left, target, curSum, new ArrayList<Integer>(path));
            find2(node.right, target, curSum, path); 
        }           
    }
    /*
     * 规定路径必须从根节点到叶子节点,更加简化了题目
    ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        findHelper(root, target);
        return ans;        
    }
    public void findHelper(TreeNode root, int target) {
        if (root == null)
            return ;
        find(root, target, 0, new ArrayList<Integer>());
        
//        findHelper(root.left, target);
//        findHelper(root.right, target);
    }
    public void find(TreeNode node, int target, int sum, ArrayList<Integer> path) {
        if (node == null)
            return ;
        int curSum = sum + node.val;
        if (curSum <= target) {
            path.add(node.val);
            if (curSum == target && node.left == null && node.right == null) {
                ans.add(path);
            } else {
                find(node.left, target, curSum, new ArrayList<Integer>(path));
                find(node.right, target, curSum, path);
            }                
        }
    }
     */
    public static void main(String[] args) {
        Num4_9 num4_9 = new Num4_9();
        TreeNode root = new TreeNode(1);
        TreeNode r1 = new TreeNode(2);
        TreeNode r2 = new TreeNode(3);
        TreeNode r3 = new TreeNode(4);
        TreeNode r4 = new TreeNode(-1);
        TreeNode r5 = new TreeNode(0);
        TreeNode r6 = new TreeNode(7);
        TreeNode r7 = new TreeNode(7);
        root.left = r1;
        root.right = r2;
        r1.left = r3;
        r1.right = r4;
        r4.right = r5;
        r2.right = r6;
        ArrayList<ArrayList<Integer>> ret = num4_9.FindPath(root, 2);
        
    }
}
View Code

 9.8硬币面值组合问题

分析转自 http://www.cnblogs.com/python27/p/3303721.html

  给定一个数值sum,假设我们有m种不同类型的硬币{V1, V2, ..., Vm},如果要组合成sum,那么我们有

sum = x1 * V1 + x2 * V2 + ... + xm * Vm 

求所有可能的组合数,就是求满足前面等值的系数{x1, x2, ..., xm}的所有可能个数。

  [思路1] 当然我们可以采用暴力枚举,各个系数可能的取值无非是x1 = {0, 1, ..., sum / V1}, x2 = {0, 1, ..., sum/ V2}等等。这对于硬币种类数较小的题目还是可以应付的,比如华为和创新工厂的题目,但是复杂度也很高O(sum/V1 * sum/V2 * sum/V3 * ...)

  [思路2] 从上面的分析中我们也可以这么考虑,我们希望用m种硬币构成sum,根据最后一个硬币Vm的系数的取值为无非有这么几种情况,xm分别取{0, 1, 2, ..., sum/Vm},换句话说,上面分析中的等式和下面的几个等式的联合是等价的。

sum = x1 * V1 + x2 * V2 + ... + 0 * Vm

sum = x1 * V1 + x2 * V2 + ... + 1 * Vm

sum = x1 * V1 + x2 * V2 + ... + 2 * Vm

...

sum = x1 * V1 + x2 * V2 + ... + K * Vm  

  其中K是该xm能取的最大数值K = sum / Vm。可是这又有什么用呢?不要急,我们先进行如下变量的定义:

dp[i][sum] = 用前i种硬币构成sum 的所有组合数。

  那么题目的问题实际上就是求dp[m][sum],即用前m种硬币(所有硬币)构成sum的所有组合数。在上面的联合等式中:当xn=0时,有多少种组合呢? 实际上就是前i-1种硬币组合sum,有dp[i-1][sum]种! xn = 1 时呢,有多少种组合? 实际上是用前i-1种硬币组合成(sum - Vm)的组合数,有dp[i-1][sum -Vm]种; xn =2呢, dp[i-1][sum - 2 * Vm]种,等等。所有的这些情况加起来就是我们的dp[i][sum]。所以:

dp[i][sum] = dp[i-1][sum - 0*Vm] + dp[i-1][sum - 1*Vm]

+ dp[i-1][sum - 2*Vm] + ... + dp[i-1][sum - K*Vm]; 其中K = sum / Vm

  换一种更抽象的数学描述就是:

递归公式

  通过此公式,我们可以看到问题被一步步缩小,那么初始情况是什么呢?如果sum=0,那么无论有前多少种来组合0,只有一种可能,就是各个系数都等于0;

dp[i][0] = 1   // i = 0, 1, 2, ... , m

  如果我们用二位数组表示dp[i][sum], 我们发现第i行的值全部依赖与i-1行的值,所以我们可以逐行求解该数组。如果前0种硬币要组成sum,我们规定为dp[0][sum] = 0. 

将二维数组转变成一维数组

    public int makeChange(int n) {
        int coins[] = {1,5,10,25};
        int dp[] = new int[n + 1];       
        dp[0] = 1;
        for (int i = 0 ;i < 4; ++i) {
            for (int j = coins[i]; j <= n; ++j) {
                dp[j] = (dp[j] + dp[j - coins[i]]) % 1000000007;               
            }
        }
        return dp[n];
    }
View Code

 9.9 n皇后问题

public class Num9_9 {
    //递归算法,效率比较差
    /*
     * 从第一行开始,判断在每一列拜访皇后是否合法,若合法则摆放,并到下一行摆放,不合法则到下一列
     * 判断是否合法主要是与前面已经摆放过的皇后进行比较,看是否同一列或者同对角线
     * 判断对角线相同的原则是行数差和列数差的绝对值相等,不用判断是否在同一行,因为在摆放的时候是逐行摆放的
     */
    public int nQueens(int n) {
        // write code here
        int[] way = new int[n];
        return placeQueens(n, 0, way);
    }
    public int placeQueens(int size, int row, int[] way) {
        if (row == size)
            return 1;
        int count = 0;
        for (int i = 0; i < size ; i++) {
            if (checkValid(row, i, way)) {
                way[row] = i;
                count += placeQueens(size, row + 1, way);
            }
        }
        return count;
    }
    public boolean checkValid(int row, int column, int[] way) {
        for (int i = 0; i < row; i++) {
            if (column == way[i] || Math.abs(column - way[i]) == row - i)
                return false;                       
        }        
        return true;
    }
}
View Code

 9.10 堆箱子

import java.util.HashMap;
import java.util.Map;

public class Num9_10 {
    public int getHeight(int[] w, int[] l, int[] h, int n) {
        // write code here
        //这里一定要循环调用
        int maxHeight = 0;
        for (int i = 0; i < n; i++) {
            int newHeight = getDp(w, l, h, i, n);
            if (newHeight > maxHeight)
                maxHeight = newHeight;                
        }
        return maxHeight;     
    }
    /*
     * 递归算法
     */
    public int get(int[] w, int[] l, int[] h, int bottom, int n) {
        int maxHeight = 0;
        for (int j = 0; j < n; j++) {
            if (canBeAbove(w, l, bottom, j)) {
                int newHeight = get(w, l, h, j, n);
                if (newHeight > maxHeight) {
                    maxHeight = newHeight;
                }
            }
        }
        if (bottom < n) {
            maxHeight += h[bottom];
        }
        return maxHeight;
    }
    /*
     * 动态规划
     */
    //保存以编号bottom为底时的最高堆高度
    Map<Integer, Integer> bottoms = new HashMap<Integer, Integer>();
    public int getDp(int[] w, int[] l, int[] h, int bottom, int n) {
        if (bottoms.containsKey(bottom)) {            
            return bottoms.get(bottom);
        }
        int maxHeight = 0;
        for (int j = 0; j < n; j++) {
            if (canBeAbove(w, l, bottom, j)) {
                int newHeight = getDp(w, l, h, j, n);
                if (newHeight > maxHeight) {
                    maxHeight = newHeight;
                }
            }
        }
        if (bottom < n) {
            maxHeight += h[bottom];
            bottoms.put(bottom, maxHeight);
        }
        return maxHeight;
    }
    public boolean canBeAbove(int[] w, int[] l, int bottom, int j) {
        if (w[j] < w[bottom] && l[j] < l[bottom])
            return true;
        return false;
    }    
}
View Code

 11.2

一开始没有看清题意,在线解题和书本的题意不同

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

public class Num11_2 {
    public ArrayList<String> sortStrings(String[] str, int n) {
        // write code here
        Arrays.sort(str);
        ArrayList<String> ans = new ArrayList<String>();
        for (int i = 0; i < n; i++) {
            boolean insertOk = true;
//            //变位词只保留字典序最前的一个
//            for (int j = 0; j < ans.size(); j++) {
//                if (str[i].length() == ans.get(j).length()) {
//                    char[] a = ans.get(j).toCharArray();
//                    char[] b = str[i].toCharArray();
//                    Arrays.sort(a);
//                    Arrays.sort(b);
//                    String aa = String.valueOf(a);
//                    String bb = String.valueOf(b);
//                    if (aa.equals(bb)) {                        
//                        insertOk = false;
//                        break;
//                    }                        
//                }
//            }
//            if (insertOk)
//                ans.add(str[i]);
            //变位词排在相邻位置
            for (int j = ans.size() - 1; j >= 0; j--) {
                if (str[i].length() == ans.get(j).length()) {
                    char[] a = ans.get(j).toCharArray();
                    char[] b = str[i].toCharArray();
                    Arrays.sort(a);
                    Arrays.sort(b);
                    String aa = String.valueOf(a);
                    String bb = String.valueOf(b);
                    if (aa.equals(bb)) {  
                        ans.add(j + 1, str[i]);
                        insertOk = false;
                        break;
                    }                        
                }                
            }
            if (insertOk)
                ans.add(str[i]);
        }  
        return ans;
    }    
    //只考虑变位词拍在相邻位置而不考虑其他顺序
    /*
     * 方法一:重写排序函数
     */
    class MyComparator implements Comparator<String> {
        public String sortStr(String str) {
            char[] array = str.toCharArray();
            Arrays.sort(array);
            return new String(array);
        }
        @Override
        public int compare(String o1, String o2) {
            // TODO Auto-generated method stub
            return sortStr(o1).compareTo(sortStr(o2));
        }
    }
    public String[] simpleSort(String[] str) {
        Arrays.sort(str, new MyComparator());
        return str;        
    }
    /*
     * 方法二:利用散列表将变位词分组
     */
    public ArrayList<String> hashSort(String[] str) {
        Map<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();
        //将变位词分组
        for (String s : str) {
            char[] arr = s.toCharArray();
            Arrays.sort(arr);
            String ss = new String(arr);
            if (!hm.containsKey(ss))
                hm.put(ss, new ArrayList<String>());
            ArrayList<String> al = hm.get(ss);
            al.add(s);
        }
        //将hashmap转为数组/ArrayList
        ArrayList<String> ans = new ArrayList<String>();
        int index = 0;
        for(String key : hm.keySet()) {
            ArrayList<String> al = hm.get(key);
            for (String s : al) {
                ans.add(s);
            }
        }
        return ans;
    }
    public static void main(String[] args) {
        Num11_2 n112 = new Num11_2();
        String[] str = {"dad","add","abc","ab","cba","ba"};
        n112.simpleSort(str);
    }
}
View Code

 11.3

public class Num11_3 {
    /*
     * 二分法的变形
     * 具体解释看书P257吧,懒得码了
     * 需要注意的是第三中情况也就是中间的数等于左右两头的数的时候,两边都要搜索了
     * 如果左边搜索得不到结果就要搜索右边,如果得到结果就直接返回
     * 所有元素不同的话时间复杂度为O(log(n)),很多元素重复的话,会有很多第三种情况出现,就会接近于O(n)
     */
    public int findElement(int[] A, int n, int x) {
        // write code here
        return find(A, 0, n - 1,x);
    }
    public int find(int[] arr, int left, int right, int x) {
        if (left <= right) {
            int mid = (left + right) / 2;
            if (x == arr[mid])
                return mid;
            if (arr[left] < arr[mid]) {
                if (x >= arr[left] && x <= arr[mid]) {
                    return find(arr, left, mid, x);
                } 
                return find(arr, mid + 1, right, x);
            }
            if (arr[mid + 1] < arr[right]) {
                if (x >= arr[mid + 1] && x <= arr[right]) {
                    return find(arr, mid + 1, right, x);
                } 
                return find(arr, left, mid, x);                
            }
            //前面两个条件都不满足时
            int ans = find(arr, left, mid, x);
            if (ans == -1)
                return find(arr, mid + 1, right, x); 
            return ans;
        }
        return -1;
    }
}
View Code

 11.6

public class Num11_6 {
    public int[] findElement(int[][] mat, int n, int m, int x) {
        // write code here
        return find(mat, 0, 0, mat[0].length - 1, x);
    }
    /*
     * 方法一:对每一行进行二分查找
     */
    public int[] find(int[][] mat, int row, int left, int right, int x) {
        if (row < mat.length) {
            if (left <= right) {
                int mid = (left + right) / 2;
                if (mat[row][mid] == x) {
                    int[] ans = {row, mid};
                    return ans;
                }
                if (mat[row][mid] > x) {
                    if (mid == 0)
                        return null;
                    return find(mat, row, left, mid - 1, x);
                }
                return find(mat, row, mid + 1, right, x);
            }
            return find(mat, row + 1, 0, mat[0].length - 1, x);
        }
        return null;
    }
    /*
     * 方法二:利用以下四个原则进行查找
     * 1.列首元素如果大于X,则往该列的左一列查找
     * 2.列尾元素如果小于X,则往该列的右一列查找
     * 3.行首元素如果大于X,则往该行的上一行查找
     * 4.行尾元素如果小于X,则往该行的下一行查找
     * 我们可以从矩阵的任意位置开始查找,这里从最右上方的元素开始
     */
    public int[] find(int[][] mat, int x) {
        int row = 0;
        int col = mat[0].length - 1;
        while (row <= mat.length && col >= 0) {
            if (mat[row][col] == x) {
                int[] ans = {row, col};
                return ans;
            }                
            if (mat[row][col] > x) {
                col--;
            } else
                row++;
        }
        return null;
    }
    /*
     * 方法三:对对角线上的元素进行二分查找,把矩阵分为四个区域,进行递归查找
     * 具体解释参照书本P262
     */
    public int[] findDiv(int[][] mat, int topRow, int topCol, int botRow, int botCol, int x) {
        if (topRow >= 0 && topCol >= 0 && botRow <= mat.length && botCol <= mat[0].length && topRow <= botRow && topCol <= botCol) {
            
            //某个子矩阵只有一个元素时
            if (topRow == botRow && topCol == botCol) {
                if (mat[topRow][topCol] == x) {
                    int[] ans = {topRow, topCol};
                    return ans;
                }
                return null;    
            }
            //矩阵不是正方形,所以得求出其中的最大正方形的对角线
            int min = Math.min(botRow - topRow, botCol - topCol);
            int mid = min / 2;
            if (mat[topRow + mid][topCol + mid] == x) {
                int[] ans = {topRow + mid, topCol + mid};
                return ans;
            } else if (mat[topRow + mid][topCol + mid] < x) {  
                int[] ans = findDiv(mat, topRow + mid + 1, topCol + mid + 1, botRow, botCol, x ); //右下区域
                if (ans == null) {
                    ans = findDiv(mat, topRow + mid + 1, topCol, botRow, topCol + mid, x ); // 左下区域
                    if (ans == null)
                        return findDiv(mat, topRow, topCol + mid + 1, topRow + mid, botCol, x ); //右上区域
                    return ans;
                } else {
                    return ans;
                }
            } else {
                int[] ans = findDiv(mat, topRow, topCol, topRow + mid, topCol + mid, x); //左上区域
                if (ans == null) {
                    ans = findDiv(mat, topRow + mid + 1, topCol, botRow, topCol + mid, x ); // 左下区域
                    if (ans == null)
                        return findDiv(mat, topRow, topCol + mid + 1, topRow + mid, botCol, x ); //右上区域
                    return ans;
                } else {
                    return ans;
                }
            }         
        }
        return null;
    }
    public static void main(String[] args) {
        Num11_6 n116 = new Num11_6();
        int[][] mat = {{0,2,3,4},{1,5,6,7}};
        n116.find(mat, 0, 0, 3, 7);
    }
}
View Code

11.8

public class Num11_8 {
    //实现一个二叉查找树来插入元素,并且在结点中存储每个结点的左子树结点数量
    RankNode root = null;
    public int[] getRankOfNumber(int[] A, int n) {
        // write code here
        int[] rank = new int[n];
        for (int i = 0; i < n; i++) {
            track(A[i]);//插入数据
            rank[i] = root.getRank(A[i]);//获得该数据的秩
        }
        return rank;
    }    
    public void track(int val) {
        if (root == null)
            root = new RankNode(val);
        else
            root.insert(val);
    }
}
//实现的数据结构
class RankNode {
    RankNode left = null, right = null;
    int val = 0;
    int leftSize = 0;
    public RankNode(int val) {
        this.val = val;
    }
    //往树中插入数据    时间复杂度为O(log(n))
    public void insert(int val) {
        if (val <= this.val) {
            if (this.left != null)
                this.left.insert(val);
            else
                this.left = new RankNode(val);
            this.leftSize++;
        } else {
            if (this.right != null)
                this.right.insert(val);
            else
                this.right = new RankNode(val);
        }
    }
    //获得秩   时间复杂度为O(log(n))
    public int getRank(int val) {
        if (val == this.val)
            return this.leftSize;
        if (val > this.val) {
            if(this.right == null)
                return -1;
            return this.leftSize + 1 + this.right.getRank(val);
        }
        if (this.left == null)
            return -1;
        return this.left.getRank(val);
    }
}
View Code

 17.3

计算n!尾部零的个数

/*
 * 计算n!尾部零的个数
 * 零的个数也就是10的个数 ,10的个数可以看成是5的倍数或2的倍数,因为是2的倍数的数比是5的倍数多
 * 所以只用求5的倍数  
 * 需要注意的是5的次方  25 = 5*5,所以相当于有两个5,125有三个5,所以次方増一,5的倍数也増一
 */
public class Num17_3 {
    public int countZero(int n) {
        if (n < 0) {
            return -1;
        }
        int count = 0;        
        for (int i = 5; n / i > 0; i *= 5) {
            count += n / i;
        }
        return count;
    }
}
View Code

 18.1

不用加号以及其他算术运算符的加法

    public int addAB(int A, int B) {
        // write code here
        /*非递归解法
        int c = 0;
        int j = 0;
        for (int i = 0; i < 32; i++) {
            if (((A & (1 << i)) | (B & (1 << i))) == 0) {
                if (j == 1) {
                    c |= 1 << i;
                    j = 0;
                }                    
            } else if (((A & (1 << i)) & (B & (1 << i))) == 0) {
                if (j == 0)
                    c |= 1 << i;
                else 
                    j = 1;
            } else {
                if (j == 1)
                    c |= 1 << i;
                else
                    j = 1;
            }
        }
        return c;*/
        //递归解法
        if (B == 0)
            return A;
        int sum = A ^ B;              //相加不进位
        int carry = (A & B) <<1;     //进位不想加
        return addAB(sum,carry);        
    }
View Code

 18.2

完美洗牌--完美打乱一个数组

public class Num18_2 {
    /*
     * 随机打乱一个数组
     * 采用递归的方式
     * 先打乱数组的前n-1个元素,再将第n个元素与前n个元素中的元素随机交换
     */
    //产生[lower,higher]之间的随机数
    public int rand(int lower,int higher) {
        return lower + (int) (Math.random() * (higher - lower + 1));
    }
    //参数i为数组最后一个元素的索引
    public int[] shuffleArrayRecursiveLy(int[] arr, int i) {
        if (i == 0)
            return arr;
        shuffleArrayRecursiveLy(arr, i - 1);
        int k = rand(0,i);   //前i+1个元素中的随机一个
        //将第i+1个元素与其交换
        int temp = arr[k];
        arr[k] = arr[i];
        arr[i] = temp;
        return arr;
    }
    /*
     * 迭代的方式
     */
    public int[] shuffleArray(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            int k = rand(0,i);
            int temp = arr[k];
            arr[k] = arr[i];
            arr[i] = temp;
        }
        return arr;
    }
}
View Code

 18.4

输出0~n中的数字含有2的个数

    public int countNumberOf2s(int n) {
        // write code here
        int count = 0;
        int length = Integer.toString(n).length();
           for (int i = 0; i < length; i ++) {
            count += countAtDigit(n,i);
        }
        return count;
    }
    public int countAtDigit(int n, int d) {
        int powerOf10 = (int)Math.pow(10,d);
        int nextPow = powerOf10 * 10;
        
        int downRound = n - n % nextPow;
        int upRound = downRound + nextPow;
        int right = n % powerOf10;
        
        int digit = (n / powerOf10) % 10; 
        if (digit == 2) {
            return downRound / 10 + right + 1;
        } else if (digit > 2) {
            return upRound / 10;
        } else 
            return downRound / 10;
   }

 18.7

public class Num17_7 {
    /*
     * 书本中给出的使用了hashmap来缓存的前提是字符串数组已经排序了
     * 这样可以从长到短来遍历
     * 这里没有使用缓存
     */
    public int getLongest(String[] str, int n) {
        // write code here
        int ans = 0, lastLen = 0;
        for (int i = 0; i < n; i++) {
            if (lastLen < str[i].length() && divideAndFind(str, str[i], i))
                lastLen = str[i].length();
        }
        return lastLen;
    }
    public boolean hasSub(String[] strs, String str, int index) {
        
        if (str == null)
            return true;
        for (int i = 0; i < strs.length; i++) {
            if (i != index) {
                if (strs[i].equals(str))
                    return true;
            }
        }
        return false;
    }
    public boolean divideAndFind(String[] strs, String str, int strIndex) {
        if (str == null || str.length() == 0)
            return true;
        for (int i = 1; i <= str.length(); i++) {
            if (hasSub(strs, str.substring(0, i), strIndex) && divideAndFind(strs, str.substring(i),strIndex))
                return true;
        }
        return false;
    }
}
View Code

18.8

利用后缀树实现的判断短字符串是否是某个字符串的子串,也附上kmp实现的代码

package 高难度题;

import java.util.ArrayList;
import java.util.HashMap;

public class Num18_8 {
    /*
     * 利用后缀树实现
     */
    public boolean[] chkSubStr(String[] p, int n, String s) {
        // write code here
        SuffixTreeNode root = new SuffixTreeNode();
        for (int i = 0;i < s.length(); i++) {
            root.insert(s.substring(i), i);
        }
        boolean[] ans = new boolean[n];
        for (int i = 0;i < n; i++) {
            if (p[i] == null || p[i].length() == 0)
                ans[i] = false;
            else
                ans[i] = root.search(p[i]);            
        }
        return ans;
    }
    /*
     * 利用kmp算法实现
     */
    public boolean[] chkSubStrKmp(String[] p, int n, String s) {
        // write code here
        boolean[] ans = new boolean[n];
        for (int i = 0; i < n; i++) {
            if (isSubStr(p[i], s))
                ans[i] = true;           
        }
        return ans;
    }
    public boolean isSubStr(String par, String ori) {
        int next[] = new int[par.length()+1];
        //求next数组每个元素值                 
        next[0] = -1;
        int k = -1, j = 0;
        while ( j < par.length()) { 
            //k == -1就表示比较到了第一位还不相等,所以next[j] = 0
            //k其实就是next[j-1](k == -1时其实也是),par.charAt(j) == par.charAt(k)满足的话,也就是说next[j] = next[j-1]+1
            //如果不满足那就要从前k个字符的前缀不相等的那一位开始
            if (k == -1 || par.charAt(j) == par.charAt(k)) {
                next[++j] = ++k;//也就是说next[j] = next[j-1]+1
            } else {
                k = next[k];//从前k个字符的前缀不相等的那一位开始从新比较
            }
        }
        int p = 0,q = 0;
        while (p < ori.length()) {
            if (par.charAt(q) == ori.charAt(p)) {
                if (q == par.length()-1) {
                    q = next[q];
                    return true;//出现模式串则返回
                } else { //相等则继续往后比较
                    p++;
                    q++;
                }                          
            } else { //不相等则移动
                q = next[q];
            }
            if (q == -1) { //比较了模式串的第一个字符且不相等
                p++;
                q++;
            }
        }      
        return false;
    }
}
/*
 * 后缀树的实现
 */
class SuffixTreeNode {
    public SuffixTreeNode() {}
    HashMap<Character, SuffixTreeNode> children = new HashMap<Character, SuffixTreeNode>();
    ArrayList<Integer> indexes = new ArrayList<Integer>();
    public void insert(String str, int index) {
        if (str != null && str.length() != 0) {
            indexes.add(index);
            char value = str.charAt(0);
            SuffixTreeNode child;
            if (children.containsKey(value)) {
                child = children.get(value);
            } else {
                child = new SuffixTreeNode();
                children.put(value, child);
            }
            String subStr = str.substring(1);
            child.insert(subStr, index);
        }
    }
    
    /*
     * 可以返回子串在原字符串中的索引,由于原字符串中的子串可能不止一个,所以返回的是ArrayList
    public ArrayList search(String t) {
        if (t == null || s.length() == 0)
            return indexes;
        else {
            char value = t.charAt(0);
            if (children.containsKey(value)) {
                String subStr = str.substring(1);
                return children.get(value).search(subStr);
            }
        }
        return null;    
    }*/
    public boolean search(String t) {
        if (t == null || t.length() == 0)
            return true;
        else {
            char value = t.charAt(0);
            if (children.containsKey(value)) {
                String subStr = t.substring(1);
                return children.get(value).search(subStr);
            }
        }
        return false;    
    }
}
View Code

 18.10

/*
 * solution :
 * 算法基础是广度优先遍历,在此基础上加上回溯
 * 对于每一个遍历过的单词,利用hashmap backTrack将其与相差一个字母的单词映射 backTrack[v] = temp,表示v可以由temp更改得到
 * backTrack 记录的是首次出现的键值对,因为以后如果再出现v,v已经visited,所以不会沿着这次出现的查询下去,因此没必要再加入backTrack
 * 当找到了目标单词后,在backTrack中往回回溯,即可得到路径
 */
public class Num18_10 {
    public int countChanges(String[] dic, int n, String s, String t) {
        // write code here
        if (s.equals(t))
            return 0;
        Set<String> dict = new HashSet<String>();
        for (String ss : dic) {
            dict.add(ss);
        }
        Queue<String> q = new LinkedList<String>();        
        Map<String, String> backTrack = new HashMap<String, String>();
        Set<String> visited = new HashSet<String>();
        q.add(s);
        visited.add(s);
        while(!q.isEmpty()) {
            String temp = q.poll();
            for (String v : change(temp)) {
                if (v.equals(t)) {
                    int ret = 0;
                    //注释部分可以用于返回整个路径
                    /*
                    List<String> ans = new ArrayList<String>();
                    ans.add(v);
                    */
                    while (temp != null) {
                        ret++;
                        /*
                        ans.add(0, temp);
                        */
                        temp = backTrack.get(temp);                        
                    }
                    //返回步数
                    return ret;
                }
                if (dict.contains(v) && !visited.contains(v)) {
                    q.add(v);
                    visited.add(v);
                    backTrack.put(v,temp);
                }
            }            
        }
        //没找到路径
        return -1;
    }
    public Set<String> change(String str) {
        Set<String> changes = new HashSet<String>();        
        for (int i = 0; i < str.length(); i++) {
            char[] arr = str.toCharArray();
            for (char c = 'a'; c <= 'z'; c++) {
                if (str.charAt(i) != c) {
                    arr[i] = c;
                    changes.add(new String(arr));
                }
            }
        }
        return changes;
    } 
}
View Code

18.11

/*
 * solution  时间复杂度 是O(n^4),最简单的做法
 * 按边长从大到小的顺序遍历矩阵中每个位置是否可以构成同值方阵,检查四条边的值是否全部相同
 * 需要注意的是方阵的中心不一定是在原始矩阵的中心
 */
public class Num18_11 {
    public int maxSubMatrix(int[][] mat, int n) {
        // write code here    
        for (int len = n; len > 0; len--) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++ ) {
                    if (i + len <= n && j + len <= n) {
                        if (checkTop(mat, i, j, len) && checkRight(mat, i, j, len) && checkBottom(mat, i, j, len) && checkLeft(mat, i, j, len)) {
                            return len;
                        }
                    }
                }
            }
        }
        return 0;
    }
    public boolean checkTop(int[][] mat, int row, int column, int len) {
        for (int i = column + 1; i < column + len; i++) {
            if (mat[row][i] != mat[row][i - 1]) {
                return false;
            }
        }
        return true;
    }
    public boolean checkRight(int[][] mat, int row, int column, int len) {
        for (int i = row + 1; i < row + len; i++) {
            if (mat[i][column + len - 1] != mat[i - 1][column + len - 1]) {
                return false;
            }
        }
        return true;        
    }
    public boolean checkBottom(int[][] mat, int row, int column, int len) {
        for (int i = column + 1; i < column + len; i++) {
            if (mat[row + len - 1][i] != mat[row + len - 1][i - 1]) {
                return false;
            }
        }
        return true;        
    }
    public boolean checkLeft(int[][] mat, int row, int column, int len) {
        for (int i = row + 1; i < row + len; i++) {
            if (mat[i][column] != mat[i - 1][column]) {
                return false;
            }
        }
        return true;          
    }    
}
View Code

 18.12

/*
 * 题意:求矩阵的元素总和最大的子矩阵 ,返回 和
 * solution : 若矩阵是R行C列   时间复杂度是O(R^2*C)
 * 子矩阵是由连续的行和连续的列组成的
 * 迭代所有连续的行的组合,在每种行的组合中找使得和最大的连续的列的组合,  再得出每种行的组合的最大值即可
 * 在每种行的组合中,我们可以把问题简化为求和最大子数组问题
 * 在每种行的组合中,把每一列的和看成一维数组的一个元素,所以我们要求一维数组的和最大子数组,这个算法比较简单,已经实现
 */
public class Num18_12 {
    public int sumOfSubMatrix(int[][] mat, int n) {
        // write code here
        if (mat == null)
            return -1;
        int maxSum = Integer.MIN_VALUE;
        int[] rowSum = new int[mat[0].length];
        for (int i = 0; i < mat.length; i++) { 
            //数组清零
            for ( int k = 0; k < mat[0].length; k++) {
                    rowSum[k] = 0;
            }
            for (int j = i; j < mat.length; j++) {
                for ( int k = 0; k < mat[0].length; k++) {
                    rowSum[k] += mat[j][k];      
                }
                int curMax = maxSubArray(rowSum);
                if (curMax > maxSum)
                    maxSum = curMax;
            } 
        }
        return maxSum;
    }
    public int maxSubArray(int[] arr) {
        if (arr == null)
            return -1;
        int max = Integer.MIN_VALUE;
        int curSum = 0;
        for (int i = 0; i < arr.length; i++) {
            curSum += arr[i];
            if (curSum > max)
                max = curSum;
            if (curSum < 0)
                curSum = 0;
        }
        return max;
    }
}
View Code
原文地址:https://www.cnblogs.com/fisherinbox/p/5568440.html