美文网首页
12.5 剑指Offer 60-67 (完)

12.5 剑指Offer 60-67 (完)

作者: AlexLJS | 来源:发表于2020-08-13 21:15 被阅读0次

60、

public class FloorPrintTree_60 {
    /**
     * 把二叉树打印成多行
     *
     * 题目描述
     * 从上到下按层打印二叉树,同一层结点从左至右输出。
     * 每一层输出一行。
     *
     * 思路:
     * 层序遍历二叉树, 前面也已经用过很多次了。
     * mark Node 标记一下何时换行就OK了
     */

    ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if (pRoot == null) return res;
        TreeNode cur = pRoot;
        TreeNode mark = new TreeNode(0);
        Queue<TreeNode> queue = new LinkedList<>();
        ArrayList<Integer> floor = new ArrayList<>();

        queue.add(cur);
        queue.add(mark);

        while (!queue.isEmpty()){
            TreeNode tmp = queue.poll();
            if (tmp == mark){
                res.add(floor);
                if (queue.isEmpty()) return res;
                floor = new ArrayList<>();
                queue.add(mark);
            }else {
                floor.add(tmp.val);
                if (tmp.left != null) queue.add(tmp.left);
                if (tmp.right != null) queue.add(tmp.right);
            }
        }

        return res;
    }

}


61、

public class SerializeTree_61 {
    /**
     * 序列化二叉树
     *
     * 题目描述
     * 请实现两个函数,分别用来序列化和反序列化二叉树
     *
     * 二叉树的序列化是指:
     * 把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,
     * 从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,
     * 序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
     *
     * 二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。*
     * 例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树
     *
     * 思路:
     * 层序遍历太复杂,推荐使用先序遍历, 因为先序遍历 方便查找头结点。
     * 必会题目。
     *
     * 注: 用 -> 效率很低
     */

    public String Serialize(TreeNode root) {
        return proSe(root);
    }
    private String proSe(TreeNode root){
        if (root == null) return "#";
        return root.val + "!" + proSe(root.left) + "!" + proSe(root.right);
    }

    public TreeNode Deserialize(String str) {
        String[] s = str.split("!");
        Queue<String> queue = new LinkedList<>();
        Arrays.stream(s).forEach(e -> queue.add(e));

        return proDe(queue);
    }

    private TreeNode proDe(Queue<String> queue){
        String cur = queue.poll();
        if (cur.equals("#")) return null;
        TreeNode res = new TreeNode(Integer.valueOf(cur));

        res.left = proDe(queue);
        res.right = proDe(queue);
        return res;
    }
}

62、

public class KthNode_62 {
    /**
     * 二叉搜索树的第k个节点
     *
     *  题目描述
     * 给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)  中,
     * 按结点数值大小顺序第三小结点的值为4。
     *
     * 思路:
     * bst的中序遍历是顺序的。 记录k即可。
     */

    TreeNode res;

    TreeNode KthNode(TreeNode pRoot, int k)
    {
        int[] state = {k};
        process(pRoot, state);
        return res;
    }

    private void process(TreeNode root, int[] state){
        if (root == null) return;
        process(root.left,state);
        state[0]--;
        if (state[0] == 0) res = root;
        else process(root.right,state);
    }


}

63、

public class Median_63 {
    /**
     * 数据流中的中位数
     *
     * 题目描述
     * 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,
     * 那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,
     * 那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,
     * 使用GetMedian()方法获取当前读取数据的中位数。
     *
     * 思路:
     * 经典思路,将数据流拆分成 大根堆 与 小根堆, 大根堆用来存小的一半元素,小根堆用来存大的一半元素,
     *
     * 用两个堆的对顶来计算中位数。
     * 注:
     * 1、insert 元素的时候, 需要考虑 小根堆与大根堆 大小, 长度差距超过2 会丢失中位数。
     * 2、判断插入元素大小
     * 默认 插大根堆,奇数情况大根堆 多一个元素。
     */
    PriorityQueue<Integer> heapMin = new PriorityQueue<>();
    PriorityQueue<Integer> heapMax = new PriorityQueue<>((o1, o2) -> o2-o1);

    public void Insert(Integer num) {
        heapMax.add(num);

        if (heapMin.size() != 0 && heapMax.peek() > heapMin.peek()) {
            Integer temp = heapMin.poll();
            heapMin.add(heapMax.poll());
            heapMax.add(temp);
        }

        if (heapMax.size() > heapMin.size() + 1) heapMin.add(heapMax.poll());
    }

    public Double GetMedian() {
        if (heapMin.size() == heapMax.size())return (heapMax.peek() + heapMin.peek()) / 2.0;
        return (double) heapMax.peek();
    }

}

64、

public class MaxInWindows_64 {
    /**
     * 滑动窗口的最大值
     *
     * 题目描述
     * 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。
     * 例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,
     * 那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5};
     * 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个:
     * {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1},
     * {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
     *
     * 思路 : 经典滑动窗口问题
     *
     * 核心 : 维护一个单调递减的最大长度为3的最大值队列
     * 保证队列头始终为当前窗口的最大值, 新加入的元素e,
     * e前面的元素(队尾元素)比e小,就弹出。直到跟e相等将e插入
     * 队列头清空越界元素,超出长度,把头弹出。
     */
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> res = new ArrayList<>();
        LinkedList<Integer> queue = new LinkedList<>();//存index ,方便控制元素是否在窗口内

        if (size == 0)return res; // 特判!!

        for (int i = 0; i < num.length; i++) {
            if ( !queue.isEmpty() && queue.peek() + size -1 < i) queue.pop();//弹出队首
            while (!queue.isEmpty() && num[queue.getLast()] < num[i]) queue.removeLast();
            queue.add(i);
            if (i >= size - 1) res.add(num[queue.peek()]);
        }

        return res;
    }

}

65、

public class HasPath_65 {
    /**
     * 矩阵中的路径
     *
     * 题目描述
     * 请设计一个函数,用来判断在一个矩阵中是否存在一条
     * 包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,
     * 每一步可以在矩阵中向左,向右,向上,向下移动一个格子。
     * 如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
     * 例如
     * a b c e
     * s f c s
     * a d e e
     * 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,
     * 因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,
     * 路径不能再次进入该格子
     *
     * 思路:
     * 这是一道经典搜索问题
     * 每个值都需要作为路径开始,向上下左右校验,能走通就继续走。
     * flag数组 用来规避 已经走过的路径
     *
     */

    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        int k = 0;
        boolean[] flag = new boolean[matrix.length];
        for (int i = 0; i < rows; i++){
            for(int j = 0; j < cols; j++){
                if (judge(matrix, i, j, rows, cols, flag, str, 0)){
                    return true;
                }
            }
        }
        return false;
    }

    public boolean judge(char[] matrix, int i, int j,int rows, int cols, boolean[] flag,char[] str, int k){
        int index = i * cols + j;
        if(i<0 || j < 0 || i >= rows || j >= cols || str[k] != matrix[index] || flag[index] == true) return false;

        if (k == str.length - 1) return true;

        flag[index] = true;

        if(judge(matrix, i+1, j, rows, cols, flag, str, k+1)||
                judge(matrix, i-1, j, rows, cols, flag, str, k+1)||
                judge(matrix, i, j+1, rows, cols, flag, str, k+1)||
                judge(matrix, i, j-1, rows, cols, flag, str, k+1)
        ){
            return true;
        }

        flag[index] = false;
        return false;
    }

}

66、

public class RobotMovingCount_66 {
    /**
     * 机器人运动范围
     *
     * 题目描述
     * 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,
     * 每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。
     * 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38)
     * ,因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
     *
     * 思路
     * 从左上角开始往 右边和下边前进,用二维数组将走过的位置标记为1。统计1的个数
     */

    public int movingCount(int threshold, int rows, int cols)
    {
        int[][] arr = new int[rows][cols];
        int count = 0;
        process(arr, 0 ,0, threshold);

        for (int i = 0; i < rows ; i++) {
            for (int j = 0; j < cols; j++) {
                if (arr[i][j] == 1) count++;
            }
        }

        //PrintUtil.printMatrix(arr);
        return count;
    }

    private void process(int[][] arr, int i, int j , int threshold){
        if (i < 0 || j < 0 || i >= arr.length || j >= arr[0].length || !judge(i,j,threshold) || arr[i][j] == 1) return;

        arr[i][j] = 1;
        process(arr,i+1,j,threshold);
        process(arr,i,j+1,threshold);
    }

    private boolean judge( int i, int j, int threshold){
        int sum = i%10 + i/10 + j%10 + j/10;
        return sum <= threshold ? true : false;
    }
}

67、

public class CutRope_67 {
    /**
     * 剪绳子
     *
     * 题目描述
     * 给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),
     * 每段绳子的长度记为k[1],...,k[m]。请问k[1]x...xk[m]可能的最大乘积是多少?
     *
     * 例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
     *
     * 思路:
     * 小学奥数题,贪心问题。核心就是凑 3 , 余1时 2*2 , 余 2时 3*2。
     *
     * 证明 m 与 (m-3)*3 比大小
     * (m-3)*3 - m > 0  => m > 4.5 即 m > 5 恒成立
     * 讨论 m = 4 , 2*2 > 1*3
     *
     * 代码边界未处理: n = 2 时 , 剪的话 return 1, 不剪 return 2
     * n = 3 , 同理 return 2,
     * 这样也可以 AC
     */

    public int cutRope(int n) {
        int m = n%3;

        if (m == 1){
            return (int) Math.pow( 3, n/3 - 1) * 4;
        }else if (m == 2){
            return (int) Math.pow( 3, n/3) * 2;
        }else {// m == 0
            return (int) Math.pow( 3, n/3);
        }
    }

}

相关文章

网友评论

      本文标题:12.5 剑指Offer 60-67 (完)

      本文链接:https://www.haomeiwen.com/subject/vlgdrktx.html