剑指offer(11-20)

11、二进制中1的个数

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

思路:解决这道题的思路比较简单,就是将整数转换为二进制,然后得到二进制中1的个数,有两种方法:

原码、补码、反码基础知识

正整数的原码、补码和反码相同

负数-5的原码,1000 0101,即在正整数的基础上加上符号位,反码则是除符号位外,每一位都取反,即1111 1010,补码则是在反码的基础上+1,即1111 0110

  • 方法一

直接调用Integer中的库函数toBinaryString转换为二进制,把该二进制转换为字符数组,循环判断字符是否为1。

  • 方法二

采用移位的策略,假设数字12转换为二进制为1100,减1后就变为1001,如果将(1100)&(1001)=1000,利用这个特性,我们可以计算出二进制中1的个数。

调用库函数

public class Solution {
    public int NumberOf1(int n) {
        int result=0;
      char[] ch=Integer.toBinaryString(n).toCharArray();
        for(int i=0;i<ch.length;i++){
            if(ch[i]=='1'){
                result++;
            }
        }
        return result;
    }
}

移位操作

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

12、数值的整数次方

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

思路:这道题有个陷阱需要考虑,就是exponent的正负不确定,所以这两种情况都要考虑到,

假设exponet为正,我们的思路通常是这样的:

①假如exponent为0,直接返回1

②假如exponent,不为0,我们可以利用for循环不断的累乘

假如exponent为负,我们不妨对exponent=-exponent,然后利用exponent为正式计算base的exponent,对于最后的输出结果,我们只需要对结果取倒数即可。

代码

public class Solution {
    public double Power(double base, int exponent) {
        double result=base;
        int n=exponent;
        if(exponent<0){
            exponent=-exponent;
        }
        if(exponent==0){
            return 1;
        }
        for(int i=1;i<exponent;i++){
            result*=base;
        }
        return n<0 ? 1/result : result;
  }
}

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

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

  • 冒泡排序

冒泡排序是两两比较数组中的相邻元素,我们可以把这道题目看作为冒泡排序的变种,将交换条件变为判断相邻元素是否为奇数、偶数

  • 插入排序

插入排序是通过移位的方式来完成交换的,我们也是同样的将条件变为判断相邻元素是否为奇数、偶数来到达排序的目的

冒泡排序

public class Solution {
    public void reOrderArray(int [] array) {
        for(int i=0;i<array.length-1;i++){
            for(int j=0;j<array.length-1-i;j++){
                if(array[j]%2==0&&array[j+1]%2!=0){
                    int temp=array[j];
                    array[j]=array[j+1];
                    array[j+1]=temp;
                }
        }
        }
    }
}

插入排序

public class Solution {
    public void reOrderArray(int [] array) {
        //插入坐标
        int insertIndex=0;
        //要插入的值
        int insertValue=0;
        //循环比较判断
        for(int i=1;i<array.length;i++){
            insertIndex=i-1;
            insertValue=array[i];
            while(insertIndex>=0&&array[insertIndex]%2==0&&insertValue%2!=0){
                array[insertIndex+1]=array[insertIndex];
                insertIndex--;
            }
            if(insertIndex+1!=i){
                array[insertIndex+1]=insertValue;
            }
        }
    }
}

14、链表中的倒数第K个结点

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

思路:这道题的关键点在于倒数第K个结点,最简单的做法就是先计算出链表中元素的个数count,然后利用index=(count-k)判断结点在链表中的正序位置。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head==null){
            return null;
        }
        if(k<=0){
            return null;
        }
        int count=1;
        ListNode node=head;
        while(head.next!=null){
            head=head.next;
            count++;
        }
        count=count-k;
    if(count<0){
       return null
    }
        
        for(int i=0;i<count;i++){
            node=node.next;
        }
       return node;
       
    }
}

15、反转链表

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

思路:这道题的核心是怎样改变链表的指向,使得链表的顺序从尾指向前。

定义两个指针pre和next,pre为当前结点的前一结点,next为当前结点的下一结点,它们的目的是为了改变链表的顺序,使得pre->head->next1->next2变成pre<-head next1->next2,利用head.next=pre反转当前结点的指向,最后让head、next依次向后移动一个结点,继续下一次的指针反转。

 /*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/

public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head==null){
            return null;
        } 
        ListNode pre=null;
        ListNode next=null;
        while(head!=null){
            //保存next结点
            next=head.next;
            //反转链表
            head.next=pre;
            //pre和next依次向后移动一个节点,继续进行下一次的反转
            pre=head;
            head=next;
        }
        return pre;
    }
}

16、合并两个排序链表(16)

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

思路:因为题目要求是将这两个链表合成单调不减的链表,所以我们可以不断的循环判断链表1和链表2中的元素值放入一个新的链表中。步骤如下

  • 初始化新链表
秒杀剑指offer(11-20)_java
  • 比较判断链表1和链表中的值,并放入新的链表
秒杀剑指offer(11-20)_java_02
  • 不断循环直到放入最后一个元素
秒杀剑指offer(11-20)_java_03

代码

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null&&list2==null){
            return null;
        }else if(list1==null&&list2!=null){
            return list2;
        }else if(list1!=null&&list2==null){
            return list1;
        }
        ListNode head=new ListNode(-1);
        ListNode node=head;
        while(list1!=null&&list2!=null){
            if(list1.val<list2.val){
                node.next=list1;
                list1=list1.next;
                node=node.next;
            }else{
                node.next=list2;
                list2=list2.next;
                node=node.next;
            }
        }
        while(list1!=null){
            node.next=list1;
            list1=list1.next;
            node=node.next;
        }
        while(list2!=null){
            node.next=list2;
            list2=list2.next;
            node=node.next;
        }
        return head.next;
    }
}

17、树的子结构

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

思路:这道题的重点是理解子树的定义,即子树就是除根结点外,其余结点组成的互不相交的有限集合称为子树。我们结合和代码来讲解这道题的思路

1、首先设置标志位result = false,因为一旦匹配成功result就设为true,剩下的代码不会执行,如果匹配不成功,默认返回false,也就证明这两课二叉树的根结点不匹配,所以就需要利用树1的左右子结点去匹配树2(采用递归的思想)

2、如果根节点相同则递归调用DoesTree1HasTree2()

3、注意null的条件,HasSubTree中,如果两棵树都不为空才进行判断,DoesTree1HasTree2中,如果Tree2为空,则说明第二棵树遍历完了,即匹配成功,tree1为空有两种情况

  • 如果tree1为空&&tree2不为空则说明不匹配
  • 如果tree1为空,tree2为空则说明匹配

代码

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

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

    }
}
*/

public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        boolean result=false;
        if(root1!=null&&root2!=null){
            if(root1.val==root2.val){
               result= doesTree1HasTree2(root1,root2);
            }
            //往左子树递归
            if(!result) result= HasSubtree(root1.left,root2);
            //往右子树递归
            if(!result) result= HasSubtree(root1.right,root2);
        }
        return result;
    }
    
    public boolean doesTree1HasTree2(TreeNode root1,TreeNode root2){
        if(root1==null&&root2!=null){
            return false;
        }
        if(root2==null){
            return true;
        }
        if(root1.val!=root2.val){
            return false;
        }
        //左右子树递归检查
        return doesTree1HasTree2(root1.left,root2.left)&&doesTree1HasTree2(root1.right,root2.right);
    }
}

18、二叉树的镜像(18)

题目:操作给定的二叉树,将其变换为原二叉树的镜像。

二叉树镜像的定义:

思路:刷了那么多题目,我们发现关于二叉树的题目大部分都是用递归来解决的,这道题也不例外,依旧采用递归来解决

结合代码来讲解下这道题的思路

1、首先判断二叉树的根结点是否为空,为空时不成立,

2、判断该根结点的左右子树是否同时为空,为空时不需要变换位置

3、若不同时为空,交换左右子树的位置,

4、采用递归的思想对剩下的结点做判断并交换位置

代码

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

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

    }
}
*/

public class Solution {
    public void Mirror(TreeNode root) {
      //采用递归的思想
      if(root==null){
          return ;
      }
        if(root.left==null&&root.right==null){
            return ;
        }
        TreeNode proot=root.left;
        root.left=root.right;
        root.right=proot;
        
        if(root.left!=null){
            Mirror(root.left);
        }
        if(root.right!=null){
            Mirror(root.right);
        }
    }
   
}

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.

方式一

定义四个变量分别表示左、右、上、下,循环条件可以是(left<=right&&top<=bottom)

定义四个for循环分别进行从左向右、从上到下、从右到左、从下到上的遍历

秒杀剑指offer(11-20)_java_04

方式二

秒杀剑指offer(11-20)_java_05

代码采用方式一的思路编写,

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
       ArrayList<Integer> result=new ArrayList<Integer>();
        //定义四个变量:左,上,右,下
        int left=0,top=0,right=matrix[0].length-1,bottom=matrix.length-1;
        while(left<=right&&top<=bottom){
            for(int i=left;i<=right;i++) result.add(matrix[top][i]);
            for(int i=top+1;i<=bottom;i++) result.add(matrix[i][right]);
            for(int i=right-1;i>=left&&top<bottom;i--) result.add(matrix[bottom][i]);
            for(int i=bottom-1;i>top&&right>left;i--) result.add(matrix[i][left]);
            top++;left++;right--;bottom--;
        }
        return result;
    }
}

20、包含min函数的栈

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

注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

思路:这道题目有两种方式可以解决,第一种方式是直接利用Iterator类,在实际的笔试中,这种方法应该是不让使用的,所以放弃,有兴趣的可以看看代码一。第二种方式定义了两个栈,totalStack用于存放正常添加的值,littleStack用于存放最小的值,要保证两个栈中的数量一致,即当新添加的元素小于littleStack的栈顶元素时,littleStack直接push新元素,反之,littleStack向栈顶添加原栈顶元素,为什么要使得两个栈中的元素的数量保持一致呢?这是保证两个栈执行pop操作时,littleStack栈顶元素永远是栈中的最小值,实现操作见代码二。

代码一

import java.util.Stack;
import java.util.Iterator;

public class Solution {

    Stack<Integer> stack=new Stack<Integer>();
    public void push(int node) {
        stack.push(node);
    }
    
    public void pop() {
        stack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        int min = stack.peek();
        int tmp = 0;
        Iterator<Integer> iterator = stack.iterator();
        while (iterator.hasNext()){
            tmp = iterator.next();
            if (min>tmp){
                min = tmp;
            }
        }
        return min;
    }
}

代码二

import java.util.Stack;
public class Solution {
    Stack<Integer> totalStack=new Stack<Integer>();
    Stack<Integer> littleStack=new Stack<Integer>();
    public void push(int node) {
       totalStack.push(node);
        if(littleStack.empty()){
            littleStack.push(node);
        }else{
            if(node<=littleStack.peek()){
                littleStack.push(node);
            }else{
                littleStack.push(littleStack.peek());
            }
            
        }
    }
    
    public void pop() {
       totalStack.pop();
        littleStack.pop();
    }
    
    public int top() {
        return totalStack.peek();
    }
    
    public int min() {
        return littleStack.peek();
    }
}