【刷题笔记/剑指Offer】Part 1 (1-10)

13 篇文章 0 订阅

剑指Offer,在牛客网上可以在线刷题,感觉比看书本要来的更爽一些,现在开始刷起来!

1. 二维数组中的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
public class Solution {
    public boolean Find(int [][] array,int target) {
		int i, j;
        for(i = 0; i < array.length; i++) {
            for(j = 0; j < array[0].length; j++){
                if(array[i][j] == target)
                    return true;
            }
        }
        return false;
    }
}
这无疑是最简单的方法,但是可以通过按行或者按列进行预判断范围来省去一部分操作。继续想,矩阵是按着从左向右递增,从上到下递增的方式进行排序,利用这点,从左下角开始搜索的话,那么就可以按着元素大于目标就上移,元素小于目标就右移的方式快速寻找目标:
public class Solution {
    public boolean Find(int [][] array,int target) {
		int row = array.length - 1;
        int list = 0;
        while(row >= 0 && list < array[0].length){
            if(array[row][list] == target)
                return true;
            else if(array[row][list] > target)
                row--;
            else
                list++;
        }
        return false;
    }
}

2. 从尾到头打印链表

输入一个链表,从尾到头打印链表每个节点的值。

这是一道经典的题目,有两种思路:

1. 利用栈结构,Stack

2. 利用递归

相对来说,利用库中的已有结构通常可以简化问题,以后要多注意。下面附上代码:

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.Stack;
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        Stack<Integer> stack = new Stack<Integer>();
        while (listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
 
        ArrayList<Integer> list = new ArrayList<Integer>();
        while (!stack.isEmpty()) {
            list.add(stack.pop());
        }
        return list;       
    }
}
今天和实验室的一个同学聊了一下这个题目,目前她会用三种方法来搞定这个问题,不包括用栈,下面说一下她的方法:
1. 从前往后遍历原链表的同时,新建一个链表,从后往前建立新链表:
public static ListNode reverse1(ListNode node){
		if (node != null) {
			ListNode tmp = new ListNode(node.val);
			while(node.next != null){
				node = node.next;
				ListNode previous = new ListNode(node.val);
				previous.next = tmp;
				tmp = previous;
			}
			return tmp;
		}
		return node;
	}



2. 从前往后遍历原链表的同时,改变原来链表节点指针的指向:
参考:http://www.cnblogs.com/xing901022/p/3760080.html

3.

3. 替换空格

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

利用java中的已有指令可以很容易达到要求,关键在于命令的选择,怎么过又快又节省空间呢,下面是代码,运行时间<1ms,占用0k:

public class Solution {
    public String replaceSpace(StringBuffer str) {
     if(str==null)
     {
        return null;
     }
     for(int i=0;i<str.length();i++)
     {
         if(str.charAt(i)==' ')
         {
            str.replace(i,i+1,"%20");
         }
      }
      return str.toString();  
    }
}

4. 重建二叉树

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


要牢记的几点:
1. 前序遍历的第一个元素就是根节点。
2. 中序遍历就是所有数从小到大排列,另外,左子树都在根节点的左边,右子树都在根节点的右边,所以根据这点定位根节点来分离左右子树,递归地重建二叉树。
附上代码:
/**
 * 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.length == 0 || in.length == 0)
            return null;
        TreeNode root = new TreeNode(pre[0]);
        root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
		return root;
    }
    public  TreeNode reConstructBinaryTree(int[] pre, int pres, int pree, int[] in, int ins, int ine){
        if(pres > pree || ins > ine)
        	return null;
        TreeNode root = new TreeNode(pre[pres]);
        int index = locate(in, root.val);
        int d = index - ins;
        root.left = reConstructBinaryTree(pre, pres + 1, pres + d, in, ins, index - 1);
        root.right = reConstructBinaryTree(pre, pres + d + 1, pree, in, index + 1, ine);
        return root;
    }
	public  int locate(int[] in, int target) {
		int index = 0;
		while(in[index] != target)
			index++;
		return index;
	}
}
看了牛客网上大神的代码,受启发,用哈希表会更简单,利用hashmap中的get可以轻易索引根节点的位置:
/**
 * 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){
            return null;
        }
 
        java.util.HashMap<Integer,Integer> map= new java.util.HashMap<Integer, Integer>();
        for(int i=0;i<in.length;i++){
            map.put(in[i],i);
        }
        return preIn(pre,0,pre.length-1,in,0,in.length-1,map);
    }
     
      public TreeNode preIn(int[] p,int pi,int pj,int[] n,int ni,int nj,java.util.HashMap<Integer,Integer> map){
 
        if(pi>pj){
            return null;
        }
        TreeNode head=new TreeNode(p[pi]);
        int index=map.get(p[pi]);
        head.left=preIn(p,pi+1,pi+index-ni,n,ni,index-1,map);
        head.right=preIn(p,pi+index-ni+1,pj,n,index+1,nj,map);
        return head;
    }     
}
顺便查了一下广度优先和深度优先遍历的方法,核心思想是通过利用队列结构存储来实现广度优先遍历,通过利用栈结构来实现深度优先遍历。
广度优先遍历:
压入root-》【弹出-》压入左节点-》压入右节点】    【·】内循环直到队列为空
 public void levelOrderTraversal(){
        if(root==null){
            System.out.println("empty tree");
            return;
        }
        ArrayDeque<TreeNode> queue=new ArrayDeque<TreeNode>();
        queue.add(root);
        while(queue.isEmpty()==false){
            TreeNode node=queue.remove();
            System.out.print(node.value+"    ");
            if(node.left!=null){
                queue.add(node.left);
            }
            if(node.right!=null){
                queue.add(node.right);
            }
        }
        System.out.print("\n");
    }

深度优先遍历:
压入root-》【弹出-》压入右节点-》压入左节点】    【·】内循环直到栈为空
    public void depthOrderTraversal(){
        if(root==null){
            System.out.println("empty tree");
            return;
        }       
        ArrayDeque<TreeNode> stack=new ArrayDeque<TreeNode>();
        stack.push(root);       
        while(stack.isEmpty()==false){
            TreeNode node=stack.pop();
            System.out.print(node.value+"    ");
            if(node.right!=null){
                stack.push(node.right);
            }
            if(node.left!=null){
                stack.push(node.left);
            }           
        }
        System.out.print("\n");
    }

5. 用两个栈实现队列

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


看到这道题自然而然的想法是用另一个栈作为辅助栈,进行主要栈的倒序辅助作用,于是乎代码如下:
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 top = stack2.pop();
        while(!stack2.isEmpty()) {
            stack1.push(stack2.pop());
        }
        return top;
    }
}
今天牛客网服务器好像出了点儿问题,没法看到其他大神的解答,等看看有没有更好的答案我再贴上来。服务器终于好了,看了一下,这个应该就是最佳解答了。终于对了一次。。。 哭

6. 旋转数组的最小数字

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

注意到题目中说的是非递减序列,所以从这里入手,考虑用二分法进行查找,但是注意二分法对于递增有效,所以还要考虑重复元素存在的情况,具体的算法解释请看:http://blog.csdn.net/jsqfengbao/article/details/47108069 。程序如下:

public class Solution {
    public static int minNumberInRotateArray(int [] array) {
        if(array.length == 0)
            return 0;
    	if(array.length == 1)
            return array[0];
        int left = 0, mid = left, right = array.length - 1;
        while(array[left] >= array[right]){
            if(right - left <= 1){
                mid = right;
                break;
            }
            mid = (left + right) / 2;
            if(array[left] == array[right] && array[left] == array[mid])
                return regular(array, left, right);
            if(array[left] <= array[mid])
                left = mid;
            else
                right = mid;
        }
        return array[mid];
    }
    public static int regular(int[] array, int left, int right){
        int min = array[left];
        for(int i = left + 1; i <= right; i++){
            if(min > array[i])
                min = array[i];
        }
        return min;
    }
}

7. 斐波那契数列

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
这个题目之前就接触过,最直观的肯定是要用递归啦,几行代码搞定,但是发现栈溢出 大哭,痛定思痛,决定用表达式计算,代码如下:
public class Solution {
    public int Fibonacci(int n) {
        if(n == 0)
            return 0;
        if(n == 1)
            return 1;
        int numfn1 = 0, numfn2 = 1;
        int currentnum = 0;
        for(int i=2; i<=n; ++i) {
            currentnum = numfn1+numfn2;
            numfn1 = numfn2;
            numfn2 = currentnum;
        }
        return currentnum;
    }
}

8. 跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
刚看到这题目的时候,我理解错了题意,以为是要求1和2的组合正好凑成target的种数,没有考虑到顺序问题。考虑顺序问题就纠结了好一阵子,最后写出前几项,找出规律,其实就是斐波那契数列的样式,这给我的提示是遇到递归问题的时候要先写出前几项,找规律。

9. 变态跳台阶

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

这个题目延续了前一道题目的风格,找规律,递归的题目一般都和找规律有关,找到规律,程序就很好编写了:

public class Solution {
    public int JumpFloorII(int target) {
        int sum = 1;
        for(int i = 1; i < target; i++)
            sum*=2;
        return sum;
    }
}

后面附上找规律的思路:http://www.nowcoder.com/profile/286927/codeBookDetail?submissionId=1522855

10. 矩形覆盖

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

依旧找规律,发现和 题目8 一样的规律,so:

public class Solution {
    public int RectCover(int target) {
		if(target < 0)
            return -1;
        else if(target == 1 || target == 0)
            return 1;
        else if(target == 2)
            return 2;
        else{
            int sum = 0;
            int fn1 = 1;
            int fn2 = 2;
            for(int i = 3; i <= target; i++){
                sum = fn1 + fn2;
                fn1 = fn2;
                fn2 = sum;
            }
            return sum;
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值