您的位置:首页 > Web前端

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

2016-02-23 10:43 330 查看
剑指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;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: