剑指offer(Java版)面试题58——68

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Katrina_ALi/article/details/81193032

面试题58:删除链表中重复的结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

代码:
        public class Solution {
            public ListNode deleteDuplication(ListNode pHead)
            {
                if(pHead==null || pHead.next==null) return pHead;
                ListNode pH=null; //要返回的节点
                if(pHead.val != pHead.next.val) {
                    pH = pHead;
                }else {
                    ListNode p = pHead;
                    while(p.next.next != null) {
                        if(p.val != p.next.val && p.next.val != p.next.next.val) {
                            pH = p.next;
                            break;
                        }
                        p = p.next;
                    }
                    if(p.next.next == null) {
                        if(p.val != p.next.val) {
                            return p.next;
                        }else {
                            return null;
                        }
                    }       
                }
                ListNode p = pH;
                ListNode current = pH.next;
                pH.next=null;
                int num = pH.val;
                while(current.next != null) {
                    if(current.val != num && current.val != current.next.val) {
                        p.next = current;
                        p = p.next;
                        num = current.val;
                        current = current.next;
                        p.next = null;
                        continue;
                        //num = current.next.val;
                    }
                    num = current.val;
                    current = current.next;
                }
                if(num != current.val) {
                    p.next = current;
                }
                return pH;
            }
        }

面试题59:二叉树的下一个结点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

代码:(未使用递归的二叉树问题)
        /*
        public class TreeLinkNode {
            int val;
            TreeLinkNode left = null;
            TreeLinkNode right = null;
            TreeLinkNode next = null;

            TreeLinkNode(int val) {
                this.val = val;
            }
        }
        */
        public class Solution {
            public TreeLinkNode GetNext(TreeLinkNode pNode)
            {
                if(pNode == null ) return null;
                    if(pNode.right != null) {
                        pNode = pNode.right;
                        while(pNode.left!=null) {
                            pNode = pNode.left;
                        }
                        return pNode;
                     }
                    while(pNode.next != null){
                        if(pNode.next.left == pNode){
                            return pNode.next;
                        }
                        pNode = pNode.next;
                    }
                    return null;
            }
        }

面试题60:对称的二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

    public class Question60 {
        boolean isSymmetrical(TreeNode pRoot)
       {
           if(pRoot==null) return true;//牛客上认为空指针也是对称的
           return isS(pRoot.left,pRoot.right);
       }
        boolean isS(TreeNode p1,TreeNode p2) {
            if(p1==null && p2==null) return true;
            if(p1==null || p2==null) return false;
            if(p1.val != p2.val) return false;
            return isS(p1.left,p2.right)&& isS(p1.right,p2.left);
        }
    }

面试题61:按之字形顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

    import java.util.ArrayList;
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
             ArrayList<ArrayList<Integer> > a =new ArrayList<ArrayList<Integer> >();
             if(pRoot == null) return a;
             int num = 0;
             ArrayList<TreeNode> current = new ArrayList<TreeNode>();//装下一级用的
             current.add(pRoot); 
             while(current.size() != 0) {
                 num++;
                 ArrayList<Integer> little = new ArrayList<Integer>();
                 ArrayList<TreeNode> current1 = new ArrayList<TreeNode>(); 
                 for(int i=0;i<current.size();i++) {
                     TreeNode p = current.get(i);
                     if(p.left != null) current1.add(p.left);
                     if(p.right != null) current1.add(p.right);
                     little.add(p.val);
                 }
                 //注意按照之子形打印
                 if(num % 2 == 0) {
                     ArrayList<Integer> s = new ArrayList<Integer>();
                     for(int i=little.size()-1;i>=0;i--) {
                         s.add(little.get(i));
                     }
                     a.add(s);
                 }else {
                     a.add(little);
                 } 
                 current = current1;            
             }
             return a;

         } //注:这道题和之前的一道题很像,都可以用队列这个数据结构来处理,但是本次采用的微微有点不同  

面试题62:把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。 (这道题和上面的那道题一样,甚至比上面那个还简单!)

    import java.util.ArrayList;
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
             ArrayList<ArrayList<Integer> > a =new ArrayList<ArrayList<Integer> >();
             if(pRoot == null) return a;
             ArrayList<TreeNode> current = new ArrayList<TreeNode>();//装下一级用的
             current.add(pRoot); 
             while(current.size() != 0) {
                 ArrayList<Integer> little = new ArrayList<Integer>();
                 ArrayList<TreeNode> current1 = new ArrayList<TreeNode>(); 
                 for(int i=0;i<current.size();i++) {
                     TreeNode p = current.get(i);
                     if(p.left != null) current1.add(p.left);
                     if(p.right != null) current1.add(p.right);
                     little.add(p.val);
                 }
                 a.add(little);
                 current = current1;            
             }
             return a;
        }

面试题63:序列化二叉树

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

    //序列化
    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();
      }
       //反序列化
        int index=-1;
        TreeNode Deserialize(String str) {
           index++;
           int len = str.length();
            if(index >= len){
                return null;
            }
            String[] strr = str.split(",");
            TreeNode node = null;
            if(!strr[index].equals("#")){
                node = new TreeNode(Integer.valueOf(strr[index]));
                node.left = Deserialize(str);
                node.right = Deserialize(str);
            }

            return node;
      } 

面试题64:二叉搜索树的第k个结点

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

    TreeNode KthNode(TreeNode pRoot, int k)
        {
            if(pRoot == null) return null;
            if(k<=0) return null;
            ArrayList<TreeNode> a = new ArrayList<TreeNode>();
            a = zhongxu(pRoot,a);
            if(a.size()==0) return null;
            if(k > a.size()) {
                return null;
            }else {
                return a.get(k-1);
            }

        }
        ArrayList<TreeNode> zhongxu(TreeNode p,ArrayList<TreeNode> a){
            if(p == null) return null;
            zhongxu(p.left,a);
            a.add(p);
            zhongxu(p.right,a); 
            return a;
        } 

面试题65:数据流中的中位数

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

import java.util.Comparator;
import java.util.PriorityQueue;

public class Question65 {
private PriorityQueue<Integer> small = new PriorityQueue<Integer>();
private PriorityQueue<Integer> big = new PriorityQueue<Integer>(11,new Comparator<Integer>() {
    public int compare(Integer num1,Integer num2) {
        return num2.compareTo(num1);
    }
});

int count=0;
public void Insert(Integer num) {
    count++;
    //总数为奇数时,大顶堆堆顶就是中位数
    if((count & 1) == 0) {//判断偶数的高效写法
        if(!big.isEmpty() && num < big.peek()) {
            big.offer(num);
            num = big.poll();
        }
        small.offer(num);
    }else {
        if(!small.isEmpty() && num > small.peek()) {
            small.offer(num);
            num = small.poll();
        }
        big.offer(num);
    }
}

public Double GetMedian() {
    if(count == 0) throw new RuntimeException("No available number");
    double result;
    if((count & 1)==1) {
        result = big.peek(); 
    }else {
        result = (small.peek() + big.peek())/2.0;

    }
    return result;
}
}

注意事项:

    1. Java的PriorityQueue 是从JDK1.5开始提供的新的数据结构接口,默认内部是自然排序,结果为小顶堆,也可以自定义排序器,比如下面反转比较,完成大顶堆。 PriorityQueue里面默认是小顶堆。 
    2. 最小堆:是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于其左子节点和右子节点的值。大顶堆:根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值。总而言之,最小的或者最大的就在根节点上
    3. 关于PriroityQueue的常用API:
        poll() 获取并移除堆顶的元素
        peek()获取堆顶的元素,但是不移除
        offer(num)将num添加进堆中
        size() 获取堆中元素的个数

面试题66:滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。

代码1:暴力算法
    public ArrayList<Integer> maxInWindows(int [] num, int size)
        { 
            ArrayList<Integer> contain = new ArrayList<Integer>();
            if(num == null || num.length==0 || size>num.length || size<=0) return contain;
            for(int i=0;i<=num.length-size;i++) {
                int max = num[i]; //记录当前窗口的最大值
                for(int n=i;n<size+i;n++) {
                    if(num[n]>max) {
                        max = num[n];
                    }
                }
                contain.add(max);
            }
            return contain;
        }
代码2:优化后的

面试题67:矩阵中的路径

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

    public class Question67 {
        public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
        {
                //标志位,初始化为false
                        boolean[] flag = new boolean[matrix.length];
                        for(int i=0;i<rows;i++){
                            for(int j=0;j<cols;j++){
                                 //循环遍历二维数组,找到起点等于str第一个元素的值,再递归判断四周是否有符合条件的----回溯法
                                 if(judge(matrix,i,j,rows,cols,flag,str,0)){
                                     return true;
                                 }
                            }
                        }
                        return false;
        }
            //judge(初始矩阵,索引行坐标i,索引纵坐标j,矩阵行数,矩阵列数,待判断的字符串,字符串索引初始为0即先判断字符串的第一位)
                private boolean judge(char[] matrix,int i,int j,int rows,int cols,boolean[] flag,char[] str,int k){
                    //先根据i和j计算匹配的第一个元素转为一维数组的位置
                    int index = i*cols+j;
                    //递归终止条件
                    if(i<0 || j<0 || i>=rows || j>=cols || matrix[index] != str[k] || flag[index] == true)
                        return false;
                    //若k已经到达str末尾了,说明之前的都已经匹配成功了,直接返回true即可
                    if(k == str.length-1)
                        return true;
                    //要走的第一个位置置为true,表示已经走过了
                    flag[index] = true;

                    //回溯,递归寻找,每次找到了就给k加一,找不到,还原
                    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;
                }

    }

知识点:

    1. 本题是回溯法的相关问题:回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

    2. 回溯法是从上到下进行深度搜索,如果深度搜索没有进行到底而不满足决策函数了,那么不好意思,请回去,然后再从最近的可以岔开的地方选择另一条路,继续之前的深度搜索,如果搜索到底,那么再通过for循环进行广度搜索。所以它也是深度搜索和广度搜索并行的。求出的解也一定是最优解。回溯法是比动态规划法更加一般的算法,如n皇后,子集和数问题!其实回溯法就是对隐式图的深度优先搜索算法。

面试题68:机器人的运动

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

        public class Question68 {
                public int movingCount(int threshold, int rows, int cols) {
                        int flag[][] = new int[rows][cols]; //记录是否已经走过
                        return helper(0, 0, rows, cols, flag, threshold);
                    }

                    private int helper(int i, int j, int rows, int cols, int[][] flag, int threshold) {
                        if (i < 0 || i >= rows || j < 0 || j >= cols || numSum(i) + numSum(j)  > threshold || flag[i][j] == 1) return 0;    
                        flag[i][j] = 1;
                        return helper(i - 1, j, rows, cols, flag, threshold)
                            + helper(i + 1, j, rows, cols, flag, threshold)
                            + helper(i, j - 1, rows, cols, flag, threshold)
                            + helper(i, j + 1, rows, cols, flag, threshold)
                            + 1;
                    }

                    private int numSum(int i) {
                        int sum = 0;
                        do{
                            sum += i%10;
                        }while((i = i/10) > 0);
                        return sum;
                    }
        }

猜你喜欢

转载自blog.csdn.net/Katrina_ALi/article/details/81193032