[刷题]剑指offer(java版)11-25

11.[二进制中1的个数]

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

方法一
1.转成二进制数
2.拿去和1相与
难点在于如何转换成二进制(这个复杂的方法可以锻炼一下能力)
方法二.
巧妙地相与

12.数值的整数次方

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

13.调整数组顺序使得奇数在偶数面前

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

思路1: 从头开始,找第一个偶数和与它最近的一个奇数,把中间的偶数都向后平移一位,奇数插入偶数原来的位置,类似于插入排序

import java.util.ArrayList;
public class Solution {
   public void reOrderArray(int [] array) {
    if(array.length==0&&array==null)
        return;
       int  i=0; int j;
       while(i<array.length){
           while(i<array.length&&!isEven(array[i])){//是奇数就往前
               i++;
           }
           j=i+1;//第一个偶数
           while(j<array.length&&isEven(array[j])){//是偶数就往前
               j++;
           }
           if(j<array.length){//找到的奇数
               int temp=array[j];
               for(int k=j;k>i;k--){//所有偶数向前挪动
                   array[k]=array[k-1];
               }
               array[i++]=temp;
           }else
               break;
       }
}
    public boolean isEven(int n){
        if(n%2==0){
            return true;
        }else 
            
            return  false;
            
    }
}

思路2: 一个指针ep从前往后走,找偶数,找到就停,另一个指针op从后往前走,找奇数,找到也停下来,当ep在op左边时,交换两个数,ep在op右边时,最右边的奇数在最左边偶数的左边,符合条件了,可以退出循环.。不可以这样做,应该题目要求保证奇数和奇数,偶数和偶数之间的相对位置不变。

14.链表中倒数第k个节点

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

思路1: 利用栈


import java.util.Stack;
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
//1.use a stack,push all the element into the stack then pop the no.k element
        if(head==null||k<1){
            return null;
        }
        Stack s=new Stack();
        while(head!=null){
            s.push(head);
            head=head.next;
                
        }
        if(k<=s.size()){
        for(int i=0;i<k-1;i++){
            s.pop();
        }
        return (ListNode)s.pop();
        }else
            return null;
    }
}

思路2:两个指针相隔k步,前指针到尾部时,头指针就是第k个


import java.util.Stack;
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
//1.use a stack,push all the element into the stack then pop the no.k element
        if(head==null||k<1){
            return null;
        }
        
        ListNode h =head;
        ListNode t = head;
        int step =0 ;
        while(step<k){
            if(t==null){
                return null;
            }
                t=t.next;
                step++;
        }
        while(t!=null){//两个指针同步往前走
            t = t.next;
            h = h.next;
        }
        
        return h;
    }
}

15.反转链表

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

思路:三个指针,从头开始把指向反转

/*
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;
        if(head.next==null){
            return head;
        }
        else{
        ListNode pre,cur,next;
        pre=null;
        cur=head;
        next=null;
     
        while(cur!=null){
            next=cur.next;
            cur.next=pre;//反转
            
            
            //移动
            pre=cur;
            cur=next;
            
          
            
            
        }
         
        return pre;
        }
    }
}

16.合并两个排序链表

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

思路:
非递归方法:先做鲁棒性检查,然后开始定head,head指向较小值的链表表头,然后让这个被指向的链表表头往后移动(相当于新表头了),再继续比较,当一个表空了就停止,把另一个表的接到队尾。

/*
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){
            return list2;
        }
        if(list2==null)
            return list1;
        
        ListNode head = null;
        ListNode cur  =null;
       
        while(list1!=null&&list2!=null){
            if(list1.val<=list2.val)//如果A的值比较小
            {
                if(head==null){
                head =cur =list1;
            }else{
                  cur.next = list1;
                  cur=cur.next;
                } 
                list1 = list1.next;
            }else{
                if(head==null){
                head =cur =list2;
            }else{
                  cur.next = list2;
                  cur=cur.next;
                } 
                list2 = list2.next;
                
            }
            
        }
        if(list1==null){
            cur.next = list2;
        }
        if(list2 == null){
            cur.next = list1;
        }
        
        
        return head;
    }
}

自己做不好的点:
1.最后的连接多出来的部分做得太复杂

if(curA==null&&curB!=null)//如果list1先结束
        {   
            while(curB!=null)
            {
                curAll.next = curB;
                curB = curB.next;
            }
        }else if (curA!=null&&curB==null)
        {
            while(curA!=null)
            {
                curAll.next = curA;
                curA = curA.next;
           }
        }

这样做是因为没有意识到,链表只要指向头节点就可以把后面的都串起来了,不用一个一个地去连接
2.一开始并没有想好head和cur节点之间应该如何赋值,其实加一个head为空的判断,如果是空的就head = cur,否则不需要这个,做连接操作就行,注意,head = cur只需要一次就能识别,就相当于cur一直往后连接节点,但head一直等于最初的cur,不需要head.next = cur.

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) { 			
    	if(list1==null){
            return list2;
        }
        if(list2==null)
            return list1;
        
        ListNode head = null;
        ListNode cur  =null;
        //找head最初应该指向的节点
        if(list1.val<=list2.val)//如果A的值比较小
            {	//将head指向list1
                head = cur = list1;
                list1 = list1.next;
            }else//否则,A的值比较大
            {	//将head指向list2
                head = cur = list2;
                list2 = list2.next;
            }
         //当list1,list2中元素不为空时
        while(list1!=null&&list2!=null){
            if(list1.val<=list2.val)
            {//如果A的值比较小
                  cur.next = list1;
                  cur=cur.next;
                  list1 = list1.next;
            }else
            {
                cur.next = list2;
                cur=cur.next;
                list2 = list2.next;
            }
        }
        
        if(list1==null){
            cur.next = list2;
        }
        if(list2 == null){
            cur.next = list1;
        }
        return head;
    }
}

3.我用curA,curB分别记录list1和list2中当前节点,其实没有必要,直接用list1,和list2就行了,因为这两个链表的头节点没有保存的必要,可以直接用来各自的cur节点
4.鲁棒性检查有错误,我一开始的写法


		if(list1==null&&list2==null){
            return null;
        }
		if(list1==null){
            return list2;
        }
        if(list2==null)
            return list1;

当只有一个是空的的时候也会返回null,就算错误的(写到这里觉得直接真的好傻逼,这种错误都犯!!!),而这个例子中的写法可以说很简洁优雅了。
递归版本:

/*
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){
            return list2;
        }
        if(list2==null)
            return list1;
        
        ListNode head = null;
        if(list1.val<=list2.val)//如果A的值比较小
            {
                head= list1;
                head.next = Merge(list1.next,list2);
            }else
            {
                head =  list2;
                head.next = Merge(list1,list2.next);
            }
       
        return head;
    }
}

自己心里明白,如果不看讨论区,自己是写不出递归版本的,因为我没有意识到,连接一个链表不需要将链表里面的节点都连接,连接头节点就可以连接整个链表。

17.树的子结构

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

思路: 递归的思想,分为以下情况
1.如果两者有一个是空树,返回false。
2.对比A,B的根节点,如果值相等,那就分别对比左孩子和右孩子是不是一样
3.如果A,B的根节点值不相等,如果左子数不空,就看这颗树是否是左子树的子树,如果右子数不空,判断这颗树是否是右子树的子数


21.旋转打印数组

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

方法1:
分析:四个循环来打印,标记好边界坐标使思路清晰。对于只有一行和一列的特殊情况,需要在下边右到左以及左边下到上的时候增加条件判断。
示例代码:

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
       //先获得数组长度宽度
	    	int m = matrix[0].length;//第一行的长度就是长,横着数
	    	int n = matrix.length;//有几行,就是竖着数
	    	//结果保存到ArrayList里面
	    	ArrayList<Integer> result = new ArrayList<Integer>();
	    	//int currentLength = n;
            
			//if(n ==1){
				//result.add(matrix[0][0]);
			//}else{
			//一圈一圈地打印,一共打印n/2圈
			//每次打印的开始是对角线上的第i的个点,也就是matrix[i,i]
	    	int left = 0,right = m-1, top = 0,bottom = n-1;
			while(left<=right&&top<=bottom){
	            
				for(int j1 = left;j1<=right;j1++)//1.第i圈从左往右,到底
				{
					result.add(matrix[top][j1]);
				}
				for(int j2 = top+1;j2<=bottom-1;j2++){//第i圈从上往下,不到底
					result.add(matrix[j2][right]);
				}
				if(top!=bottom){
					for(int j3 = right;j3>=left;j3--){//第i圈从右往左,到底
						result.add(matrix[bottom][j3]);
					}
                }
			    if(left!=right){
					for(int j4 = bottom-1;j4>top;j4--){//第i圈从下往上
						result.add(matrix[j4][left]);
					}
				}
                
				left++;
                right--;
                top++;
                bottom--;
              
			}
	    	
	    	//}
	    	return result;
    }
}

一直编译不通过的原因:
1.最后两个循环的条件判断搞反了 ,在第三个循环之前应该是要判断是否够高,所以应该是top和bottom的关系,第四个是是否够宽,是left和right的关系

22.栈的最小值

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数

(时间复杂度应为O(1))。
思路: 一个栈用来装数据s,另一个栈sMin用来保存当前状态下最小元素,比如,sMin的第i个元素,保存栈s的第i个元素进栈的时候,当前栈的最小值

import java.util.Stack;

public class Solution {

    Stack s = new Stack();//用于操作的栈
    Stack sMin = new Stack();//用于保存当前操作的栈中最小元素的值
    int min;
     

    public void push(int node) {
        if(s.isEmpty()){
            min = node;
        }else if(node<min) {//进栈元素比较小,node进min栈,否则
            min = node;
        }
        sMin.push(min);
        s.push(node);
        
        
    }
    
    public void pop() {
        s.pop();
        sMin.pop();
    }
    
    public int top() {
        if(s.isEmpty()){
            return -1;
        }else{
            return (int)s.peek();
        }
           
    }
    
    public int min() {
        if(s.isEmpty()){
            return -1;
        }else {
            return (int)sMin.peek();
              }
        
        
            
        }
    
}

注意:入栈出栈操作都会影响sMin栈

总结栈的方法:

方法作用
void push(Object obj)入栈
Object pop()出栈当前元素,如果为空则返回-1
Object peek()查看堆栈顶部的对象,但不从堆栈中移除它
isEmpty()判断栈是否为空

23.[栈的出栈,入栈]

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

思路:动态地模拟进栈出栈操作,新建一个空栈用来对比,从pushA里一个一个进栈,当栈不空而且出栈序列popA还有元素的时候,比较栈顶是否等于popA现在的元素,等于的话就出栈,并且popA往下移一个元素,不等的话就break;
当pushA的元素都全部进栈,如果栈不空则说明不符合,栈空才说明所有的出栈队列元素都匹配完而且出栈了。

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
       //新建一个栈模拟压入的序列
       Stack sIn = new Stack();
       //nextIn表示下一个要入栈的元素nextOut表示下一个要比较的出栈元素
       int nextIn = 0;
       int nextOut = 0;
       int len = pushA.length;
       while(nextIn<len)
       {//当没有入栈完毕
           sIn.push(pushA[nextIn]);//入栈
           nextIn++;
           while(nextOut<len&&(!sIn.isEmpty()))
           {//当出栈序列没走完&栈不空
               if((int)sIn.peek()==popA[nextOut])
               {//栈定元素等于出栈序列当前元素
                   sIn.pop();//出栈当前元素
                   nextOut++;//出栈序列下标向后移
               }else{break;}
                   
           }
         }
            return sIn.isEmpty();
               }
           }
 

一开始卡住的原因:我把序列先装进栈里面才开始一个一个弹出来比较,这个不科学,应该是个模拟的操作。
后来出现的问题:注意break语句,如果没有的话,会进入死循环。以后所有的只要if语句没有else语句的情况下都需要注意避免死循环的发生

24.从上往下打印二叉树

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

思路: 用队列来实现,根进队然后循环:队列不空的时候,当前节点Val加入结果result列表–>左孩子不空则进队–>右孩子不空则进队

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

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

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        
        ArrayList<Integer> result = new ArrayList<Integer>();
        TreeNode current;
        if(root == null){
            return result;
        }else{
            Queue<TreeNode> q = new LinkedList<TreeNode>();
            q.add(root);
            while(q.peek()!=null){
                current = q.poll();//获取并移除队头
                result.add(current.val);
                if(current.left!= null){
                    q.add(current.left);
                }if(current.right!= null){
                    q.add(current.right);
                }
            }
            
        }
        
        return result;
    }
}

值得注意的是队列的构造,JDK中,LinkedList类实现了Queue接口,可以当Queue使用,所以我们是这样构造的:Queue q = new LinkedList();
错误用法:Queue q = new Queue();

总结队列的使用方法:
1.构造方法

方法解释
Queue q = new LinkedList()LinkedList是实现了Queue接口的,可以用它来实现

2.操作方法

方法解释
add(E e)增加元素,无法添加节时抛出异常
offer(E e)增加元素,无法添加节时抛出异常,但是在有容量限制的时候会比add性能好
remove()获取并删除队头
poll()获取并删除队头,如果为空则返回null
element()获取但不删除队头
peek()获取但不删除队头,如果为空则返回null

25.二叉搜索树的后序遍历序列

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

思路: 二叉搜索树的后续遍历序列的特点就是根节点在最后,左子树遍历序列在右子树遍历序列的前面,左子树的元素值都比根节点小,右子树的元素值都比根节点大,可以利用这个特点去检查是否是二叉搜索树

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
      //左子树的节点只都小于根节点,右子树的节点只都大于根节点,只需判断完成即可
        //字符串是length字段,数组是方法
        if(sequence==null||sequence.length==0)
        {
            return false;//判断
        }else {
             boolean isVerify = verify(0,sequence.length-1,sequence);
            return isVerify;
        }
           
       
    }
    //传入数组的开始和结束位置,代表子树的后序序列
    public boolean verify(int start,int end,int [] sequence)
    {
        int lstart = start;//左子树序列开始位置
        int lend  =0; //左子树序列结束位置
        int rstart =0;//右子树序列开始位置
        int rend  = end-1;//右子树序列结束位置
        //树只有一个元素的时候,说明这个子树只有自己,左右子树都为空,返回是true,如果没有元素,放在后面处理
        if(end<start){
            return true;
        }
        if(end==start){
            return true;
        }
        
        //end是根节点,先获取其值
        int rootValue =sequence[end];
        //开始找子树,找到之后判断大小并递归
        while(rstart<end){
            //找到第一个比根节点大的值,记录下来
            if(sequence[rstart]>rootValue)
            {
                break;
            }else{
                rstart++;
            }
        }
       lend = rstart-1;
        //先检查右子树的情况,如果为空则只需要检查左子树          
        if(rstart ==rend){
            //如果右子树是空的,但树的节点是一个以上,所以左子树不可能为空
            return verify(lstart,lend,sequence);//对左树进行验证
        }else{//右子树不空,需要检查左空或者左不空
            //先验证右子树是否符合要求
           for(int i = rstart;i<rend;i++)
        {
            if(sequence[i]<rootValue)//发现有小的,就返回false
            {
               return false;
            }
        }
            //检查完大小再检查结构
            //左空
            if(rstart == lstart){
                return verify(rstart,rend,sequence);
            }else if(rstart<lstart){//左不空,返回两个
                return 	verify(lstart,lend,sequence)&verify(rstart,rend,sequence);
            }
        }
        }
    }

一开始没有加这一段,一直通过不了

	if(end<start){
            return true;
    }

我认为我代码不会出现到这一个end<start的情况,因为我后面之所以写得比别人复杂就是因为我想分清楚每一种情况,但是我相继检查到了几个我没有分得完全清楚导致end<start的错误
2处错误:
在这里插入图片描述
首先是while循环的边界,如果是rstart<end的话,当右子树为空的时候,最后一次循环是rstart=end-1,t循环结束之后rstart==end的,而rend被设置为end-1,之后的循环中又没有处理rend<rstart的分支,这就是错的了
然后看lend = rstart -1,如果左子树为空,rstart是第一个元素,lend-1就越数组下界了
这些都改了之后:
在这里插入图片描述
运行还是会有问题,而且还会发生数组越界的错误提示
后来仔细想了一下,发现lend和rstart最初不应该是从0开始的,应该是从start开始的
在这里插入图片描述
在这里插入图片描述
把那些0改正了之后发现运行可以通过了???

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值