Fork me on GitHub

剑指offer总结

题目描述

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

解法

该题目属于查找的类型。查找优化条件在于数组中的数据是按顺序排列的,那么一种方法是二分法,另一种就是通过指针去指向然后移动指针去查找。

二分法的方式,因为是二维数组,因此在举一个例子分析就可以知道,二分法查找是很费时的,因为我们完全可以判断该二维数组的每一行的最后一个元素是否大于这个数,而不是先从中间判断。

所以它的解法也就很明白:

先从第一行的最后一个元素开始比较,如果大于,那么就可以往左去比较,如果小于,那么就往下去比较。

 


题目描述

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

解法

很简单的实现方式是,遇到每一个空格的时候,就替换。但这样的方法每一次替换完成就需要把后面的字符向后移动2位。这样太慢了。

可以从后往前进行替换,先计算出空格的个数,固定替换后的字符串的长度,然后设置两个指针,第一个指针指向原来字符串的最后的元素,第二个指针指向现在字符串的最后一个索引。然后两个同时向前移动,每当遇到空格的时候,就让第一个索引向前移一位,而第二个索引向前移三位。这样就完成了字符串的替换,而且减少了字符串的移动次数。

 


题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

解法

利用链表的头插法,新建一个链表,然后按顺序把链表的元素依次插入到新链表的头部,这样就完成了从尾到头的功能。

 


题目描述

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

解法

知道了前序遍历和中序遍历数组,然后将二叉树重建出来。需要用到二叉树的特性。

首先分析前序遍历,先根节点,然后左节点,然后右节点。因此根节点是该数组的第一个元素(这是关键)

中序遍历,先左节点,然后根节点,然后是右节点。因此根节点是在该数组的中间,而且根节点的左边所有节点就是根的左子树组成,右边是根的右子树组成。

{1,2,4,7,3,5,6,8}、{4,7,2,1,5,3,8,6}

要重建二叉树,就需要利用一种算法每次都能固定一个根节点,然后递归实现。

第一次,取下前序数组的第一个元素,它就是二叉树的根节点。1 此时我们可以知道前序数组被分为了{2,4,7}、{3,5,6,8},而中序数组被分为{4,7,2}、{5,3,8,6}(这个划分就是在中序数组中找到那个前序数组的第一个元素)

第二次,我们把数组分为了部分,我们对每一个部分进行处理,

先处理前部:{2,4,7}、{4,7,2},取出根节点2,此时又会继续第一次的分裂。。。。这个2就是1的左子树。

 再处理后部:{3,5,6,8}、{5,3,8,6},取出根节点3,此时也会继续分裂。。。。这个3就是1的右子树。

这样很明显这是个递归的过程。需要提供的方法是在分裂数组的方法。

 


题目描述

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

解法

栈是先进后出,队列是先进先出。

push操作就是压栈的操作,关键是pop操作,如何将栈底的元素取出来。可以很轻松想到的是,push的时候都压入第一个栈,但在pop的时候,将第一个栈的元素都取出来放在第二个栈中。那么第二个栈就将第一个栈的元素反序存储了。

那么问题就简单了,我们只需要判断第二个栈是否为空,如果为空的话就把第一个栈的元素压入第二个栈中。如果不为空,就不压栈,而是直接把第二个栈中的元素出栈。

 


 

 

题目描述

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

解法

看似和上面的很类似,但其实实现的思路不同。

问题在于,入队和出队的顺序一样,我们在出队的时候不能直接出队,因此需要使得一个队列中只有一个元素,然后将其出队,在入队的时候,我们要保证我们入的是有很多元素的队列。

那么就很清楚了,队列A。队列B

第一次入栈:入A中,B为空。如果出栈,则直接将A中的出。如果继续入栈,那么都入A队列。

当出栈时,需要将A队列中的元素都出队,只剩最后的一个元素。然后把出队的元素入队到B中,出栈操作就是将A中剩的一个元素出队。

然后继续出栈的话,判断哪个队列不为空,不为空的继续出栈操作。

如果入栈的话,也要判断哪个队列不为空,就入队哪个队列。

public class Main {
    private static Queue<Integer> q1=new LinkedList<Integer>();
    private static Queue<Integer> q2=new LinkedList<Integer>();

    public static void main(String[] args) {

        push(1);
        System.out.println(pop());
        push(2);
        System.out.println(pop());
        push(3);

        System.out.println(pop());
    }

    public static void push(int e){
        //这里只需要判断哪个不是空的然后将该元素入不是空的队
        if(q1.isEmpty() && q2.isEmpty()){
            q1.add(e);
        }else if(q2.isEmpty()){
            q1.add(e);
        }else if(q1.isEmpty()){
            q2.add(e);
        }else {
            System.out.println("出错了");
        }
    }
    public static int pop(){
        //需要判断哪个队列有元素就将哪个队列移除队,然后只剩一个
        if(q1.isEmpty() && q2.isEmpty()){
            System.out.println("不能出队");
            return -1;
        }else if(q2.isEmpty()){
            while(q1.size()>1){
                q2.add(q1.remove());
            }
            return q1.remove();
        }else if(q1.isEmpty()){
            while(q2.size()>1){
                q1.add(q2.remove());
            }
            return q2.remove();
        }else{
            System.out.println("不能出队");
            return -1;
        }
    }
}

 

 
 

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

解法

本质上还是查找,找最小元素。如果是无序的只能按照遍历,但这是一个有一定规则的,那么按照这个规则,我们只需要去寻找当前元素比上一个元素小的第一个找到的元素就是最小了。

但如果这个元素在最后面,那需要很长时间,因此,可以使用二分查找。

每次查找数组中间的元素,该元素如与第一个元素比较,如果大于第一个元素,那么从它后面找;如果小于这个元素,那么从它前面找。直到开始与结束指针相差1个。

 


题目描述

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。

n<=39

解法

1.递归,这个很简单,设置出口,当n=0,输出0,n=1,输出1;那么后面的直接输出f(n)=f(n-1)+f(n-2);

2.动态规划,0 1 1 2 3 5 8 13 21 34 。。。。

那么每次用两个变量来存储

a=0 b=1;    n=0

a=1 b=0;    n=1

a=1 b=1;    n=2

a=2 b=1     n=3

a=3 b=2     n=4

....

这样就可以知道,

利用一个循环,每次a的值就是要求的值,但b的值存储的是a的上一次的值。
 

题目描述

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

解法

无论是正数还是负数,可以用一个位1与该整数进行相与,如果结果是1,那么该位就是1.

因此,先从整数的第一位开始与1运算,如果结果是1,就计数。

然后将1向左移一位,继续与,知道1移位之后变成0.

 


题目描述

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
 
保证base和exponent不同时为0

解法

该题考查的是对条件的判断。列出各种不同的情况即可。

 


题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

解法

该题属于数组的移动问题。如何按要求移动,而且不会把顺序的相对位置打乱。

经过分析,可以用插入法:2 7 6 3 5 9 8 0 11

每次都找一个奇数插入数组的前面:

第一次:7 2 6 3 5 9 8 0 11

第二次:7 3 2 6 5 9 8 0 11

...每次都插入

public class Odd {


    public static void main(String[] args) {

        int[] a=new int[]{2,7,6,3,5,9,8,0,11};

        for(int i=1;i<a.length;i++){

            //待插入的位置
            int index=i-1;
            //插入的值
            int value=a[i];

            while(index>=0 && a[index]%2==0 &&value%2==1){
                a[index+1]=a[index];
                index--;
            }
            a[index+1]=value;


        }
        System.out.println(Arrays.toString(a));

    }
}

 

 
 

题目描述

输入一个链表,输出该链表中倒数第k个结点。

解法

设置两个指针,一个指针指向第k个节点,另一个指针指向第一个节点,然后同时向后移动,当第一个指针指向null的时候,另一个指针就指向了倒数第k个。

 


 

题目描述

输入一个链表,反转链表后,输出新链表的表头。

解法

反转链表使用头插法即可实现。

 


 

题目描述

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

解法

链表合成也是用的插入法。这种方法分析之后其实很简单,设置两个指针,p和q。

p指向链表A的第一个节点,q指向链表B的第一个节点。

比较q和p指向的节点value值的大小,若p小于q,那么先将p的指针后移,然后将q连接到p之前的那个节点后面。

依次类推,最后,两个指针都指向null之后,链表就合成了。

 


 

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

解法

该题就是一个逻辑的问题,

第一步,先从A中找到B的根节点,如果存在则继续,若不存在,直接判断失败。

第二步,A.left==B.left && A.right==B.right,若A==null说明不存在,若B==null说明存在。

 
package com.liuxinghang.threadpool;

public class Tree {


    public boolean HasSubtree(TreeNode root1,TreeNode root2) {

        if(root1==null ||root2==null){
            return false;
        }
        return isSub(root1,root2) || HasSubtree(root1.left,root2) ||HasSubtree(root1.right,root2) ;
    }
    //判断是不是子结构
    public boolean isSub(TreeNode root1,TreeNode root2){
        if(root2==null){
            return true;
        }
        if(root1==null){
            return false;
        }

        if(root1.val!=root2.val){
            return false;
        }
        return isSub(root1.left,root2.left) && isSub(root1.right,root2.right);
    }
}
class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }
}

 

 该题目还是有点难度的,要仔细考虑。
 

题目描述

操作给定的二叉树,将其变换为源二叉树的镜像。

解法

也要用到递归,有关树的大部分都要用到递归

这个问题分析了之后还是比较简单的,递归交换左右子树即可。

 public void Mirror(TreeNode root){
        if(root==null){
            return;
        }
        TreeNode temp=root.left;
        root.left=root.right;
        root.right=temp;

        Mirror(root.left);
        Mirror(root.right);
    }

 

 

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

解法

该题需要考虑的是边界条件。

根据分析,这就是一个转圈的算法,要分析的是圈的层数,和怎么转。

根据计算一个n*n的矩阵,其层数是(n+1)/2。然后需要判断每一层的起始位置和结束位置。 第一层起点是0,终点是length-1;第二层起点是1,终点是length-2.......

因此,就 可以写出代码了。

public class Clock {

    public static void main(String[] args) {
        int[][] matrix=new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};

        ArrayList<Integer> integers = printMatrix(matrix);
        System.out.println(integers);

    }

    public static ArrayList<Integer> printMatrix(int[][] matrix){
        ArrayList<Integer> result=new ArrayList<>();

        int n=matrix.length;
        int level=(n+1)/2;

        for(int i=0;i<level;i++){

            int begin=i;
            int end=n-i;

            int x=i;
            int y=i;
            while(y<end){
                result.add(matrix[x][y]);
                y++;
            }
            y--;
            x++;
            while(x<end){
                result.add(matrix[x][y]);
                x++;
            }
            x--;
            y--;
            while(y>=begin){
                result.add(matrix[x][y]);
                y--;
            }
            y++;
            x--;
            while(x>begin){
                result.add(matrix[x][y]);
                x--;
            }
            x++;
        }
        return result;
    }
}
View Code

 

 


 

题目描述

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

解法

只利用一个辅助栈,里面存放栈中的最小值,

分析一下就能知道其中的逻辑。

 
public class Main {
    private static Queue<Integer> q1=new LinkedList<Integer>();
    private static Queue<Integer> q2=new LinkedList<Integer>();

    public static void main(String[] args) {

        push(1);
        System.out.println(pop());
        push(2);
        System.out.println(pop());
        push(3);

        System.out.println(pop());
    }

    public static void push(int e){
        //这里只需要判断哪个不是空的然后将该元素入不是空的队
        if(q1.isEmpty() && q2.isEmpty()){
            q1.add(e);
        }else if(q2.isEmpty()){
            q1.add(e);
        }else if(q1.isEmpty()){
            q2.add(e);
        }else {
            System.out.println("出错了");
        }
    }
    public static int pop(){
        //需要判断哪个队列有元素就将哪个队列移除队,然后只剩一个
        if(q1.isEmpty() && q2.isEmpty()){
            System.out.println("不能出队");
            return -1;
        }else if(q2.isEmpty()){
            while(q1.size()>1){
                q2.add(q1.remove());
            }
            return q1.remove();
        }else if(q1.isEmpty()){
            while(q2.size()>1){
                q1.add(q2.remove());
            }
            return q2.remove();
        }else{
            System.out.println("不能出队");
            return -1;
        }
    }
}
View Code

 

 

题目描述

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

解法

模拟入栈出栈的顺序,看是否能完成出栈的操作,若完不成说明有问题。

public static boolean IsPopOrder(int[] pushA, int[] popA) {

        Stack<Integer> s=new Stack();
        int length = pushA.length;

        int pop = 0;
        for (int i = 0; i < length; i++) {

            s.push(pushA[i]);

            if (pushA[i] == popA[pop]) {
                s.pop();
                pop++;
            }
            if(i==length-1){
                while(!s.isEmpty() &&s.peek() == popA[pop]){
                    s.pop();
                    pop++;
                }
            }
        }
        if(pop==length){
            return true;
        }else {
            return  false;
        }
    }

 

 
 

题目描述

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

解法

其实就是一个层序遍历。有点像广度优先搜索算法。

可以使用一个队列,按照左右节点将节点添加到队列里面,每次按顺序取。这样就可以层级打印

public void print1(TreeNode root){
        if(root==null){
            return;
        }
        Queue<TreeNode> queue=new LinkedList();
        queue.add(root);
        while(queue.size()!=0){

            //取出来,打印,然后得到左右子节点
            TreeNode node = queue.remove();
            System.out.println(node.val);
            if(node.left!=null) {
                queue.add(node.left);
            }
            if(node.right!=null) {
                queue.add(node.right);
            }
        }
    }

 

 

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

解法

二叉搜索树的特点,左节点比根节点小,右节点比根节点大。

一个判断方法是,取数组最后一个,然后如果数组能够分成两部分,前一部分小于后一部分,那么就是,若不能,那么就不是。也要利用递归。

public static boolean VerifySquenceOfBST(int [] sequence){

        if(sequence==null || sequence.length==0 ||sequence.length==1){
            return true;
        }

        int length=sequence.length;
        int root=sequence[length-1];


        int index=-1;

        for(int i=0;i<length;i++){
            if(sequence[i]<root){
                index++;
            }
        }
        boolean flag1=false;
        for(int i=0;i<=index;i++){
            if(sequence[i]>root){
                flag1=true;
            }
        }
        boolean flag2=false;
        for(int i=index+1;i<length-1;i++){
            if(sequence[i]<root){
                flag2=true;
            }
        }

        if(flag1 ||flag2){
            return false;
        }
//        if(!flag1 && !flag2){
//            return true;
//        }

        return VerifySquenceOfBST(Arrays.copyOfRange(sequence,0,index+1)) && VerifySquenceOfBST(Arrays.copyOfRange(sequence,index+1,length-1));
View Code

 

 
 

题目描述

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

解法

利用递归的思路,按照前序遍历的路径,每进入一个节点,就在list里面加入该节点,并且对target-value,这样当target=0的时候,而且是叶子节点,就将该list加入到大的list里面。然后退回到上一个节点,这时要删掉liast里面的最后一个值,然后继续。一次类推。

public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {

        if(root==null){
            return alllist;
        }

        list.add(root.val);

        target=target-root.val;
        if(target==0 && root.left==null && root.right==null){
            alllist.add(new ArrayList<>(list));
        }

        FindPath(root.left,target);
        FindPath(root.right,target);

        list.remove(list.size()-1);
        return alllist;

    }

 

 


 

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

解法

复杂链表的复制,不能像普通那样的思路。要注意特殊的指针。

思路是这样的:

步骤1:复制,先把每一个节点都复制一份,特殊指针先不赋值,但每一个节点跟在自己复制的节点的后面。

步骤2:连接,复制的节点的复杂指针要指向源节点指向的复杂指针的下一个指针。

步骤3:分离,每隔一个节点进行连接。

 public static RandomListNode Clone(RandomListNode pHead) {

        RandomListNode head=pHead;
        //复制
        while(head!=null){
            RandomListNode node=new RandomListNode(head.label);
            RandomListNode now=head;
            head=head.next;
            node.next=now.next;
            now.next=node;
        }
        //连接
        RandomListNode headyuan=pHead;
        RandomListNode headycopy=pHead.next;
        while (headyuan!=null && headycopy!=null){

            headycopy.random=headyuan.random.next;
            if(headycopy.next==null){
                break;
            }
            headyuan=headyuan.next.next;
            headycopy=headycopy.next.next;
        }

        head=pHead;
        RandomListNode subh=pHead.next;
        RandomListNode subhead=subh;
        while(head!=null){
            head.next=head.next.next;
            if(subhead.next!=null){
                subhead.next=subhead.next.next;
            }
            head=head.next;
            subhead=subhead.next;

        }



        return subh;

    }
View Code

 


题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解法

因为二叉搜索书用中序遍历就可以得到一个排序的值。使用非递归的中序遍历算法。

用到了一个辅助栈。

第一步:先去找最左边的节点,在找的过程当中,将路过的左节点都压入栈中。

第二步:找到最左边,将栈中的最左边的节点出栈,然后设置一个pre的节点,将最左边的节点赋值给pre的左节点上。维护这个pre。然后去维护当前节点的left节点为pre。这样就构成了一个双向的链表。

 
public TreeNode Convert(TreeNode pRootOfTree) {

        //先实现一个非递归的中序遍历

        //条件1:先从最左端出发,
        //条件2:如果当前节点有右子树,需要先遍历到右子树。(因为左子树肯定遍历过了)
        //条件3:如果没有右子树,那么需要回退,回退就是栈的结构,可以用栈来实现。

        //定义一个栈来存放节点
        Stack<TreeNode> s=new Stack<>();
        //定义一个节点来表示当前的节点
        TreeNode index=pRootOfTree;
        //定义一个节点来表示最左边的节点
        TreeNode root=null;
        //定义一个节点来保存前一个节点
        TreeNode pre=null;
        //设置一个标记,来判断是不是第一次寻找最左边节点
        boolean flag=true;
        //循环条件是index不为空,并且栈里面有节点
        while(index!=null || !s.isEmpty()) {

            while (index != null) {
                s.push(index);
                index = index.left;
            }
            index = s.pop();
            if (flag == true) {
                pre = index;   //确定了最左边的节点
                root = pre;
                flag=false;
            } else {
                //把前一个节点的右指针指向当前节点,同时把当前节点的左指针指向前一个节点。
                pre.right = index;
                index.left = pre;
                pre = index;
            }
            index=index.right;
        }
        return root;
    }
View Code

 


题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

解法

1.递归

先从最后面开始交换。

a b c d

递归的时候。先交换cd,然后再换回来,重复执行。

 

 

 

public static void PermutationHelper(char[] cs, int i, List<String> list) {
        //递归出口
        if(i==cs.length-1){
            list.add(String.valueOf(cs));
        }
        for(int j=i;j<cs.length;j++){
            //先把i和j交换
            swap(cs,i,j);
            //然后执行递归。
            PermutationHelper(cs,i+1,list);
            //然后把i和j换回来
            swap(cs,i,j);
        }

 

2.略
 

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解法

解法很简单,主要是如何提高效率。

1。使用排序的方式,然后若该值满足条件,那么一定是排序后中间的数。

2.使用随时记录的方式,维护一个result值和times。维护到最后的result是可能的值,然后判断是否出现一半以上的次数

3.利用一个hashmap,然后把每个数字的个数都维护起来,然后判断各种值的出现个数。

 


题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

解法

1.排序

2.利用快速排序的思路,如果排好的数的索引=4,说明已经排好了。

3.最大堆的思路

这个问题可以好好研究。


题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

解法

使用动态规划的思路,

遍历数组,每次都判断前一个的和+现在数组的值,与现在数组的值做比较,取最大的。

这里使用result来存最终的值,

 public static int FindGreatestSumOfSubArray(int[] array) {


        int res = array[0]; //记录当前所有子数组的和的最大值
        int max=array[0];   //包含array[i]的连续数组最大值
        for (int i = 1; i < array.length; i++) {
            max=Math.max(max+array[i], array[i]);
            res=Math.max(max, res);
        }
        return max;
    }

 


题目描述

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

解法

1.遍历每一个数字,然后得到该数字中1的个数。计数。

 


题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

解法

先将数组中的数字转换为字符串,然后将字符串按照从小到大排列,排列规则是,将其中每两个相加,然后比较是加在前面大,还是加在后面大。

上面例子来说,332 >323 那么选择32在3之前,32321 >32132,那么321排在32之前,因此最后的排序是321 32 3

 


 

 

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

解法

第一个丑数是1

1*2 1*3 1*5     选择最小的  2  同时2的个数加1

2*2 1*3 1*5     选择最小的  3  同时3的个数加1

2*2 2*3 1*5     选择最小的  4  同时2的个数加1

3*2 2*3 1*5     选择最小的  5  同时5的个数加1

3*2 2*3 2*5     选择最小的  6  同时2的个数加1   同时3的个数加1

4*2 3*3 2*5     选择最小的  8  同时2的个数加1

。。。。

很容易写出代码

public int GetUglyNumber_Solution(int index) {
        if(index==0)
            return 0;
        int [] res=new int[index];
        res[0]=1;
        int t2=0,t3=0,t5=0;
        for(int i=1;i<index;i++){
            res[i]=min(2*res[t2],3*res[t3],5*res[t5]);
            if(res[i]==2*res[t2])
                t2++;
            if(res[i]==3*res[t3])
                t3++;
            if(res[i]==5*res[t5])
                t5++;
        }
        return res[index-1];
    }
    public int min(int a,int b,int c){
        if(a<=b && a<=c){
            return a;
        }
        else if(b<=a && b<=c)
            return b;
        else
            return c;
    }
View Code

 

 


题目描述

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

解法

利用哈希的思路,将出现的字符都存入一个key中,value++。查看value==1的是哪个字符。

 


题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

解法

暴力解法很简单,直接遍历数组,前面减去后面,若为正数则加1,循环。

 


题目描述

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

解法

1.使用hashmap,把第一个链表的值存入map中,然后遍历第二个链表,如果链表中已经存在,那么可以直接返回。

2.将两个链表连接起来,那么总会找到相同的链表


题目描述

统计一个数字在排序数组中出现的次数。

解法

使用二分查找

 


题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

解法

利用递归,每遍历一层+1

如果左边的层数大,那么就选择左边的,右边的层数大就选择右边的。

每次递归一层就在某一个子树上+1,最后选择最大的、

 


题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

解法

平衡二叉树左右子树的层数相差为不大于1

利用递归,去查看每一段左右子树是否都相差不大于1

 


题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

解法

1.暴力求解

2.利用异或。先对数组中的每一个进行异或,得到异或后的结果,然后根据异或或的1,将不同的数分成两个部分,然后再依次异或

 


 

 

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

解法

利用滑窗法,设置两个指针,先固定左指针,然后右指针右移,如果累加和大于sum,就将左指针向左移,保证累加和的值是在两个指针之间的值。

public static ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {

        ArrayList<ArrayList<Integer>> listall=new ArrayList<>();

        int left=1;
        int right=2;
        int sumValue=left+right;

        while(sum>right){

            if(sumValue<sum){
                //右指针右移
                right++;
                sumValue+=right;
            }else if(sumValue>sum){
                //左指针右移,并从sumValue中减去值
                sumValue-=left;
                left++;
            }else {
                //sum相等了
                ArrayList<Integer> list=new ArrayList<>();
                for(int i=left;i<=right;i++){
                    list.add(i);
                }
                listall.add(list);
                right++;
                sumValue+=right;
            }
        }
        return listall;
    }
View Code

 

 


题目描述

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

解法

1.使用暴力解法,遍历

2.双指针法,一个指向开头,一个指向结尾,然后往里移动,知道两个指针相遇。

 


题目描述

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

解法

1.简单逻辑,转换为数组,然后寻找第k个索引,保存前k个,然后把后面的左移,最后把保存的加到后面。

2.使用字符串的三次翻转,abcXYZ,n=3

令A=abc,B=XYZ,AB翻转为cbaZYX,然后再翻转一次,XYZabc.

 


题目描述

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

解法

1.使用split函数,按照空格分隔。然后翻转数组。

2.先把整个字符串进行翻转,然后将其中的每个单词进行翻转。对单词的翻转可以借用滑窗的思路,记录单词的左右边界,然后翻转。

 

public static String ReverseSentence(String str) {

        //先将整个字符串翻转
        char[] chars = str.toCharArray();
        Reverse(chars,0,chars.length-1);
        System.out.println(String.valueOf(chars));
        //然后去利用滑窗,记录每一个单词的起始位置
        int begin=0;
        int end=0;
        for(int i=0;i<chars.length;i++){
            if(chars[i]==' '){
                Reverse(chars,begin,end-1);
                end++;
                begin=end;

            }else if(end==chars.length-1){
                Reverse(chars,begin,end);
                break;
            } else {
                end++;
            }
        }
        return String.valueOf(chars);
    }

    public static void Reverse(char[] chars,int begin,int end){

        while(begin<end){
            char temp=chars[begin];
            chars[begin]=chars[end];
            chars[end]=temp;

            begin++;
            end--;
        }
    }
View Code

 

 

 


 

题目描述

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

解法

分三个步骤

步骤1:排序

步骤2:得到0的个数以及确定有无重复的非0数

步骤3:得到间隔相差的值的累加

然后比较0的个数和累加。

 


题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

解法

1.利用一个链表来存储,没报到数的同学从链表中删除,然后继续循环,知道链表中剩下最后一个数。

 


题目描述

求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

解法

1.用递归,递推公式是F(n)=F(n-1)+n

递归出口是n==1

2.利用动态规划

 public static int Sum_Solution(int n) {
        if(n==1){
            return 1;
        }
        return Sum_Solution(n-1)+n;

    }

    public static int Sum_Solution1(int n) {

        int a=0,b=1;
        for(int i=0;i<n;i++){
            a=a+b;
            b++;
        }
        return a;

    }

 

 

 

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

解法

步骤1:俩数异或。得sum 无进位

步骤2:两数与向左移位,得c 进位
步骤3:sum和c变为两数,循环,知道c==0
 

题目描述

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

解法

主要是条件判断

 


题目描述

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

解法

1.暴力法

2.利用特性,利用交换法,将每一个数都找到自己的位置,然后看其他位置上是不是也有这个数

比如,2 3 1 0 2 5 3 ,先看2,它应该在数组的2索引,将它与2索引的值交换,得:1 3 2 0 2 5 3

然后看1,将它与1索引位置的值交换:3 1 2 0 2 5 3

。。。。

就可以让每个数字放在自己的位置上,然后,如果我又去查看其他位置,发现我现在的值的位置上已经位置正确了,那么就出现了重复值。

 public static boolean duplicate(int numbers[],int length,int [] duplication) {

        int index=0;
        while (index<length){
            int now=numbers[index];
            if(now!=index){
                if(numbers[now]==now){
                    duplication[0]=now;
                    return true;
                }
                int temp=numbers[index];
                numbers[index]=numbers[now];
                numbers[now]=temp;
            }else {
                index++;
            }
        }
        return false;
    }

 

 


题目描述

给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];)

解法

利用数学的特性

 

 

public static int[] multiply(int[] A) {

        int[] C=new int[A.length];
        int[] D=new int[A.length];
        int[] B=new int[A.length];

        C[0] = 1;
        for(int i=1;i<A.length;i++) {
            C[i] = C[i-1] * A[i-1];
        }
        D[A.length-1]=1;
        for(int i=A.length-2;i>=0;i--){
            D[i]=D[i+1]*A[i+1];
        }

        for(int i=0;i<A.length;i++){
            B[i]=C[i]*D[i];
        }
        return B;

    }

 

 

 

题目描述

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配

解法

分不同的情况讨论,

patten字符串的下一个字符不为*时,对于str的第一个字符的判断就是,相同,则指针后移,不相同则返回false。

patten字符串的下一个字符为*时,对于star的第一个字符的判断分两种:

第一种:当前字符str与petten匹配

  1.str的第一个字符不动,将patten字符串往后跳过两个位置。(代表*表示的是匹配0次)

  2.str的第一个字符右移一位,将patten字符串往后跳过两个位置。(代表*表示的是匹配1次)

  2.patten的指针不动,将str的指针往后移动1位,知道与patten的第一个字符不匹配为止。表示匹配多次

第二种:当前字符不匹配,(当成匹配0次)

  1.str的第一个字符不动,将patten字符串往后跳过两个位置。(代表*表示的是匹配0次)

 

 
public static boolean match(char[] str, char[] pattern)
    {
        if(str==null ||pattern==null){
            return false;
        }
        return matchHelper(str,0,pattern,0);

    }

    public static boolean matchHelper(char[] str,int istr, char[] pattern,int ipatten)
    {
        //递归出口
        if(istr==str.length  && ipatten==pattern.length)//指针都走完了
        {
            return true;
        }
        if(istr<str.length && ipatten==pattern.length){//匹配的走完了,但str没走完,说明不对称
            return false;
        }

        //判断*的情况
        if(ipatten+1<pattern.length  && pattern[ipatten+1]=='*'){    //匹配没有走完,并且下一个是*
            //判断当前字符的情况,如果当前指针一样,才会进行复杂判断
            if(istr<str.length && str[istr]==pattern[ipatten]){
                //进行三种操作
                return
                        matchHelper(str,istr,pattern,ipatten+2)||   //表示*匹配的个数为空
                        matchHelper(str,istr+1,pattern,ipatten+2)   ||   //表示*匹配的个数为1个
                         matchHelper(str,istr+1,pattern,ipatten);//表示匹配多个
            }else {
                return matchHelper(str,istr,pattern,ipatten+2);
            }
        }
        if((istr<str.length&&str[istr]==pattern[ipatten]) || (istr<str.length&&pattern[ipatten]=='.') ){
            return matchHelper(str,istr+1,pattern,ipatten+1);
        }else {
            return false;
        }
    }
View Code

 

 

题目描述

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

解法

分析题意,可以知道一个数值的表示方法为【+|-】【A】【.】【B】【E|e】【+|-】【C】

 其中A和B和C都是整数,.前面可以没有,有点必须有B,有E必须有C。
 
public static boolean isNumeric(char[] str) {

        //判断A是不是整数
        boolean isnum=num(str);

        //有小数点
        if(index<str.length &&str[index]=='.'){
            index++;
            isnum= unnum(str) && isnum ;   //看小数点后面的B是不是整数
        }

        //有e
        if(index<str.length &&(str[index]=='e'||str[index]=='E')){
            index++;
            isnum=isnum && num(str);
        }

        return isnum  &&(index==str.length);

    }

    public static boolean num(char[] str){
        if(index<str.length && str[index]=='+'  ||str[index]=='-'){
            index++;
        }
        return unnum(str);
    }

    public static boolean unnum(char[] str){
        int start=index;
        while(index<str.length  && str[index]>='0' &&str[index]<='9'){
            index++;
        }
        return index>start;
    }
View Code

 


题目描述

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

解法

1.遍历

2.hashtable

 


题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

解法

分三个步骤

步骤1:确定该链表中是否有环,可以设置一个快指针,一个慢指针,如果慢指针能追上快指针,说明该链表有环。

步骤2:确定环中节点的个数,使用一个指针去循环,转一圈之后的计数。

步骤3:设置两个指针,一个指针先走环个数步,另一个指针从开头追,两个指针相遇的地方就是入口。

 


题目描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

解法

因为是排序的链表,因此,只需要判断,当前节点是否与下一个节点相等,如果相等,则将当前节点和下一个节点都删掉。

维护一个pre节点表示当前节点的上一个节点。

 


题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

解法

仔细观察,可以把中序下一结点归为几种类型

有右子树,下一结点是右子树中的最左结点,例如 B,下一结点是 H

无右子树,且结点是该结点父结点的左子树,则下一结点是该结点的父结点,例如 H,下一结点是 E

无右子树,且结点是该结点父结点的右子树,则我们一直沿着父结点追朔,直到找到某个结点是其父结点的左子树,如果存在这样的结点,那么这个结点的父结点就是我们要找的下一结点。例如 I,下一结点是 A;例如 G,并没有符合情况的结点,所以 G 没有下一结点

 


题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
 

解法

解法1,求出其镜像,然后判断是不是相同,若相等,返回true

解法2,利用递归,判断左子树和右子树是否镜像

 


题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

解法

使用两个栈,一个存放奇数层的节点,一个存放偶数层的节点

 


题目描述

请实现两个函数,分别用来序列化和反序列化二叉树
 
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

解法

遍历存储

 


题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。

解法

中序遍历的非递归形式

 


题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
 

解法

最大堆

思路如下:

维护一个大顶堆,一个小顶堆。

大顶堆维护的是中位数前面的小值,在堆顶存放的是最大值,而小顶堆存放的是大值,堆顶存放的是最小值。

我们只需要关心,两个堆里的最大值和最小值。利用优先队列来实现堆。 优先队列默认是一个最小堆。

 

 

 


题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

解法

暴力解法:遍历每一个滑窗求出最大值

 


题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 

 

 

矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
 

 解法

停一停

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
posted @ 2020-03-26 09:27  lovejune  阅读(243)  评论(0编辑  收藏  举报