牛客网剑指Offer - java版

剑指Offer - java版

文章目录

JZ01

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

暴力解法,直接双循环。时间复杂度为: O(n^2)

    public boolean Find(int target, int [][] array) {
        for(int i = 0 ; i < array.length; i++){
            for(int j = 0; j < array[i].length; j++){
                if(target == array[i][j]){
                    return true;
                }
            }
        }
        
        return false;
    }

根据题意,是从小到大递增的数组,所以按照这个规律,先比较每行最末尾的数,若target小于该数,遍历该行即可;若存在,直接返回;不存在,到下一行。

JZ02 替换空格

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

直接遍历,替换空格。

    public String replaceSpace(StringBuffer str) {
        String sb = "";

        for(int i = 0; i < str.length(); i++){
            if( str.charAt(i) != ' '){
                sb = sb + str.charAt(i);
            } else
                sb = sb + "%20";
        }

        return sb;
    }

另外也可以直接调用java的函数.replace()

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

JZ03 从尾到头打印链表

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

按部就班,将链表中的值输入到另一个数组中,再将数组中的值,反序输入到ArrayList中。

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> arry = new ArrayList<Integer>();
        int[] arr = new int[10000];
        int i = 0;
        
        if(listNode == null)
            return arry;
        while(listNode.next != null ){
            arr[i] = listNode.val;
            listNode = listNode.next;
            i++;
        }
        arr[i] = listNode.val;
        
        for( int j = i ; j >= 0 ; j--){
            arry.add(arr[j]);
        }
        return arry;
    }

JZ04 重建二叉树

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

在这里首先要明确二叉树的前序、中序和后序遍历。

  1. 前序:根左右
  2. 中序:左根右
  3. 后序:左右根

那么前序遍历的第一个值,即为根节点;中序遍历根节点前的均为左子树的中序遍历,即可得到左子树的节点数,那么就可以在前序遍历中得到左子树的前序遍历。二者中剩余的变为右子树的,即可带入遍历中。每次遍历返回一个根节点,即为上一次的子节点。另外因为要切割数组,所以用到了java.util.Arrays工具类中的copyOfRange()。牢记左开右闭

public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if( pre.length == 0 || in.length == 0 )
            return null;
        TreeNode root = new TreeNode(pre[0]);
        for(int i = 0 ; i < in.length ; i++){
            if( pre[0] == in[i]) {
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(in, 0, i));
                root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(in, i + 1, in.length));
                break;
            }
        }
        
        return root;
    }

JZ05 用两个栈实现队列

用两个栈来实现一个队列,完成队列的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() {
        int lenth = stack1.size();
        for (int i = 0; i < lenth; i++) {
            stack2.push(stack1.pop());
        }

        int res = stack2.pop();
        lenth = stack2.size();
        for (int i = 0; i < lenth ; i++) {
            stack1.push(stack2.pop());
        }

        return res;
    }
}

JZ06 旋转数组的最小数字

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

  1. 直接使用遍历,暴力求解。
    public int minNumberInRotateArray(int [] array) {
        int min = array[0];
        for(int i = 1; i< array.length; i++){
            min = (min < array[i] ? min : array[i]);
        }
        
        return min;
    }
  1. 利用二分法的思想,由于题目中是非递减排序后的数组的旋转,所以可以利用这个特点,但同时也要考虑该特点的特异性(011111或类似相同的数组的一种旋转)。一组非递减排序后的数组,经过旋转之后,可由最小值处分为两段。联想到二分法,mid与last进行比较。有以下情况:
    1. mid > last , 则最小值在mid 和 last之中。
    2. mid < last ,则最小值在first 和 mid之中。
    3. mid == last,分不清,只能一步步缩小last的值。
    public int minNumberInRotateArray(int [] array) {
        if( array.length == 0 )
            return 0;
        int first = 0;
        int last = array.length - 1;
        while (first < last ){
            if( array[first] < array[last] )
                return array[first];
            int mid = ( first + last) >> 1;
            if( array[mid] < array[last]){
                last = mid;
            } else if ( array[mid] > array[last])
                first = mid + 1;
            else
                --last;
        }
        
        return array[first];
    }

JZ07 斐波那契数列

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

经典问题:直接递归。

public int Fibonacci(int n) {
        if(n == 0)
            return 0;
        if(n==1)
            return 1;
        return Fibonacci(n-1) + Fibonacci(n-2);
    }

JZ08 跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

看到这个题,直接斐波那契数列求解。需要注意的是,在不同的数列中,f0的取值不同。

public int JumpFloor(int target) {
        if( target <= 1)
            return 1;
        return JumpFloor(target - 1) + JumpFloor(target - 2);
    }

当然也可以使用,从下到上的动态规划来解题。

    public int JumpFloor(int target) {
        int[] dp = new int[target + 1];
        if(target == 0)
            dp[0] = 1;
        if(target >= 1)
        {
            dp[0] = 1;
            dp[1] = 1;
        }
            
        for(int i = 2; i < target + 1; i++) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        
        return dp[target];
    }

JZ09 青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
很明显的递归问题:第一次跳有n种跳法,假设第一次跳了x步,则还剩(n-x)步,将(n-x)又回到原题中了。当剩下的步数(n-x)为0时,就跳到了终点,此时加1即可。

public class Solution {
    public static int COUNT = 0;
    public int JumpFloorII(int target) {
        COUNT = 0;
        jump(target);
        return COUNT;
    }
    
    public void jump(int n){
        for( int i = 1; i <= n; i++){
            if( i== n)
                COUNT ++;
            else
                jump(n-i);
        }
    }
}

通过测试分析可以发现,n = 1时,有1种;n = 2时,有2种;n = 3时,有4种…

发现规律,最终的总数为2的n-1次方。

public class Solution {
    
    public int JumpFloorII(int target) {
        return Math.pow(2, target - 1);
    }
}

JZ10 矩形覆盖

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

比如n=3时,2*3的矩形块有3种覆盖方法。

看似复杂,实则还是斐波那契数列,只是需要注意,此时f(n)序列中的f(0)值为1,但实际上f(0)的值为0。这一点要考虑上。

    public int RectCover(int target) {
        int[] dp = new int[target + 1];
        
        if(target == 0)
            dp[0] = 1;
        
        if( target >= 1 ) {
            dp[0] = 1;
            dp[1] = 1;
            for(int i = 2; i < target + 1; i++)
                dp[i] = dp[i - 1] + dp[i - 2];
        }
        
        if( target == 0 ) return 0;
        return dp[target];
    }

JZ11 二进制中1的个数

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

考察位运算,直接&1即可求得最后一位是否是1,然后依次右移即可。但是这种简单的方法,在java中超出了运算时间2ms。

public int NumberOf1(int n) {
        int count = 0;
        while ( n != 0 ){
            if((n & 1) == 1)
                count++;
            n >>= 1;
        }
        return count;
    }

所以考虑对其进行优化。在上面的方法中每次都要对0进行判断,所以如果能越过0就能节约时间。11001000 - 1后为11000111 再与原数求与,得到11000000即,每次都将最右的一位1去掉了。

    public int NumberOf1(int n) {
        int count = 0;
        while ( n != 0 ){
            ++ count;
            n = n & (n-1);
        }
        return count;
    }

JZ12 数值的整数次方

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

直接用Math包(import java.lang.Math)下的pow()方法即可。

public double Power(double base, int exponent) {
        return Math.pow(base, exponent);
  }

当然,也可以直接一个while循环,exponent--.

JZ13 调整数组顺序,使奇数位于偶数前面

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

暴力求解:直接三次循环。1. 将数组中的奇数放入暂存数组中。2. 将数组中的偶数放入暂存数组中。3. 将暂存数组放回到原来的数组。

    public void reOrderArray(int [] array) {
        int[] b = new int[array.length];
        
        int j = 0;
        for(int i = 0; i < array.length ; i++) {
            if( array[i] % 2 == 1 ){
                b[j] = array[i];
                j ++;
            }
        }
        
        for(int i = 0; i < array.length ; i++) {
            if( array[i] % 2 == 0 ){
                b[j] = array[i];
                j ++;
            }
        }
        
        for(int i = 0; i < array.length ; i++) {
            array[i] = b[i];
        }
    }

JZ14 链表中倒数第k个结点

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

两种思路,一是直接遍历该链表,得到链表的长度length,倒数第k个结点,即为正数第(n+1-k)个。

public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k <= 0 )
            return null;
        
        int length = 0;
        ListNode out = head;
        while( out != null){
            out = out.next;
            length ++;
        }
        
        if( k > length)
            return null;
        
        for(int i = 0; i < length - k ; i++) {
            head = head.next;
        }
       
        return head;
    }

二是快慢指针,同时两个指针对链表进行遍历,fast指针先走k步,那么当fast到null时,slow到倒数第k个位置。

public ListNode FindKthToTail(ListNode head,int k) {
        if( head == null || k <= 0 ) return null;
        ListNode slow = head;
        ListNode fast = head;

        while( k > 0 ){
            if( fast == null)
                return null;
            fast = fast.next;
            k--;
        }
        
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        
        return slow;
    }

JZ15 反转链表

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

用三个指针来调整指针的位置。

  1. pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr
  2. cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head
  3. nex指针指向待反转链表的第二个节点。

首先将指向下一个位置的指针保留到nex中;然后才进行反转操作。

把当前元素的下一个位置的指针指向上一个元素。

然后把当前元素接到pre中。

当前元素指向原始序列的下一个元素中。

    public ListNode ReverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        ListNode nex = null;
        
        while( cur != null ) {
            nex = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nex;
        }
        
        return pre;
    }

JZ16 合并两个排序的链表

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

有两个思路,一个是直接依次比较,然后输出整个链表。其二是迭代。

直接比较时,需要两个个指向新链表头部的指针。一个为了保存新链表的头部,作为输出;另一个则指向当前元素,做添加操作。所以在每次的循环结束后,都会将next指向它。

public ListNode Merge(ListNode list1,ListNode list2) {
        ListNode newList = null;
        ListNode head = null;
        if( list1 == null ) return list2;
        if( list2 == null ) return list1;
        if(list1.val < list2.val){
            newList = list1;
            list1 = list1.next;
        }
        else {
            newList = list2;
            list2 = list2.next;
        }

        head = newList;
        while( !( list1 == null && list2 == null ) ){
            if(list1 == null ) {
                newList.next = list2;
                list2 = list2.next;
            } else if (list2 == null ) {
                newList.next = list1;
                list1 = list1.next;
            }else if(list1.val < list2.val) {
                newList.next = list1;
                list1 = list1.next;
            }
            else {
                newList.next = list2;
                list2 = list2.next;
            }
            
            newList = newList.next;
        }
        
        
        return head;
    }

其二是通过迭代的方式去得到最终结果。需要考虑的是迭代的终止条件到底是什么?是输入的两个指针都为空,还是至少一个为空。至少一个为空时,将剩余的接上即可。

public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null) return list2;
        if (list2 == null) return list1;
        
        ListNode head = null;
        if(list1.val <= list2.val){
            head = list1;
            head.next = Merge(list1.next, list2);
        } else {
            head = list2;
            head.next = Merge(list1, list2.next);
        }
            
        return head;
    }

JZ17 树的子结构

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

错误想法:

同样也是两个思路

1. 直接遍历A的所有节点,与B进行比较。

2. 迭代比较,直接比较A B, 若A != B则比较A的左右子树和B是否相同。

子结构:树A和树B的根结点相等,并且树A的左子树和树B的左子树相等,树A的右子树和树B的右子树相等。

  1. 递归函数的功能:判断2个数是否有相同的结构,如果相同,返回true,否则返回false。
  2. 递归终止条件:如果树B为空,返回true,此时,不管树A是否为空,都为true。
    否则,如果树B不为空,但是树A为空,返回false,此时B还没空但A空了,显然false。
  3. 下一步递归参数:如果A的根节点和B的根节点不相等,直接返回false。否则,相等,就继续判断A的左子树和B的左子树,A的右子树和B的右子树

然后就是正确遍历树A中的各个根节点,并对这些根节点进行判断。

    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if( root1 == null || root2 == null ) return false;
        return equals(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    }
    
    public boolean equals (TreeNode root1,TreeNode root2) {
        if( root2 == null ) return true;
        if( root1 == null ) return false;
        
        return root1.val == root2.val && equals(root1.left, root2.left) && equals(root1.right, root2.right);
    }

错误的迭代方法:

    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if( root1 == null || root2 == null )
            return false;
        
        if( equals(root1, root2) )
            return true;
        else
            return HasSubtree(root1.left, root2) || HasSubtree(root1.left, root2);
    }
    
    public boolean equals(TreeNode root1,TreeNode root2) {
        if( root2 != null ) {
            if( root1.val != root2.val )
                return false;
            else {
                boolean left;
                boolean right;
                if( root2.left != null)
                    left = equals(root1.left, root2.left);
                else
                    left = false;
                if( root2.right != null)
                    right = equals(root1.right, root2.right);
                else
                    right = false;
                if( left && right)
                    return true;
                else
                    return false;
            }
           
        } else return false;
    }

JZ18 二叉树的镜像

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

依旧是遍历问题,但是要注意合适的终止情况,当节点为null时,或节点的两个子节点为空时,退出。而其余情况,交换两个节点的位置,然后对该节点继续遍历。

public void Mirror(TreeNode root) {
        TreeNode temp;

        if( root == null );
        else if( root.left == null && root.right == null ) {

        } else {
            temp = root.left;
            root.left = root.right;
            root.right = temp;
            Mirror(root.left);
            Mirror(root.right);
            
        }
    }

JZ19 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

明显的循环问题,注意循环体结束的点。定义上下左右四个边界并不断地收缩矩阵的边界,当出界时,终止循环。

public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0)
            return list;
        
        int up = 0;
        int down = matrix.length - 1;
        int left = 0;
        int right = matrix[0].length - 1;
        
        while(true) {
            for(int col = left; col <= right ; col++) 
                list.add(matrix[up][col]);
            up ++;
            if(up > down)
                break;
            
            for(int row = up; row <= down; row++) 
                list.add(matrix[row][right]);
            right --;
            if(right < left)
                break;
            
            for(int col = right; col >= left; col--) 
                list.add(matrix[down][col]);
            down --;
            if( down < up)
                break;
            
            for(int row = down; row >= up; row-- ) 
                list.add(matrix[row][left]);
            left ++;
            if( left > right)
                break;
        }
        
        return list;
        
    }

JZ20 包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

使用双栈法,注意࿱

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值