剑指offer刷题

JZ1二维数组中的查找

**题目:**在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
【例子】例如下面的二维数组就是每行、每列都递增顺序。如果在这个数组中查找数字7,则返回true;如果查找数字5,由于数组不含有该数字,则返回false。

1		2		8		9
2		4		9		12
4		7		10		13
6		8		11		15

**思路:**当我们需要解决一个复杂的问题时,一个有效办法就是从一个具体问题入手,通过分析简单具体的例子,寻找普遍的规律。以在数组中查找数字7为例,首先我们选取右上角的9,由于9大于7,并且9还是第4列的第一个数字,因此7不可能出现在数字9所在的列。于是,可把这一列从需要考虑的区域内剔除,在剩下的3列中,位于右上角的数字是8,同样8也大于7,因此8所在列可被剔除,接下来只需分析剩下的两列即可。在剩下两列组成的数组中, 数字2位于数组的右上角。2小于7,那么查找的7可能在2的右边(在前面的分析中已经把2右边列剔除),也有可能在2的下边,因此7只有可能在2的下边,于是我们把数字2所在行也剔除,只分析剩下的三行两列数字。在剩下上的数字中,数字4位于右上角,和前面一样,我们把数字4所在行也删除,最后剩下两行两列数字,在剩下的数组中,位于右上角的刚好是我们要查找的数字7,则查找过程结束。


**规律:**总结查找过程,发现规律,首先选取数组中右上角的数字,如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在列;如果该数字小于要查找的数字,剔除这个数字所在行。也就是说,如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。
代码:

public class Find {
    public static void main(String[] args) {
        int [][]arraylist={{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
        int search=18;
        Find f=new Find();
        System.out.println(f.Find(search,arraylist));
    }
    public boolean Find(int target, int [][] array) {
        int row=0;
        int col=array[0].length-1;
        while (row<array.length && col>=0){
            if(array[row][col]>target) {
                col--;
            }else if(array[row][col]<target) {
                row++;
            }else{
                return true;
            }
        }
        return false;
    }
}
#true

JZ2 替换空格

题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
**思路:**如果是在原来的字符串上做替换,那么就有可能覆盖修改在该字符串的后面的内存。如果是创建新的字符串并在新的字符串上做替换,那么我们可以自己分配足够多的内存。时间复杂度为O(n^2)的解法:

从头到尾扫描字符串,每一次碰到空格字符的时候做替换,由于是吧1个字符替换成3个字符,则必须把空格后面所有字符都后移2个字节,否则就有两个字符被覆盖。假设字符串长度是n,对每个空格字符,需要移动后面O(n)个字符,因此对含有O(n)个空格字符的字符串而言总的时间效率为O(n^2)。

O(n)的解法:

我们先遍历一次字符串,这样就能统计出字符串中空格的总数,并由此计算出替换之后字符串的总长度。我们从字符串的后面开始复制和替换。首先准备两个指针,p1指向原始字符串的末尾,p2指向替换之后的字符串的末尾。接下来,向前移动指针p1,逐个把它指向的字符串复制到p2指向的位置,直到碰到第一个空格为止,碰到第一个空格后,把p1向前移动1格,在p2之前插入字符串“%20”,由于“20%”长度为3,同时也要把p2向前移动	3格。经分析,所有字符都只复制(移动)一次,因此这个算法时间效率是O(n),比第一解法要快。

O(n^2)的解法,采用正则表达式:

public String replaceSpace(StringBuffer str) {
        return str.toString().replace(" ", "%20");
    }

O(n)的解法:

public class Blank {
    public static void main(String[] args) {
        StringBuffer s=new StringBuffer("We are Happy.");
        Blank b=new Blank();
        System.out.println(b.replaceSpace(s));
    }
    public String replaceSpace(StringBuffer str) {
        int spacenum = 0;//spacenum为计算空格数
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)==' ')
                spacenum++;
        }
        int indexold = str.length()-1; //indexold为为替换前的str下标
        int newlength = str.length() + spacenum*2;//计算空格转换成%20之后的str长度
        int indexnew = newlength-1;//indexold为为把空格替换为%20后的str下标
        str.setLength(newlength);//使str的长度扩大到转换成%20之后的长度,防止下标越界
        for(;indexold>=0 && indexold<newlength;--indexold){
            if(str.charAt(indexold) == ' '){  //
                str.setCharAt(indexnew--, '0');
                str.setCharAt(indexnew--, '2');
                str.setCharAt(indexnew--, '%');
            }else{
                str.setCharAt(indexnew--, str.charAt(indexold));
            }
        }
        return str.toString();
    }
}
#We%20are%20Happy.

JZ3 从尾到头打印链表

题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
思路: 通常打印是一个只读操作,一般不希望打印时修改内容。我们先遍历链表,遍历的顺序是从头到尾的顺序,可输出的顺序却是从尾到头。即第一个遍历的结点最后一个输出,而最后一个遍历到的结点第一个输出。这种典型的“后进先出”,用栈实现这种顺序。每经过一个结点的时候,把该结点放到一个栈中,当遍历完整个链表后,再从栈顶开始逐个输出结点的值。而递归在本质上就是一个栈结构,所以用递归来实现反过来输出链表,每访问到一个结点的时候,先递归输出它后面的结点,再输出该结点自身,这样链表输出结果就反过来了。但有个问题:当链表非常长的时候,就会导致函数调用的层级很深,从而导致函数调用栈溢出。显示用栈基于循环实现的代码的鲁棒性要好些。
代码:

class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
public class PrintList {
    public static void main(String[] args) {
        PrintList list=new PrintList();
ListNode listNode=new ListNode(2);
ListNode s=new ListNode(4);
ListNode t=new ListNode(6);
ListNode four=new ListNode(8);
ListNode five=new ListNode(10);
listNode.next=s;
s.next=t;
t.next=four;
four.next=five;
five.next=null;
System.out.println(list.printListFromTailToHead(listNode));
    }
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        Stack<Integer> stack=new Stack();
        while(listNode!=null){
            stack.add(listNode.val);
            listNode=listNode.next;
        }
        ArrayList<Integer> integers=new ArrayList<>(stack.capacity());
        while(!stack.isEmpty()){
            integers.add(stack.pop());
        }
        return integers;
    }
}
#[10,8,6,4,2]

JZ4 重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
**思路:**在二叉树的前序遍历序列中,第一个数字总是树的根结点的值,但在中序遍历序列中,根结点的值在序列的中间,左子树的根结点的值位于根结点的值的左边,而右子树的结点的值位于根结点值的右边。在找到左右子树的前序遍历和中序遍历序列后,我们可以用同样的方法分别去构建左右子树,即可用递归的方法 去完成。先根据前序遍历序列的第一个数字创建根结点,接下来在中序遍历序列中找到根结点位置,这就可以确定左、右子树结点数量,在前序遍历和中序遍历的序列中划分了左、右子树结点的值之后,调用递归函数,去分别构建它的左右子树。

代码:

package com.cc.jianzhi;

//class TreeNode {
//      int val;
//      TreeNode left;
//      TreeNode right;
//      TreeNode(int x) { val = x; }
//  }

public class RebuildBinary {
    public static void main(String[] args) {
int[] p={1,2,4,7,3,5,6,8};
int[] i={4,7,2,1,5,3,8,6};
RebuildBinary rb=new RebuildBinary();
System.out.println(rb.reBuildTree(p,i));
    }
    public TreeNode reBuildTree(int[] pre, int[] in) {
        TreeNode root = reBuildTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
        return root;
    }

    private TreeNode reBuildTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) {
        //下标异常,返回空
        if (startPre > endPre || startIn > endIn)
            return null;
        //构造根结点
        TreeNode root = new TreeNode(pre[startPre]);
        //构造右子树
        for (int i = startIn; i <= endIn; i++) {
            //如果中序的==前序的
            if (in[i] == pre[startPre]) {//在中序列表中找到根结点
                //root左子树的根结点
                root.left = reBuildTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1);
                //root右子树的根结点
                root.right = reBuildTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn);
                break;
            }
        }
        return root;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值