剑指Offer编程题-思路、代码

一、二维数组中的查找

  1. 题目描述:
    在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
    考点:数组
  2. 解题思路:
    第一种方法:这道题如果不考虑任何算法和效率,可以直接遍历整个数组,然后查找,但是不建议用此方法。
    第二种方法:一般出此题目就是考算法,需要根据二维数组的特性进行分析,从而实现最快速的查找。首先根据数组中的数据规律对数据数据做拆分,找到可用于比较又不会缺失比较的基准数。根据题目所说的上到下、左到右递增的特性,考虑二维数组四个端点的值,分析:左下角、右上角端点开始比较的话,只有一个分支非大即小,左上角、右下角端点,会有两条分支无法判定大小走向。确定好基准数后,用传入的整数与任意一个基准数值进行计较,然后根据比较结果,在二维数组中左右上下移动比较,最终确定二维数据中是否包含该整数。这样的思路避免了不必要的判断,不用遍历整个数据,是最优的时间复杂度。
  3. 代码实现:
    1. 左下角开始:
      public class Solution {
          public boolean Find(int target, int [][] array) {
             int height=array.length-1;
             int weight=0;
              while(height >= 0 && weight <= array[0].length-1){
                  if(target > array[height][weight]){
                      weight++;
                  }else if(target < array[height][weight]){
                      height--;
                  }else{
                      return true;
                  }
              }
              return false;
          }
      }
      
    2. 右上角开始:
      public class Solution {
          public boolean Find(int target, int [][] array) {
              int weight=array[0].length-1;
              int height=0;
              while(weight >= 0 && height <= array.length-1){
                  if(target > array[height][weight]){
                      height++;
                  }else if(target < array[height][weight]){
                      weight--;
                  }else{
                      return true;
                  }
              }
              return false;
          }
      }
      

二、替换空格

  1. 题目描述:
    请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
    考点:字符串
  2. 解题思路:
    第一种方法:直接使用java.lang.String的replaceAll()方法进行替换,但是一般此题考的是算法,所以需要手动写一个算法实现此功能。
    第二种方法:先把字符串替换成字符数组,然后逐一进行比较替换。
  3. 代码实现:
    1. 第一种方法:
      public class Solution {
          public String replaceSpace(StringBuffer str) {
          	String s1=str.toString();
              String s2=s1.replaceAll(" ","%20");
              return s2;
          }
      }
      
    2. 第二种方法:
      public class Solution {
          public String replaceSpace(StringBuffer str) {
          	char[] charArr=str.toString().toCharArray();
              StringBuffer sbf=new StringBuffer();
              for(int i=0;i<charArr.length;i++){
                  if(' ' == charArr[i]){
                      sbf.append("%20");
                  }else{
                      sbf.append(charArr[i]);
                  }
              }
              return sbf.toString();
          }
      }
      

三、从尾到头打印链表

  1. 题目描述:
    输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
    考点:链表
  2. 解题思路:
    第一种方法:因为从尾到头的特性,考虑可以使用栈的后入先出的特性,先把链表压入栈中,然后依次从栈里取进行填充ArrayList,就实现了从尾到头打印链表的功能。
    第二种方法:可以使用递归方法,就是不断的调用自己本身方法,然后根据一个递归调用结束的判断来结束最后一层的递归调用,最后一层即链表的最后一个节点,然后在最后一层的时候把节点数据放入ArrayList,这就是ArrayList中第一个数据了,然后会依次回归刚才的调用填充ArrayList,这样链表的最后一个节点数据就是ArrayList中第一个数据,第一个节点的数据就是ArrayList中最后一个数据,这样就实现了从尾到头打印链表的功能。
    第三种方法:使用ArrayList的add(0,x)的方法,这样每回节点都会放在ArrayList的第一位,但是此种方法较简单。
  3. 代码实现:
    1. 第一种方法:
      import java.util.ArrayList;
      import java.util.Stack;
      public class Solution {
          public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
              ArrayList<Integer> arrList=new ArrayList<Integer>();
              Stack<Integer> stack=new Stack<Integer>();
              while(listNode != null){
                  stack.push(listNode.val);
                  listNode=listNode.next;
              }
              while(!stack.isEmpty()){
                  arrList.add(stack.pop());
              }
              return arrList;
          }
      }
      
    2. 第二种方法:
      public class Solution {
          ArrayList<Integer> arrList=new ArrayList<Integer>();
          public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
              if(listNode != null){
                  this.printListFromTailToHead(listNode.next);
                  arrList.add(listNode.val);
              }
              return arrList;
          }
      }
      
    3. 第三种方法:
      public class Solution {
          public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
              ArrayList<Integer> arrList=new ArrayList<Integer>();
              while(listNode != null){
                  arrList.add(0,listNode.val);
                  listNode=listNode.next;
              }
              return arrList;
          }
      }
      

四、重建二叉树

  1. 题目描述:
    输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
    考点:树
  2. 解题思路:
    已知二叉树的先序中序遍历可以确定二叉树的形状,根据先序遍历的特性:(根左右)、中序遍历的特性(左根右)可知:1即二叉树的根、472为左子树、5386为右子树,具体左子树、右子树的顺序根据先序遍历确定,此过程是递归的过程。
    PS:为保证程序的健壮性,最好先对传入数组进行判定。
  3. 代码实现:
    public class Solution {
       public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
            if(pre.length <= 0 || in.length <=0 || pre.length != in.length){
                return null;
            }
            return reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
        }
        
        private TreeNode reConstructBinaryTree(int[] pre,int preStart,int preEnd,int[] in,int inStart,int inEnd){
            if(preStart > preEnd || inStart > inEnd){
                return null;
            }
            TreeNode root=new TreeNode(pre[preStart]);
            for(int i=inStart;i<=inEnd;i++){
                if(in[i]==pre[preStart]){
                    //遍历左子树
                    /*preStart+1,指的是前序遍历左子树的开始位置,即前序遍历根节点的下一个节点。
                      i-inStart,i指的是中序遍历根节点的位置,减去中序遍历开始位置即左子树的长度。所以preStart+i-inStart是前序遍历左子树结束的位置。
                      startPre+i-startIn+1,即从startPre位置越过左孩子及其子节点的偏移值再往下一个节点走。
                     */
                    root.left=reConstructBinaryTree(pre,preStart+1,preStart+i-inStart,in,inStart,i-1);
                    //遍历右子树
                    root.right=reConstructBinaryTree(pre,preStart+i-inStart+1,preEnd,in,i+1,inEnd);
                    break;
                }
            }
            return root;
        }
    }
    

五、用两个栈实现队列

  1. 题目描述:
    用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
    考点:栈和队列
  2. 解题思路:
    根据栈的特性:先进后出,队列的特性:先进先出,进行分析。
    如果用两个栈来实现一个队列,利用栈的特性,先把数据存入一个栈,然后再把数据从这个栈中全部取出存入另一个栈,然后再取出,这样就实现了队列的特性。
    可以把两个栈看成两个容器,一个容器做存的操作,一个容器做取的操作,需要注意的是对于栈的操作需要一次性操作全部的数据。
  3. 代码实现:
    import java.util.Stack;
    
    public class Solution {
        Stack<Integer> stack1 = new Stack<Integer>();
        Stack<Integer> stack2 = new Stack<Integer>();
        
        public void push(int node) {
            stack1.push(node);
        }
        
        public int pop() {
            if(stack1.isEmpty() && stack2.isEmpty()){
                throw new RuntimeException("stack1、stack2同时为空,不被允许访问pop方法");
            }
            if(stack2.isEmpty()){
                while(!stack1.isEmpty()){
                    stack2.push(stack1.pop());
                }
            }
            return stack2.pop();
        }
    }
    

六、旋转数组的最小数字

  1. 题目描述:
    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
    输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
    例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
    NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
    考点:查找和排序
  2. 解题思路:
    首先需要明确非递减排序的数组的概念,然后根据题目,列出旋转后可能出现的情况。
    为了提高算法的效率,采用二分法进行查找,二分法查找一定会有中点,用中点作为基准值列举以下旋转后出现的情况。
    模拟指针移动,设数组起始为start,结束为end,中点为mid。
    int start=0;int end=array.length-1;int mid=start+(end-start)/2;
    情况1:array[mid] > array[end]:出现这种情况的array类似 [4,5,6,7,1,2] ,此时最小数字一定在mid的右边。
    此情况start = mid + 1
    情况2:array[mid] == array[end]:出现这种情况的array类似 [2,1,2,2,2,2] 、[2,2,2,2,1,2],此时最小数字不好判断在mid左边还是右边,这时只好一个一个试 。
    此情况end= end -1
    情况3:array[mid] < array[high]:出现这种情况的array类似[4,5,6,1,2,3]、[4,5,1,2,2,3],此时最小数字一定就是array[mid]或者在mid的左边。因为右边必然都是递增的。
    此情况end=mid;
  3. 代码实现:
    import java.util.ArrayList;
    public class Solution {
        public int minNumberInRotateArray(int [] array) {
            if(array == null || array.length==0){
                return 0;
            }
            int start=0;
            int end=array.length - 1;
            while(start < end){
                int mid=start+(end-start)/2;
                if(array[mid]>array[end]){
                    start=mid+1;
                }else if(array[mid]==array[end]){
                    end=end-1;
                }else{
                    end=mid;
                }
            }
            return array[start];
        }
    }
    

七、斐波那契数列

  1. 题目描述:
    大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
    n<=39
    考点:递归和循环
  2. 解题思路:
    此题可以使用递归和迭代,因为此题注明n<=39,若不注明,不建议使用递归,因为递归可能会造成栈内存溢出。
    所以,考虑使用迭代循环赋值的方式。
  3. 代码实现:
    public class Solution {
        public int Fibonacci(int n) {
            if(n <= 1){
                return n;
            }
            int result=0;
            int one=0;
            int two=1;
            for(int i=2;i<=n;i++){
                result=one+two;
                one=two;
                two=result;
            }
            return result;
        }
    }
    

八、跳台阶

  1. 题目描述:
    一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
    考点:递归和循环

  2. 解题思路:

  3. 代码实现:

你可能感兴趣的:(Java编程,剑指offer题解,数据结构算法)