• 剑指Offer系列之题61~题67(完结)


    写在前面:本随笔是剑指Offer系列最后一篇。后续会记录一些Java、Spring、数据库、分布式等方面的内容。

    61.序列化二叉树

    请实现两个函数,分别用来序列化和反序列化二叉树

    二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

    二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

    递归;非递归。选择相应的遍历顺序。


    1.递归:

    public class Solution {
        int index=-1;
        String Serialize(TreeNode root) {
            //以 !表示结点值的结束,#表示空节点
            StringBuffer sb=new StringBuffer();
            if(root==null){
                sb.append("#!");
                return sb.toString();
            }
            //前序遍历
            sb.append(root.val+"!");
            sb.append(Serialize(root.left));
            sb.append(Serialize(root.right));
            return sb.toString();
      }
        TreeNode Deserialize(String str) {
            index++;//依次后移判断对应结点
            TreeNode root=null;
            String[] node=str.split("!");
            if(!node[index].equals("#")){
                root=new TreeNode(Integer.valueOf(node[index]));
                root.left=Deserialize(str);
                root.right=Deserialize(str);
            }
            return root;
      }
    }
    

    2.非递归(层次遍历):

    /*
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
        }
    }
    */
    import java.util.Queue;
    import java.util.LinkedList;
    public class Solution {
        int index=-1;
        String Serialize(TreeNode root) {
            //以 !表示结点值的结束,#表示空节点
            if(root==null)
                return "#!";
            Queue<TreeNode> queue=new LinkedList<>();
            StringBuffer sb=new StringBuffer();
            int width=0;//宽度,当前层结点个数
            int count=0;//当前层已遍历结点数
            TreeNode temp=null;//存储遍历的结点
            queue.offer(root);
            sb.append(root.val+"!");
            while(!queue.isEmpty()){
                width=queue.size();
                while(count<width){//遍历当前层
                    temp=queue.poll();
                    count++;
                    //在入队时,直接将值添加到字符串
                    if(temp.left!=null){
                        queue.offer(temp.left);
                        sb.append(temp.left.val+"!");
                    }else{
                        sb.append("#!");
                    }
                    if(temp.right!=null){
                        queue.offer(temp.right);
                        sb.append(temp.right.val+"!");
                    }else{
                        sb.append("#!");
                    }
                }
                count=0;
            }
            return sb.toString();
      }
        TreeNode Deserialize(String str) {
            TreeNode head = null;
            if(str == null || str.length() == 0)
                return head;
            String[] nodes = str.split("!");
            TreeNode[] treeNodes = new TreeNode[nodes.length];
            for(int i=0; i<nodes.length; i++){//将字符串数组转换为结点数组
                if(!nodes[i].equals("#"))
                    treeNodes[i] = new TreeNode(Integer.valueOf(nodes[i]));
            }
            for(int i=0,j=1;j<treeNodes.length;i++){
                //i代表要连接左右子树的父节点,j表示左右子树
                if(treeNodes[i]!=null){
                    treeNodes[i].left=treeNodes[j++];
                    treeNodes[i].right=treeNodes[j++];
                }
            }
            return treeNodes[0];
      }
    }
    

    62.二叉搜索树的第k个节点

    给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

    辅助栈:递归将结点按右根左的顺序入栈,最后从栈顶到栈底是升序排列。


    1.辅助栈:

    /*
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    import java.util.Stack;
    public class Solution {
        Stack<TreeNode> stack=new Stack<>();
        TreeNode KthNode(TreeNode pRoot, int k)
        {
            // 根大于左,小于右;最左子节点是最小值  中序遍历
            if(pRoot==null || k<1)
                return null;
            //从最左子节点开始计数
            helper(pRoot);
            TreeNode temp=null;
            while(!stack.empty() && k>0){
                temp=stack.pop();
                k--;
            }
            if(k>0)//当k大于结点个数时
                return null;
            return temp;
        }
        //从右到左入栈,从栈顶到栈底是升序
        void helper(TreeNode root){
            if(root!=null){
                if(root.right!=null)
                    helper(root.right);//将右子节点入栈
                stack.push(root);//将根入栈
                if(root.left!=null)
                    helper(root.left);//将左子节点入栈
            }
        }
    }
    

    2.非递归:

    假设有二叉搜索树为 2 3 4,求第三小的结点,以下过程为:

    3入栈,2入栈(最左子节点),然后2出栈,不等,又因为2的右结点为空,所以3出栈,不等,但3的右结点非空,所以4入栈,然后4出栈。

    import java.util.Stack;
    public class Solution {
        TreeNode KthNode(TreeNode pRoot, int k)
        {
            // 根大于左,小于右;最左子节点是最小值  中序遍历
            if(pRoot==null || k<1)
                return null;
            //从最左子节点开始计数
            TreeNode temp=pRoot;
            Stack<TreeNode> stack=new Stack<>();
            int count=0;
            while(temp!=null || !stack.isEmpty()){
                if(temp!=null){//一直入左子节点,直到空
                    stack.push(temp);
                    temp=temp.left;
                }else{
                    temp=stack.pop();//第一次出栈是最小值
                    count++;
                    if(count==k)
                        return temp;
                    temp=temp.right;//赋为右结点,若非空则将入栈即栈顶,此时左 根已遍历,下一个遍历结点即该右结点;空则接着出栈顶元素
                }
            }
            return null;
        }
    }
    

    63.数据流中的中位数

    如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

    暴力解(时空复杂度过高):ArrayList存储数字,然后赋值给数组排序,返回中位数;

    大顶堆和小顶堆大顶堆用来存较小的数,从大到小排列小顶堆存较大的数,从小到大的顺序排序,显然中位数就是大顶堆的根节点与小顶堆的根节点和的平均数。


    1.暴力解:

    import java.util.Arrays;
    import java.util.ArrayList;
    public class Solution {
        ArrayList<Integer> res=new ArrayList<>();
        public void Insert(Integer num) {
            res.add(num);
        }
        public Double GetMedian() {
            if(res.isEmpty())
                return null;
            double arr[]=new double[res.size()];
            for(int i=0;i<arr.length;++i){
                arr[i]=(double)res.get(i);
            }
            Arrays.sort(arr);//排序
            if(arr.length%2!=0){//奇数个数
                return arr[arr.length/2];
            }
            return (arr[arr.length/2-1]+arr[arr.length/2])/2;//偶数个数
        }
    }
    

    2.堆排序(大顶堆、小顶堆):

    import java.util.PriorityQueue;
    import java.util.Comparator;
    public class Solution {
        //小顶堆
        private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
    
        //大顶堆
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(15, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
    
        //记录偶数个还是奇数个
        int count = 0;
        //每次插入小顶堆的是当前大顶堆中最大的数
        //每次插入大顶堆的是当前小顶堆中最小的数
        //这样保证小顶堆中的数永远大于等于大顶堆中的数
        //中位数就可以方便地从两者的根结点中获取了
        public void Insert(Integer num) {
            //奇数或偶数时入大或入小都可,关键是需要先入然后出最大或最小值入另一个堆中
    
            //已有个数为偶数的话,则先插入到大顶堆,然后将大顶堆中最大的数插入小顶堆中
            if(count % 2 == 0){
                maxHeap.offer(num);
                int max = maxHeap.poll();
                minHeap.offer(max);
            }else{
                //已有个数为奇数的话,则先插入到小顶堆,然后将小顶堆中最小的数插入大顶堆中
                minHeap.offer(num);
                int min = minHeap.poll();
                maxHeap.offer(min);
            }
            count++;
        }
        public Double GetMedian() {
            //当前为偶数个,则取小顶堆和大顶堆的堆顶元素求平均
            if(count % 2 == 0){
                return new Double(minHeap.peek() + maxHeap.peek())/2;
            }else{
                //当前为奇数个,则直接从小顶堆中取元素即可
                return new Double(minHeap.peek());
            }
        }
    }
    

    参考:数据流中的中位数

    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]}。

    暴力解;每次移动后遍历当前滑动窗口;

    双端队列:对新来的元素k,将其与双端队列中的元素相比较 1)前面比k小的,直接移出队列(因为不再可能成为后面滑动窗口的最大值了!),2)前面比k大的X,比较两者下标,判断X是否已不在窗口之内,不在了,直接移出队列队列的第一个元素是滑动窗口中的最大值


    1.暴力解:

    import java.util.ArrayList;
    public class Solution {
        public ArrayList<Integer> maxInWindows(int [] num, int size)
        {
            //窗口里的最大值
            ArrayList<Integer> res=new ArrayList<>();
            if(num.length<=0 || size<=0)//异常输入
                return res;
            int fir=0;//起始位置
            int last=size-1;//末尾
            int max=0;
            if(size>num.length)//若只存在一个滑动窗口
                return res;
            while(last<num.length){
                max=num[fir];
                for(int i=fir;i<=last;++i){
                    if(num[i]>max)
                        max=num[i];
                }
                res.add(max);
                fir++;
                last++;
            }
            return res;
        }
    }
    

    2.利用双端队列:

    import java.util.ArrayList;
    import java.util.Deque;
    import java.util.LinkedList;
    public class Solution {
        public ArrayList<Integer> maxInWindows(int [] num, int size)
        {
            //窗口里的最大值
            ArrayList<Integer> res=new ArrayList<>();
            if(num.length<=0 || size<=0)//异常输入
                return res;
            Deque<Integer> q=new LinkedList<>();
            for(int i=0;i<num.length;++i){
                int flag=i-size+1;//flag是当前位置的前size处的下标,用来判断队列内个数是否达到了size个
                if(q.isEmpty())//若队列空,将当前元素的下标入队,表示该元素在当前滑动窗口内
                    q.add(i);
                else if(flag>q.peekFirst())//此时队列内元素下标个数超过滑动窗口大小
                    q.pollFirst();//弹出队首元素
                while(!q.isEmpty() &&  num[q.peekLast()]<= num[i])//若队尾元素小于当前元素,该步删除队列内比当前元素小的元素
                    q.pollLast();//将队尾元素弹出,即删除比当前元素小的元素的下标
                q.add(i);//将当前元素下标入队
                if(flag>=0)//说明当前是滑动窗口
                    res.add(num[q.peekFirst()]);//队首下标即当前滑动窗口内最大元素的下标
            }
            return res;
        }
    }
    

    65.矩阵中的路径

    请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如
    ( left[ egin{matrix} a&b&c&e\ s&f&c&s\ a&d&e&e end{matrix} ight] )
    矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

    回溯:找到第一个相同的然后开始递归判断,递归过程中如果不符合条件返回false同时将当前位的访问状态重置


    1.回溯:

    public class Solution {
        public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
        {
            int count=0;//第几个字符
            boolean flag[]=new boolean[matrix.length];
            if(rows<1 || cols<1 || str.length<1)
                return false;
            for(int i=0;i<rows;++i){
                for(int j=0;j<cols;++j){
                    if(helper(matrix,rows,cols,i,j,str,count,flag))
                        return true;
                }
            }
            return false;
        }
        boolean helper(char[] matrix,int rows,int cols,int row,int col,char[] str,int count,boolean[] flag){
            //如果超出范围/不等/已访问都返回false;
            if(row<0 || col<0 || row==rows || col==cols || matrix[row*cols+col]!=str[count]||flag[row*cols+col])
                return false;
            //若count=str长度,说明全部匹配成功
            if(count==str.length-1)
                return true;
            //非空且相等时继续对比下一位
            flag[row*cols+col]=true;//设为已访问
            //递归寻找,找到了就count+1
            if(helper(matrix,rows,cols,row+1,col,str,count+1,flag) ||
                    helper(matrix,rows,cols,row-1,col,str,count+1,flag) ||
                    helper(matrix,rows,cols,row,col-1,str,count+1,flag) ||
                    helper(matrix,rows,cols,row,col+1,str,count+1,flag)){
                return true;
            }
            //上面语句不通过说明未找到,回溯,将该位置为未访问
            flag[row*cols+col]=false;
            return false;
        }
    }
    

    66.机器人的运动范围

    地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

    递归。


    1.递归:

    public class Solution {
        public int movingCount(int threshold, int rows, int cols)
        {
            boolean flag[]=new boolean[rows*cols];
            if(rows<=0 || cols<=0 || threshold<0)
                return 0;
            int count=helper(rows,cols,0,0,threshold,flag);
            return count;
        }
    
        int helper(int rows,int cols,int row,int col,int k,boolean[] flag){
            //退出条件
            if(row<0 || col<0 || row==rows || col==cols || flag[row*cols+col] || !checkSum(row,col,k))
                return 0;
            flag[row*cols+col]=true;//置为已访问
            return helper(rows,cols,row-1,col,k,flag)+
                helper(rows,cols,row+1,col,k,flag)+
                helper(rows,cols,row,col-1,k,flag)+
                helper(rows,cols,row,col+1,k,flag)+1;
        }
        //检查数位和
        boolean checkSum(int row,int col,int k){
            int sum=0;
            while(row!=0 || col!=0){
                if(row!=0){
                    sum+=row%10;
                    row=row/10;
                }
                if(col!=0){
                    sum+=col%10;
                    col/=10;
                }
            }
            if(sum>k)
                return false;
            return true;
        }
    }
    

    2.非递归:

    public class Solution {
        public int movingCount(int threshold, int rows, int cols)
        {
            if(rows<=0 || cols<=0 || threshold<0)
                return 0;
            int count=0;
            //遍历每个点
            for(int i=0;i<rows;++i){
                for(int j=0;j<cols;++j){
                    if(checkSum(i,j,threshold))//满足条件
                        count++;
                    else if(rows==1 || cols==1)//当只有一行或一列时,遇到不满足的点就退出
                        return count;
                }
            }
            return count;
        }
    
        //检查数位和
        boolean checkSum(int row,int col,int k){
            int sum=0;
            while(row!=0 || col!=0){
                if(row!=0){
                    sum+=row%10;
                    row=row/10;
                }
                if(col!=0){
                    sum+=col%10;
                    col/=10;
                }
            }
            if(sum>k)
                return false;
            return true;
        }
    

    67.剪绳子

    给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

    输入描述:输入一个数n,意义见题面。(2 <= n <= 60)

    找到规律:当都是2和3时乘积最大,且3的个数大于2的个数。

    5<2*3,6<3*3,比6更大的数字我们就更不用考虑了,肯定要继续分。


    1.:

    public class Solution {
        public int cutRope(int target) {
            //m>1,n>1
            //根据规律可看出是 2 和 3的乘积
            int multi=1;
            //小值判断
            if(target<=3)
                return target-1;
            while(target>4){//只有5才可分为 2+3,比5小的直接当作一段时乘积最大
                target-=3;
                multi*=3;
            }
            return multi*target;
        }
    }
    

    如有错误,欢迎指正

  • 相关阅读:
    关于 Mercury_Lc 说明
    Java 对象和类
    Java int 与 Integer 区别
    Java Number & Math 类
    HTML | CSS | JavaScript 常见错误
    B. Heaters ( Codeforces Round #515 (Div. 3) )
    A. Vova and Train ( Codeforces Round #515 (Div. 3) )
    数据结构实验之排序四:寻找大富翁(SDUT 3401)
    JavaScript 与 Java
    A. The Fair Nut and Elevator (Codeforces Round #526 (Div. 2))
  • 原文地址:https://www.cnblogs.com/lfz1211/p/12719851.html
Copyright © 2020-2023  润新知