剑指offer(java实现)(1-->11)

剑指offer这本书,做点笔记吧,以后方便自己再查看,文中也有很多借鉴,在此就不一一引述了。

1、singleton 设计一个类,我们只能生成该类的一个实例

**effective java中推荐

public class Singleton{
    private static class SingletonHolder{
        private static final Singleton INSTANCE= new Singleton();
    }
    private Singleton(){}//私有构造
    public static final Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

**饱汉模式(优点:实现简单;缺点:在不需要的时候,白创建了对象,造成资源的浪费)

public class Singleton{
    private singleton(){}
    private final static SingleTon instance = new SingleTon();  
    public static SingleTon getInstance(){  
        return instance;  
    }   

}

**懒汉式(优点:需要对象的时候才创建;缺点:线程不安全)

public class SingleTon {  
    private SingleTon(){}  
    private static instance = null;//new SingleTon();  
    public static synchronized SingleTon getInstance(){  
        if(instance == null){  
            instance = new SingleTon();  
        }  
        return instance;  
    }  
}  

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

解析:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。
// 二维数组matrix中,每一行都从左到右递增排序,
// 每一列都从上到下递增排序
public static bool find(int[][] matrix, int rows, int columns, int number)
{
    bool isFind = false;
    if (matrix != null && rows > 0 && columns > 0){
        int row = 0;   // 从第一行开始
        int column = columns - 1;   // 从最后一列开始
        // 行:从上到下,列:从右到左
        while (row < rows && column >= 0){
            if (matrix[row, column] == number){
                isFind = true;
                break;
            }
            else if (matrix[row, column] > number){
                column--;
            }
            else{
                row++;
            }
        }
    }
    return isFind;
}

3、请实现一个函数,把字符串中的每个空格替换成”%20”。例如输入“We are happy.”,则输出“We%20are%20happy.”。

解析:

Step1.先遍历一次字符串,这样就能统计出字符串中空格的总数,并可以由此计算出替换之后的字符串的总长度。以前面的字符串”We arehappy.”为例,”We are happy.”这个字符串的长度是14(包括结尾符号’\0’),里面有两个空格,因此替换之后字符串的长度是18。

Step2.从字符串的后面开始复制和替换。准备两个指针,P1和P2。P1指向原始字符串的末尾,而P2指向替换之后的字符串的末尾。接下来向前移动指针P1,逐个把它指向的字符复制到P2指向的位置,直到碰到第一个空格为止。接着向前复制,直到碰到第二、三或第n个空格。

public static void ReplaceBlank(char[] target, int maxLength){
    if (target == null || maxLength <= 0){
        return;
    }

    // originalLength 为字符串target的实际长度
    int originalLength = 0;
    int blankCount = 0;
    int i = 0;

    while (target[i] != '\0'){   //判断是否结束
        originalLength++;
        // 计算空格数量
        if (target[i] == ' '){
            blankCount++;
        }
        i++;
    }

    // newLength 为把空格替换成'%20'之后的长度
    int newLength = originalLength + 2 * blankCount;
    if (newLength > maxLength){
        return;
    }

    // 设置两个指针,一个指向原始字符串的末尾,另一个指向替换之后的字符串的末尾
    int indexOfOriginal = originalLength;
    int indexOfNew = newLength;

    while (indexOfOriginal >= 0 && indexOfNew >= 0){
        if (target[indexOfOriginal] == ' '){
            target[indexOfNew--] = '0';
            target[indexOfNew--] = '2';
            target[indexOfNew--] = '%';
        }
        else{
            target[indexOfNew--] = target[indexOfOriginal];
        }

        indexOfOriginal--;
    }
}

4、输入一个链表的头结点,从尾到头反过来打印出每个结点的值。

解析:

首先遍历链表。遍历的顺序是从头到尾,输出的顺序为从尾部到头部,即第一个遍历的最后输出,最后遍历的第一个输出。后进先出(栈结构)。
每经过一个结点的时候,把该结点放到一个栈中。当遍历完整个链表后,再从栈顶开始逐个输出结点的值,此时输出的结点的顺序已经反转过来了。

//链表的结构类定义
public class ListNode { 
    int val; 
    ListNode next = null; 
    ListNode(int val) { 
        this.val = val; 
    } 
} 
//(1)使用额外的一个ArrayList存储链表
 public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list=new ArrayList<Integer>();
        ListNode head=listNode;
        while(head!=null) {
            list.add(head.val);
            head=head.next;
        }
        ArrayList<Integer> result=new ArrayList<Integer>();
        for(int i=list.size()-1;i>=0;i--) {
            result.add(list.get(i));
        }
        return result;
}
//(2)单向链表通过栈(stack)结构实现
public static ArrayList<Integer> printListFromTailToHeadWithStack(ListNode listNode) {
    Stack<Integer> stack = new Stack<Integer>();
    ArrayList<Integer> list=new ArrayList<Integer>();
    ListNode head=listNode;
    while(head!=null) {
        stack.push(head.val);
        head=head.next;
    }
    while(!stack.isEmpty()) {
        Integer item=stack.pop();
        list.add(item);
    }
    return list;
}

5、重建二叉树(输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{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) {  
        return build(pre,in,0,pre.length-1,0,in.length-1);          
    }  

    public TreeNode build(int[] pre,int[] in,int pstart,int pend,int istart,int iend){  
        if(pstart>pend) return null;          
        int cur = pre[pstart];//根节点  
        int find = istart;  
        while(find<=iend){//在中序遍历中查找根节点  
            if(cur==in[find]) break;  
            else find++;  
        }  
        int len = find-istart;  
        TreeNode res = new TreeNode(cur);//创建根节点  
        res.left = build(pre,in,pstart+1,pstart+len,istart,find-1);//创建左子树  
        res.right = build(pre,in,pstart+len+1,pend,find+1,iend);//创建右子树  
        return res;  
    }  
}  

6、用两个栈实现一个队列。分别完成在队列尾部插入结点和在队列头部删除结点的功能。

解析

一个队列包含了两个栈stack1和stack2,操作这两个“先进后出”的栈实现一个“先进先出”的队列CQueue。对于两个栈而言,对于插入操作,都先插入到stack1,也就实现了入队操作。但是出队操作,则需要借助stack2的过渡:当stack2中不为空时,在stack2中的栈顶元素是最先进入队列的元素,可以弹出。如果stack2为空时,我们把stack1中的元素逐个弹出并压入stack2。由于先进入队列的元素被压到stack1的底端,经过弹出和压入之后就处于stack2的顶端了,又可以直接弹出。

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.add(node);
    }

    public int pop() {
        if(stack2.isEmpty()){  //stack2为空的话,将把stack1中的放入到stack2中
            while(!stack1.isEmpty()){
                stack2.add(stack1.pop());
            }    
        }          
        return stack2.pop();        
    }
}

7、旋转数组的最小数字(把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减序列的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1)

解析

旋转数组可以划分为两个排序的子数组,前面的子数组的元素都大于或者等于后面子数组的元素,最小元素恰好是这两个数组的分界线。可以通过二分查找法实现。取中间mid元素,如果array[mid]>array[end],说明最小元素应该在mid的右边,因为我们实际是要找没有旋转之前的第一个元素,观察数组规律可以知道,在mid的右边如果array[mid]

8、斐波那契数列 定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)

解析:递归解决的话,由于重复计算导致很慢,解决办法就是从下往上计算。首先根据f(0)和f(1)计算出f(2),再根据f(1)和f(2)计算出f(3)……时间复杂度为O(n)。
public class Solution{
    public int Fibonacci(int n){
        int result[2]={0,1}
        if(n<2)
            return result[n];
        int fibNminusOne=1;
        int fibNminusTwo=0;
        int fibN=0;
        for(int i=2;i<=n;i++){
            fibN=fibNminusOne+fibNminusTwo;
            fibNminusTwo=fibNminusTwo;
            fibNminusOne=fibN;
        }
        return fibN;
    }
}

9、二进制中1的个数(输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。)

解析:把一个整数减去1,再和原整数做与运算,会把改整数最右边一个1变成0.一个整数的二进制表示中有多少个1,就可以进行多少次这样的运算。(p81)
public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n!=0){   
            n=(n&(n-1));
            count++;
        }
        return count;
    }
}

10、数值的整数次方(给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。)

//(1)递归
public class Solution {
    public double Power(double base, int exponent) {        
        if(exponent<0){
            exponent = -exponent;
            return 1/solve(base,exponent);
        }
        return solve(base,exponent);
      }

    public double solve(double base, int exponent){
        if(exponent==0) return 1;
        if(exponent%2==1){
            return base*solve(base,(exponent-1)/2)*solve(base,(exponent-1)/2);
        }else{
            return solve(base,exponent/2)*solve(base,exponent/2);
        }
    }
}


//(2)
public class Solution {
    public double Power(double base, int exponent) {
        // 当底数为0,指数为负数时,则抛出异常或者返回0.0
        if (equal(base, 0) && exponent < 0) {
            return 0.0;
        }

        // 先对指数进行取绝对值计算
        int absExponent = Math.abs(exponent);
        double result = powerWithExponent(base, absExponent);

        // 判断如果传入的指数是负数,进行取反,否则直接返回
        if (exponent < 0) {
            result = 1.0 / result;
        }
        return result;
    }

    // 计算数值的整数次方
    public double powerWithExponent(double base, int absExponent) {
        double result = 1.0;
        for (int i = 1; i <= absExponent; ++i) {
            result *= base;
        }
        return result;
    }

    // 判断两个double类型的数值是否相等
    public boolean equal(double num1, double num2) {
        if ((num1 - num2 > -0.0000001) && (num1 - num2 < 0.0000001)) {
            return true;
        } else
            return false;
    }

}

11、打印1到最大的n位数(输入数字n,按顺序打印出从1到最大的n位十进制数,比如输入3,则打印出1,2,3一直到最大的3位数即999)

解析:用数字排列的方法表示:如果我们在数字前面补0的话,就会发现n位所有十进制数其实就是n个从0到9的全排列。也就是说,我们把数字的每一位都从0到9排列一遍,就得到了所有的十进制数。当然打印的时候,我们应该将前面的0补位去掉。
public static void Print1ToMaxOfNDigits_3(int n){
    if(n < 0){
        return;
    }
    StringBuffer s = new StringBuffer(n);
    for(int i = 0; i < n; i++){
        s.append('0');
    }
    for(int i = 0; i < 10; i++){

        s.setCharAt(0, (char) (i+'0'));
        Print1ToMaxOfNDigits_3_Recursely(s, n, 0);
    }

}

public static void Print1ToMaxOfNDigits_3_Recursely(StringBuffer s, int n , int index){
    if(index == n - 1){
        PrintNumber(s);
        return;
    }

    for(int i = 0; i < 10; i++){
        s.setCharAt(index+1, (char) (i+'0'));
        Print1ToMaxOfNDigits_3_Recursely(s, n, index+1);
    }
}

public static void PrintNumber(StringBuffer s){
    boolean isBeginning0 = true;
    for(int i = 0; i < s.length(); i++){
        if(isBeginning0 && s.charAt(i) != '0'){
            isBeginning0 = false;
        }
        if(!isBeginning0){
            System.out.print(s.charAt(i));
        }
    }
    System.out.println();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值