剑指offer(Java版)

剑指offer|11-20题解题思路及代码(Java版)

2019-04-12  本文已影响0人  蓝白绛

剑指offer11到20题总览:

  1. 二进制中1的个数
  2. 数值的整数次方
  3. 调整数组顺序使奇数位于偶数前面
  4. 链表中倒数第k个结点
  5. 反转链表
  6. 合并两个排序的链表
  7. 树的子结构
  8. 二叉树的镜像
  9. 顺时针打印矩阵
  10. 包含min函数的栈

11、二进制中1的个数

题目描述

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

解题思路

如果一个整数n(包括0、正数和负数)不等于0,则该整数一定有至少一位为1。如果将n减一,则最右边的1会变为0,原最右边的1的右边的所有0都会变成1。例如n=1100,n-1=1011。我们将n和n-1做与运算,的n&(n-1)=1000,得到的1000相当于去掉了1100中的最右边的1。如此重复,直到n变为0,就可以得到n中包含的1的个数。

注意事项

  1. 整数包括了负数。
  2. 循环条件是n!=0。
public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n != 0){//循环直到去掉n中所有的1为止
            n = n & (n-1);//去掉n中最右边的1
            count++;
        }
        return count;
    }
}

12、数值的整数次方

题目描述

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

解题思路

此题求幂,如果按照将base乘abs(exponent)次计算,则时间复杂度为O(n),参考快速幂的方法,可以将求幂运算的时间复杂度降为O(\log n)。例如求a^{11},将11化成二进制为1011。则\begin{aligned} a^{11}&=a^{2^0}*a^{2^1}*a^{2^3}\\ &=a^1*a^2*a^8 \end{aligned}

注意事项

  1. 输入数据判断:底数base等于0时返回0,exponent为0时返回1,exponent为1时返回base。
  2. 当exponent为负时,先按照exponent为正计算,然后返回幂的倒数。
public class Solution {
    public double Power(double base, int exponent) {
        if(base == 0){return 0;}
        if(exponent == 1){return base;}
        double res = 1;//res为返回结果
        double curr = base;//curr表示如果当前位为1,则res需要乘上的数
        int n = Math.abs(exponent);//对指数求绝对值,先按正数计算
        while(n!=0){//我们将将指数n的二进制数不断右移一位
            if((n & 1) == 1){//如果n最右边的一位为1
                res = res * curr;//则res乘上当前位为1需要乘上的curr
            }
            curr = curr * curr;//curr变为原来数的平方是每次n右移一位都要做的
            n = n >> 1;
        }
        return exponent>0? res : 1/res;
    }
}

13、调整数组顺序使奇数位于偶数前面

题目描述

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

解题思路

一种是排序的方法,只要排序的算法是稳定的就行。再就是新建数组的方法。

import java.util.ArrayList;

public class Solution {
    public void reOrderArray(int [] array) {
        ArrayList<Integer> odd = new ArrayList<>();//奇数
        ArrayList<Integer> even = new ArrayList<>();//偶数
        for(int i=0; i<array.length; i++){
            if(array[i]%2 == 1){
                odd.add(array[i]);
            }else{
                even.add(array[i]);
            }
        }
        for(int i=0; i<odd.size(); i++){
            array[i] = odd.get(i);
        }
        for(int i=0; i<even.size(); i++){
            array[i+odd.size()] = even.get(i);
        }
    }
}

14、链表中倒数第k个结点

题目描述

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

解题思路

用两个指针,理想情况下,指针p先走k-1步,然后p和q一起走,直到指针p走到链表尾,提出q.val即可。

注意事项

  1. 检查输入:当输入链表为空时返回null;当k=0时返回null。
  2. 当链表长度小于k时,返回null。
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null){return null;}//链表为空时返回null
        if(k==0){return null;}//k等于0时返回null
        ListNode p = head;
        ListNode q = head;
        int count = 0;//count记录p提前走的步数
        while(count < k-1){
            if(p.next!=null){//如果p可以往后走,则往后走
                p = p.next;
                count++;
            }else{//如果p无法往后走,而此时count还没有达到k-1步,则链表长小于k,返回null
                return null;
            }
        }
        while(p.next != null){//p和q同时往后走
            p = p.next;
            q = q.next;
        }
        return q;
    }
}

15、反转链表

题目描述

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

解题思路

设置两个指针p和q,p一直指向原head,q指向p的next。我们将p的next指向的节点插入到新head的前面,并成为新head。如下图: 剑指offer15|反转链表

注意事项

  1. 输入数据判断:链表为空则返回null;只有一个节点则不需要反转,直接返回head。
  2. 插入节点的时候,指针变化的前后顺序。
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null){return null;}
        if(head.next == null){return head;}
        ListNode p = head;
        ListNode q;
        while(p.next != null){//将q插到最前面,成为新head
            q = p.next;
            p.next = q.next;
            q.next = head;
            head = q;
        }
        return head;
    }
}

16、合并两个排序的链表

题目描述

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

解题思路

可以用非递归也可以用递归的方法。比较list1和list2的头结点的val,如果list1小则新链表接上list1的头结点,将list1剩下的部分和list2再递归进行Merge。

public class Solution {
    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);//将list1剩下的部分和list2合并
        }else{
            head = list2;
            head.next = Merge(list1, list2.next);//将list1和list2剩下的部分合并
        }
        return head;
    }
}

17、树的子结构

题目描述

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

解题思路

本题是判断子结构而不是子树,也就是,当我们发现root1的某个结点与root2的根节点的val相同后,我们判断其是否是子结构时,如果root2中的结点在root1中都对应相同,但是root1后面还有一些多的结点,这种也属于子结构。也就是,root2中的叶子结点,在root1中不一定非要也是叶结点,还有一些子结点也是可以的。
基本的思路没有什么特殊的技巧,就是在root1中找到与root2根节点val相同的值,然后判断root2是否与root1中的一样。

public class Solution {
    public boolean isSub(TreeNode root1,TreeNode root2){
        //root1==null root2!=null ->false
        //root1==null root2==null ->true
        //root1!=null root2==null ->true,只需是子结构就可以
        //root1!=null root2!=null ->判断val,如果val不相等则返回false,val相等则递归左右子树
        if(root1 == null && root2 != null) return false;
        if(root2 == null) return true;
        if(root1.val != root2.val) return false;
        return isSub(root1.left, root2.left) && isSub(root1.right, root2.right);
    }
    public boolean HasSubtree(TreeNode root1,TreeNode root2)
    {
        boolean result = false;
        //root1==null root2==null ->false
        //root1!=null root2==null ->false
        //root1==null root2!=null ->false
        //root1!=null root2!=null ->判断val是否相等,如果相等则调用isSub(),如果不相等则递归左右子树,root2不变
        if(root1 != null && root2 != null){//只需要判断root!=null && root2!=null的情况
            if(root1.val == root2.val){
                result = isSub(root1,root2);//如果根结点val一样,则调用isSub
            }
            if(!result){
                result = (HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2));
                //如果根节点不一样,则对root1的左右子树进行递归
            }
        }
        return result;
    }
}

18、二叉树的镜像

题目描述

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

解题思路

就是对树中每个结点的左右子树进行交换。

public class Solution {
    public void Mirror(TreeNode root) {
        if(root != null){
            TreeNode temp = root.left;//交换左右子树
            root.left = root.right;
            root.right = temp;
            Mirror(root.right);//将左子树进行镜像
            Mirror(root.left);//将右子树进行镜像
        }
    }
}

19、顺时针打印矩阵

题目描述

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

解题思路

没有什么特别的,就是需要计算清楚边界,比较麻烦。

import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if(matrix.length == 0){return list;}
        if(matrix[0].length == 0){return list;}
        int row_num = matrix.length;//原矩阵行数
        int column_num = matrix[0].length;//原矩阵列数
        int row_current = row_num;//内部圈行数,每次循环-2
        int column_current = column_num;//内部圈列数,每次循环-2
        while(row_current>1 && column_current>1){//如果内部圈行或列为1后面再考虑,这里考虑内圈行列大于1的情况
            int margin = (row_num - row_current)/2;//计算内圈距离外圈的边界厚度
            int i = margin;//循环从内圈的左上顶点开始
            int j = margin;
            for(; j<column_current+margin-1; j++){//向右
                list.add(matrix[i][j]);
            }
            for(; i<row_current+margin-1; i++){//向下
                list.add(matrix[i][j]);
            }
            for(; j>margin; j--){//向左
                list.add(matrix[i][j]);
            }
            for(; i>margin; i--){//向上
                list.add(matrix[i][j]);
            }
            row_current -= 2;//循环完之后行列-2
            column_current -=2;
        }
        if(row_current == 1){//当内圈行数为1时,一次循环就行
            int margin = (row_num-1)/2;
            int column_len = column_num - 2*margin;
            for(int i=0; i<column_len; i++){
                list.add(matrix[margin][margin+i]);
            }
            column_current = 0;//因为我将行列分开判断,这里需要将剩余列数置为0
        }
        if(column_current == 1){//当内圈列数为1时
            int margin = (column_num-1)/2;
            int row_len = row_num - 2*margin;
            for(int i=0; i<row_len; i++){
                list.add(matrix[margin+i][margin]);
            }
        }
        return list;
    }
}

20、包含min函数的栈

题目描述

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

解题思路

使用两个栈,一个数据栈data_stack,一个最小值栈min_stack。
当push数据时,如果数据栈为空,则将数据push到数据栈,同时将数据push到最小值栈;如果数据栈不为空,则将数据push到数据栈,将数据与最小值栈栈顶元素比较,如果比栈顶元素小,则将新数据push到最小值栈,如果比栈顶元素大,则将栈顶元素再push一遍到最小值栈。
当pop数据时,数据栈和最小值栈同时pop。

注意事项:

  1. 获取栈顶元素的方法是stack.peek();
public class Solution {
    Stack<Integer> data_stack = new Stack<>();
    Stack<Integer> min_stack = new Stack<>();
    public void push(int node) {
        if(data_stack.empty()){//如果栈为空,则将node分别push到数据栈和最小值栈
            data_stack.push(node);
            min_stack.push(node);
        }else{//如果栈不为空,则要与最小值栈的栈顶元素进行比较,将较小者push到最小值栈中
            data_stack.push(node);
            if(node > min_stack.peek()){
                min_stack.push(min_stack.peek());
            }else{
                min_stack.push(node);
            }
        }
    }
    
    public void pop() {//题目规定没有返回值
        data_stack.pop();
        min_stack.pop();
    }
    
    public int top() {//java中栈获取栈顶元素是stack.peek();
        return data_stack.peek();
    }
    
    public int min() {//直接获取最小值栈的栈顶元素即可
        return min_stack.peek();
    }
}

结尾

如果您发现我的文章有任何错误,或对我的文章有什么好的建议,请联系我!如果您喜欢我的文章,请点喜欢~*我是蓝白绛,感谢你的阅读!

上一篇下一篇

猜你喜欢

热点阅读