剑指offer第三题至第九题(java详解)

第三题

/**
 * 题目:在一个二维数组中,每一行都按照从左到右的递增顺序排序,
 *  每一列都按照从上到下的递增顺序排序。请完成一个函数,输入
 *  这样一个二位数组和一个整数,判断数组中是否含有该整数,含有
 *  就返回true,否则返回false。
 * 
 * 解题思路:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找结束;
 *  如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数
 *  字,剔除这个数字所在的行。再从右上角的数字开始,每一步都可以缩小查找范围,
 *  直到找到数字,或者查找范围为空。(注意不能选取左上角或者右下角的数据开始)
 * 
 *
 */
public class NO3 {
    public static void main(String[] args) {
        int[][] arr = { { 1, 2, 8, 9 }, 
                { 2, 4, 9, 12 }, 
                { 4, 7, 10, 13 }, 
                { 6, 8, 11, 15 } };
        System.out.println(search(arr, 4));
    }

    public static boolean search(int[][] arr, int key) {
        int i = 0;
        int j = arr[0].length - 1;
        while (i <= arr.length - 1 && j >= 0) {
            if (arr[i][j] == key)
                return true;

            if (arr[i][j] > key)
                j--;
            else
                i++;
        }
        return false;
    }
}

第四题

package com.jiaohanhan.swordtooffer;
/**
 * 问题描述:请实现一个函数,把字符串中的每个空格替换成"%20".
 *          输入:"We are happy"
 *          输出:"We%20are%20happy"
 * 
 * 解题思路:先通过一次遍历字符串计算出含有的空格数,然后new一个数组
 *          长度为原数组的字符串长度+空格数*2,因为一个"%20"等于3空
 *          格字符的长度;然后就逆序把字符串拷贝到数组中,每遇到空格
 *          就用"%20"替换
 * 
 * 解题思路二:可以直接用StringBuilder,在O(n)时间就能完成
 *
 */
public class NO4 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String s = "we are happy";

        char[] charArr = s.toCharArray();
        System.out.println(change1(charArr));

        System.out.println(change2(s));

    }

    public static String change1(char[] charArr) {

        int count = 0;          // 统计空格数
        for (int i = 0; i < charArr.length; i++)
            if (charArr[i] == ' ')
                count++;
        // 容错处理
        if (count == 0)
            return null;

        char[] temp = new char[charArr.length + 2 * count];

        int j = temp.length - 1;    // 返回结果的尾指针
        int i = charArr.length - 1; // 原字符串的尾指针

        while (i >= 0) {
            if (charArr[i] == ' ') {
                temp[j] = '0';
                temp[j - 1] = '2';
                temp[j - 2] = '%';
                j = j - 3;
            } else {
                temp[j] = charArr[i];
                j--;
            }
            i--;    // i--要放在判定条件的外面,i是扫描指针
        }

        return new String(temp);
    }

    public static String change2(String s) {

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == ' ')
                sb.append("%20");
            else
                sb.append(s.charAt(i));
        }
        return sb.toString();
    }

}

第五题

package com.jiaohanhan.swordtooffer;

import java.util.Stack;

/**
 * 题目描述:输入一个链表的头结点,从尾到头反过来打印出每个结点的值
 * 
 * 解题思路1:递归反转,用头指针和next找到最后一个结点,逆序打印,缺点
 *      链表很长的时候,容易造成溢出。优点:没有改变链表的结构。
 * 
 * 解题思路2:直接改变链表的结构,变成逆向的链表,即改变next指针的取反
 * 
 * 解题思路3:用栈保存每一步遍历链表得到的结点,再弹出栈
 * 
 *
 */
public class NO5 {
    public static void main(String[] args) {
        Node node1 = new Node("A");
        Node node2 = new Node("B");
        Node node3 = new Node("C");
        Node node4 = new Node("D");
        Node node5 = new Node("E");
        node1.setNext(node2);
        node2.setNext(node3);
        node3.setNext(node4);
        node4.setNext(node5);

        System.out.print("解题思路1:");
        reverse1(node1);
        System.out.println();

        System.out.print("解题思路3:");
        reverse3(node1);
        System.out.println();

        System.out.print("解题思路2:");
        reverse2(node1);

    }

    // 递归反转
    public static void reverse1(Node head) {
        if (head != null) {
            if (head.getNext() != null)
                reverse1(head.getNext());   // 这一步其实就是head = head.getNext();
                                            // reverse1(head);
        }
        System.out.print(head.getData() + " ");

    }

    // 非递归反转
    public static void reverse2(Node head) {
        Node pre = head;
        Node cur = head.getNext();
        Node temp;
        while (cur != null) {
            temp = cur.getNext();
            cur.setNext(pre);
            pre = cur;
            cur = temp;
        }
        head.setNext(null);

        while (pre != null) {
            System.out.print(pre.getData() + " ");
            pre = pre.getNext();
        }
    }

    // 利用栈缓存每一步的得到的结点
    public static void reverse3(Node head) {
        Stack<Node> stack = new Stack<>();
        while (head != null) {
            stack.push(head);
            head = head.getNext();
        }
        while (!stack.isEmpty())
            System.out.print(stack.pop().getData() + " ");
    }
}

class Node {
    private String data;
    private Node next;

    public Node(String data) {
        super();
        this.data = data;
    }

    public Node(String data, Node next) {
        super();
        this.data = data;
        this.next = next;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

}

第六题

package com.jiaohanhan.swordtooffer;

/**
 * 题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
 *      假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入
 *      前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1
 *      ,5,3,8,6},则重建出如下图所示的二叉树并输出它的头结点。
 * 
 *          1
 *      2       3
 *    4      5     6
 *       7       8  
 * 
 * 
 * 解题思路:前序遍历的第一个数字就是根结点的值。在中序遍历中根结点左侧的
 *      都是左子树的结点,在根结点右侧都是右子树。
 * 
 *
 */
public class NO6 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String preOrder = "12473568";
        String midOrder = "47215386";
        BiTree tree = new BiTree(preOrder, midOrder, preOrder.length());
        tree.postRootTraverse(tree.root);
    }

}

class BiTree{
    TreeNode root;

    public BiTree(String preOrder,String midOrder, int count){
        if(count <= 0)
            return;

        //找到根结点所在位置
        int i = 0;
        for(;i<count;i++){
            if(midOrder.charAt(i) == preOrder.charAt(0))
                break;
        }

        root = new TreeNode(preOrder.charAt(0));
        root.setLChild(new BiTree(preOrder.substring(1, i+1), 
                midOrder.substring(0,i), i).root);
        root.setRChild(new BiTree(preOrder.substring(i+1),
                midOrder.substring(i+1), count-i-1).root);
    }

    public void postRootTraverse(TreeNode root){
        if(root != null){
            postRootTraverse(root.getLChild());
            postRootTraverse(root.getRChild());
            System.out.print(root.getData());
        }
    }
}

class TreeNode {

    private char data;
    private TreeNode LChild;
    private TreeNode RChild;

    public TreeNode(char data){
        super();
        this.data = data;
    }

    public TreeNode(char data, TreeNode LChild, TreeNode RChild) {
        super();
        this.data = data;
        this.LChild = LChild;
        this.RChild = RChild;
    }

    public char getData() {
        return data;
    }

    public void setData(char data) {
        this.data = data;
    }

    public TreeNode getLChild() {
        return LChild;
    }

    public void setLChild(TreeNode LChild) {
        this.LChild = LChild;
    }

    public TreeNode getRChild() {
        return RChild;
    }

    public void setRChild(TreeNode RChild) {
        this.RChild = RChild;
    }



}

第七题(一)

package com.jiaohanhan.swordtooffer;

import java.util.Stack;

/**
 * 题目描述:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数
 *      appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头
 *      部删除结点的功能
 * 
 * 解题思路:给出的结构中含有两个栈,s1,s2,对于进队操作,可以直接向s1中
 *      压栈,那么对于出队来说就需要s2的参与,把s1中的元素全部弹出压入s2
 *      栈中,原先栈底的元素即队列的头元素,就跑到栈顶了,便可以直接进行
 *      弹出栈的操作,也就实现了出队的操作
 * 
 * 说明:这里用Object实现泛型,当然也可以通过  public class NO7<T>{ }实现
 *      泛型
 * 
 * @author 焦含寒
 *
 */
public class NO7 {

    private Stack s1 = new Stack();
    private Stack s2 = new Stack();

    public static void main(String[] args) {
        NO7 queue = new NO7();
        queue.appendTail("a");
        queue.appendTail("b");
        queue.appendTail("c");
        queue.deleteHead();
        queue.deleteHead();
        queue.deleteHead();
        queue.deleteHead();
    }

    @SuppressWarnings("unchecked")
    public void appendTail(Object x) {
        s1.push(x);
    }

    @SuppressWarnings("unchecked")
    public void deleteHead() {
        if (s1.size() == 0 && s2.size() == 0)
            try {
                throw new Exception("队列为空");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        else {
            if (s2.size() != 0) {
                System.out.println(s2.pop().toString()); 
            } else {
                while (s1.size() > 0)
                    s2.push(s1.pop());
                System.out.println(s2.pop().toString());
            }
        }

    }

}

第七题(二)

package com.jiaohanhan.swordtooffer;
/**
 * 题目描述:对一个有几万人的公司的员工的年龄排序
 * 
 * 解题思路:因为年龄都在0~99,所以是一个小范围排序,可以记录下
 *      每个年龄出现的次数,再处理
 * 
 * @author 焦含寒
 *
 */
public class NO7_5 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] employeeAge = {22,22,34,23,44,56,28,30,38,22,34,20,34,36};
        try {
            sortAges(employeeAge);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        for(int i=0;i<employeeAge.length;i++){
            System.out.print(employeeAge[i]+ " ");
        }
    }

    public static void sortAges(int[] ages) throws Exception{
        if(ages == null || ages.length == 0)
            return;

        int oldestAge = 99;
        int[] timesOfAge = new int[100];

        for(int i =0;i<timesOfAge.length;i++)
            timesOfAge[i] = 0;


        for(int i = 0;i<ages.length;++i){
            int age = ages[i];
            if(age < 0 || age > oldestAge)
                throw new Exception("年龄超出范围");
            ++timesOfAge[age];
        }

        int index = 0;
        for(int i=0;i<oldestAge;i++){
            for(int j = 0;j<timesOfAge[i];j++){
                ages[index] = i;
                ++index;
            }
        }
    }

}

第八题

package com.jiaohanhan.swordtooffer;
/**
 * 题目描述:把一个数组最开始的若干元素搬到数组的末尾,称之为数组的旋转。
 *      输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数
 *      组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1
 * 
 * 解题思路:假设我们持有两个指针,第一个指针left指向前面的递增数组的元素,
 *      第二个指针right指向第二个递增数组的的元素,初始化的时候第一个指针
 *      指向数组的第一个元素,第二个指针指向数组的最后一个元素,用二分法的
 *      思想,比较左右指针和中间元素mid的大小,要是比左边元素大的话就让
 *      left=mid,否则就让right=mid;要注意{2,2,2,0,1,2}这种情况的处理
 *      当left,mid,right指向的数据相等的时候就要处理这种情况了。
 * 
 * 
 * @author 焦含寒
 *
 */
public class NO8 {

    public static void main(String[] args) throws Exception {
        int[] arr = {3,4,5,1,2};        
        System.out.println(findMin(arr));


    }

    public static int findMin(int[] arr) throws Exception {
        if (arr == null || arr.length == 0)
            throw new Exception("数组为空");

        int left = 0;
        int right = arr.length - 1;

        if (arr[right] > arr[left])
            throw new Exception("非旋转数组");

        while (left < right) {
            int mid = (left + right) / 2;

            // 对于{1,0,1,1,1,1}之类情况的特殊处理
            if (arr[mid] == arr[left] && arr[left] == arr[right])
                return searchMin(arr, left, right);

            if (right - left == 1)
                break;

            if (arr[mid] >= arr[left])
                left = mid;
            else
                right = mid;
        }
        return arr[right];

    }

    private static int searchMin(int[] arr, int left, int right) {
        // TODO Auto-generated method stub
        int result = arr[left];
        for (int i = left + 1; i <= right; ++i)
            if (arr[i] < result)
                result = arr[i];

        return result;
    }

}

第九题(一)

package com.jiaohanhan.swordtooffer;
/**
 * 题目描述:写一个函数,输入n,求斐波那契数列的第n项
 * 
 * 解题思路:递归最直白,效率有限;可以动态规划,就是有点浪费空间,
 *          也可以保存上一步的值再求解。
 *          当然O(logn)的复杂度的算法也是有的
 * 
 * 
 * @author 焦含寒
 *
 */
public class NO9_1 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(fibnacci(10));

    }

    public static long fibnacci(int n) {
        long[] a = { 0, 1 };
        if (n < 2)
            return a[n];

        long fib1 = 0;
        long fib2 = 1;
        long fibN = 0;

        for (int i = 2; i <= n; i++) {
            fibN = fib1 + fib2;
            fib1 = fib2;
            fib2 = fibN;
        }

        return fibN;
    }

}

第九题(二)

package com.jiaohanhan.swordtooffer;



/**
 * 题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。
 *      求该青蛙跳上一个n级台阶总共有几种跳法。
 * 
 * 解题思路:类似斐波那契数列,可以递归,也可以动态规划
 * 
 * @author 焦含寒
 *
 */
public class NO9_2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(count1(10));
        System.out.println(count2(10));
        System.out.println(count3(10));
        System.out.println(count4(10));
    }

    // 递归
    public static int count1(int n) {
        if (n <= 0)
            throw new IllegalArgumentException("非法参数");
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;

        return count1(n - 1) + count1(n - 2);

    }

    // 动态规划 空间换时间
    public static int count2(int n) {
        if (n <= 0)
            throw new IllegalArgumentException("非法参数");
        int[] arr = new int[n + 1];
        arr[1] = 1;
        arr[2] = 2;
        for (int i = 3; i < n + 1; i++) {
            arr[i] = arr[i - 1] + arr[i - 2];
        }

        return arr[n];
    }

    // 动态规划的优化,只保留最近三次的数据
    public static int count3(int n) {
        if (n <= 0)
            throw new IllegalArgumentException("非法参数");
        int[] arr = new int[3];
        arr[1] = 1;
        arr[2] = 2;
        for (int i = 3; i < n + 1; i++) {
            // 用arr[0]保存结果
            arr[0] = arr[1] + arr[2];
            arr[1] = arr[2];
            arr[2] = arr[0];

        }

        return arr[0];
    }

    // 再优化,二次交换数据都没必要
    public static int count4(int n) {
        if (n <= 0)
            throw new IllegalArgumentException("非法参数");
        int[] arr = new int[3];
        int j = 0; // 返回值的位置
        for (int i = 1; i < n + 1; i++) {
            j = i % 3;
            if (i == 1) {
                arr[j] = 1;
            } else if (i == 2) {
                arr[j] = 2;
            } else {
                arr[j] = 0; // 清0
                for (int k = 0; k < 3; k++) {
                    if (k == j)
                        continue;
                    arr[j] += arr[k];
                }
            }
        }

        return arr[j];
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值