1、二维数组的查找

题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

思路:因为该数组中的元素是行递增、列递增,那么数组矩阵中右上角的值就是行中的最大值,列中的最小值。我们可以右上角的值为起点进行搜索,判断该值与目标值的大小,如果大于目标值,就将数组的行小标左移一位,如果小于目标值,将数组的右小标右移一位,一直循环匹配。

public class Solution {
    public boolean Find(int target, int [][] array) {
        int row=0;
        int col=array[0].length-1;
        int guessValue=0;
        while(row<=array.length-1&&col>=0){
            guessValue=array[row][col];
            if(guessValue==target){
                return true;
            }
            if(guessValue>target){
                col--;
            }
            else{
                row++;
            }
        }
      
        return false;
    }
}

2、替换空格

题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:这道算法的思路比较简单,最大的难点在于定位字符串中空格,定位空格后有两种方法可以解决①新建一个字符串数组,对原有的字符串数组进行一个个的进行筛选,是字符的直接复制到新数组,是空格的替换%20后加入到新数组。②对原有的字符串数组直接进行替换

代码1

public class Solution {
    public String replaceSpace(StringBuffer str) {
     if(str==null){
            return null;
        }
        StringBuffer sb=new StringBuffer();
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)==' '){
                sb.append('%');
                sb.append('2');
                sb.append('0');
            }else{
                sb.append(str.charAt(i));
            }
        }
        return sb.toString();
    }
}

代码2

public class Solution {
    public String replaceSpace(StringBuffer str) {
     if(str==null)
     {
        return null;
     }
     for(int i=0;i<str.length();i++)
     {
         char c = str.charAt(i);
         if(c==' ')
         {
             /**
             i:开始索引,包含此节点
             i+1:终止索引,不包含此节点
             "%20":要插入的字符串
             */

            str.replace(i,i+1,"%20");
         }
      }
      String newstr = str.toString();
      return newstr;   
    }
}

3、从头到尾打印链表

题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

思路:这道题目比较容易理解,我们需要从头到尾遍历整个链表上的元素,将遍历到的元素存放在一个list中,因为题目找中的要求是从头到尾,所以我们需要在新建一个数组result,将list数组从尾到头遍历并将遍历的元素存放在result上

秒杀剑指offer(01-10)_java

代码

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list=new ArrayList<>();
        ArrayList<Integer> result=new ArrayList<>();
        ListNode temp=listNode;
          while(temp!=null){
              list.add(temp.val);
              temp=temp.next;
          }
             for(int i=list.size()-1;i>=0;i--){
                 result.add(list.get(i));
             }
        return result;
        
    }
}

4、重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

思路:这道题需要有二叉树的一些知识,前序遍历的第一个元素为二叉树的根结点,在中序遍历序列中,以该元素的左右可以分为左子树,和右子树,在左右子树中又可以根据其左右子树的根结点结点不断的拆分新的的左右子树,所以我们可以用递归的思想来解决这道题

代码

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if(pre==null||in==null||pre.length!=in.length){
            return null;
        }
        return construct(pre,0,pre.length-1,in,0,in.length-1);
    }
    //将序列分为左右子树,进行重建
    public TreeNode construct(int[] pre,int ps,int pe,int[] in ,int is,int ie){
        if(ps>pe){
            return null;
        }
        int value=pre[ps];//根结点的值
        int index=is;
        //找打划分左右子树的位置
        while(index<ie&&in[index]!=value){
            index++;
        }
        if(index>ie){
            throw new RuntimeException("invalid input");
        }
        //创建当前的根结点,并为结点赋值
        TreeNode node=new TreeNode(value);
        //左子树的个数为index-is
        //中序遍历左子树的范围为:[is,index-1],右子树的范围为[index+1,ie]
        //前序遍历左子树的范围为[ps+1,ps+index-is],右子树的范围为[ps+index-is+1,pe]
        node.left=construct(pre,ps+1,ps+index-is,in,is,index-1);
        node.right=construct(pre,ps+index-is+1,pe,in,index+1,ie);
        return node;
    }
}

5、用两个栈实现队列

题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。队列中的元素为int类型。

思路:我们需要先理解栈和队列的一些特性,栈是先入后出,队列是先入先出,所有用两个栈构建队列时,入队时,只需要将元素加入到某一个栈stack1即可,出栈时,需要将stack1中的元素弹出到另一个栈stack2中,获取到弹出的值后,为了保障下一次弹出的顺序,需要将stack2中的元素重新弹出到stack1中。

图解

  • 假设分别将1、2、3、4入队,此时的入队push就和入栈等同
秒杀剑指offer(01-10)_java_02
  • 从对列中弹出元素值为4,pop操作
秒杀剑指offer(01-10)_java_03
  • 为了保障下次的正确添加与弹出,需要恢复栈操作
秒杀剑指offer(01-10)_java_04

代码

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() {
  while(!stack1.isEmpty()){
      stack2.push(stack1.pop());
     
  }
        int returnValue=stack2.pop();
        while(!stack2.isEmpty()){
            stack1.push(stack2.pop());
        }
        return returnValue;
        
         
    }
}

6、旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路:这道题如果用暴力查找是很容易解决的,但是时间复杂度太高了,因为数组是非递减排序的,所以很容易想到用二分查找,又因为在原来的数组上进行了旋转,所以在该数组中存在两段非递减序列,所以在使用二分查找是需要考虑以下情况:

  • array[mid]>array[right]

表明数组中的最小值在右半部分,所以令left=mid+1

  • array[mid]==array[right]

这种情况很难判断最小值是在左边还是右边,如在左边:11011111,若在右边11111101,所以需要一个个比较判断,令right=right-1

  • array[mid]<array[right]

这种情况最小值可能是在中间array[mid],也有可能在右边,因为右边是递增的,所以令right=mid

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
            if(array==null){
              return 0;
    }
        int left=0;
        int right=array.length-1;
        
        while(left<right){
            int mid=(left+right)/2;
           if(array[mid]>array[right]){
               left=mid+1;
           }
            else if(array[left]==array[mid]){
                right=mid-1;
            }else{
                right=mid;
            }
    }
        return array[left];
}
}

7、斐波那契数列

题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。n<=39

思路:这道题目比较简单,采用递归的就可以直接求出来,递归重要的思想是要找到结束条件,这道题目的结束条件是n<=2。

代码

public class Solution {
    public int Fibonacci(int n) {
     if(n<=0){
         return 0;
     }else if(n==1||n==2){
         return 1;
     }
       else{
            return Fibonacci(n-1)+Fibonacci(n-2);
        }
      
    }

}

8、跳台阶

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

思路:刚开始看到这道题时,是没有什么思路的,所以我们先找一下规律,发现

1阶台阶:1种跳法

2阶台阶:2种跳法

3阶台阶:3种跳法

4阶台阶:5种跳法

n阶台阶:f(n)=f(n-1)+f(n-2)

我们通过规律发现跳台阶问题就是斐波那契问题的变种。

代码

public class Solution {
    public int JumpFloor(int target) {
        int one=1;
        int two=2;
        if(target==1){
            return 1;
        }
        else if(target==2){
            return 2;
        }
        else{
            return JumpFloor(target-1)+JumpFloor(target-2);
        }
    }
      
    }

9、变态跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法

思路:这道题和普通跳台阶不同的地方在于青蛙可以任意跳任意阶数,我们依旧找规律来归纳我们的算法,假设一共有n阶台阶:

  • 假设青蛙跳1阶台阶,还剩n-1阶台阶,则剩下的跳法就为f(n-1)
  • 假设青蛙跳2阶台阶,还剩n-2阶台阶,则剩下的跳法就为f(n-2)
  • 依次类推,直到青蛙依次跳n阶台阶

我们的推导公式为:

f(n)=f(n-1)+f(n-2)+.....+f(1),因为f(n-1)=f(n-2)+f(n-3)+...f(1)

所以f(n)=2*f(n-1),其中f(1)=1,f(2)=2

所以这道题目可以用两种方法来做,一种是循环,另一种使用递归

循环

public class Solution {
    public int JumpFloorII(int target) {
        if(target==0){
            return 0;
        }
        if(target==1){
            return 1;
        }
        int a=1,b=2;
        for(int i=2;i<=target;i++){
            b=2*a;
            a=b;
        }
        return b;
    }
}

递归

public class Solution {
    public int JumpFloorII(int target) {
        if (target <= 0) {
            return -1;
        } else if (target == 1) {
            return 1;
        } else {
            return 2 * JumpFloorII(target - 1);
        }
    }
}

10、矩形覆盖

题目:我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

比如n=3时,2*3的矩形块有三种覆盖方法

秒杀剑指offer(01-10)_java_05

思路:对于这种题目,最简单的方式就是找规律然后归纳出一个通用的算法公式,我们先找规律

  • n=1时,有1种方法
秒杀剑指offer(01-10)_java_06
  • n=2,有2种方法
秒杀剑指offer(01-10)_java_07
  • n=3,有3种算法
秒杀剑指offer(01-10)_java_08
  • n=4,有5种算法
秒杀剑指offer(01-10)_java_09
  • n=5,有8种算法

依次类推,可以看到这符合斐波那契算法,所以f(n)=f(n-1)+f(n-2)(n>2时)

代码

public class Solution {
    public int RectCover(int target) {
        if(target<1){
            return 0;
        }
        else if(target==1||target==2){
            return target;
        }
        else{
            return RectCover(target-1)+RectCover(target-2);
        }
    }
}