剑指Offer(四)

题目汇总
31.栈的压入、弹出序列(中等),本题考查举例让抽象具体化
32.从上往下打印二叉树(中等),本题考查举例让抽象具体化
33.二叉搜索树的后序遍历序列(中等),本题考查举例让抽象具体化
34.二叉树中和为某一值的路径(中等),本题考查举例让抽象具体化
35.复杂链表的复制(中等),本题考查分解让复杂问题简单
36.二叉搜索树与双向链表(中等),本题考查分解让复杂问题简单
37.序列化二叉树(困难)
38.字符串的排列(中等),本题考查分解让复杂问题简单
39.数组中出现次数超过一半的数字(简单),本题考查时间效率
40.最小的k个数(简单),本题考查时间效率

31.栈的压入、弹出序列(中等),本题考查举例让抽象具体化

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

思路:

建立一个辅助栈,把输入的第一个序列中的数字依次压入该辅助栈,如果下一个弹出的数字刚好是栈顶元素,那么出栈;如果下一个弹出的数字不是栈顶元素,那么还没有入栈的元素压入辅助栈,直到下一个弹出的数字是栈顶元素。最后如果辅助栈为空,说明该序列是一个弹出序列。

import java.util.ArrayList;
import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if (pushA.length == 0 || popA.length == 0) {
            return false;
        }
        Stack stack = new Stack<>();//辅助栈
        int j = 0;
        for(int i=0;i

32.从上往下打印二叉树(中等)

题目一描述:从上往下打印出二叉树本题考查举例让抽象具体化

从上往下打印出二叉树的每个节点,同层节点从左至右打印。
例如:
给定二叉树: [3,9,20,null,null,15,7],返回:[3,9,20,15,7]


二叉树.PNG

思路:

举例分析可以找到打印二叉树的规律:
每次打印一个节点的时候,如果该节点有子节点,则把该节点的子节点放到一个队列的末尾。然后到队列的头部取出最早进入队列的节点,重复前面的打印操作,直至队列中所有的节点都被打印出来。

整理一下JAVA中队列的常用方法

JAVA中队列是接口,继承了Collection类,先进先出
add(Object e)   //将指定元素加入此队列的尾部。
Object element()   //获取队列头部的元素,但是不删除该元素。
boolean offer(Object e) //将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
Object peek()  //获取队列头部的元素,但是不删除该元素,如果此队列为空,则返回null。
Object poll() //获取队列头部的元素,并删除该元素,如果此队列为空,则返回null。
Object remove();   //获取队列头部的元素,并删除该元素

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 PrintFromTopToBottom(TreeNode root) {
        ArrayList list = new ArrayList<>();
        if(root==null)
            return list;
        //果需要经常执行插入、删除操作来改变List集合大小,则应该使用LinkedList集合,而不是ArrayList。
        //使用ArrayList、Vector集合将需要经常重新分配内存数组的大小,其时间开销往往是使用LinkedList时时间开销的几十倍,效果很差。
        Queue queue = new LinkedList();
        queue.offer(root);//将根节点插入队列
        while(!queue.isEmpty()){
            TreeNode temp = queue.poll();//获取并移除队头
            list.add(temp.val);
            if(temp.left != null)queue.offer(temp.left);
            if(temp.right != null)queue.offer(temp.right);
        }
    return list;
    }
}

题目二描述:

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
例如:
给定二叉树: [3,9,20,null,null,15,7]
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]


二叉树.PNG

题目三描述:按照之字形顺序打印二叉树

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7]


二叉树.PNG

返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]

33.二叉搜索树的后序遍历序列(中等),本题考查举例让抽象具体化

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

思路:递归

二叉搜索树定义:
又称二叉排序树,它或者是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
在后序遍历序列中,最后一个数字是树的根节点的值,数组中前面的数字可以分为两部分:第一部分是左子树节点的值,它们都比根节点的值小;第二部分是右子树节点的值,它们都比根节点的值大。递归确定子树的结构。

//2020.2.12
import java.util.Arrays;
public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length<=0||sequence==null)
            return false;
        int root = sequence[sequence.length-1];//最后一个数是二叉搜索树的根
        //二叉搜索树中左子树节点的值小于根节点的值
        int i = 0;
        for(;iroot)
                break;
        }
        //二叉搜索树中右子树节点的值大于根节点的值
        int j = i;
        for(;j0)
            left = VerifySquenceOfBST(Arrays.copyOfRange(sequence,0,i));
        //判断右子树是不是二叉搜索树
        boolean right = true;
        if(i

34.二叉树中和为某一值的路径(中等),本题考查举例让抽象具体化

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

思路:递归

当用前序遍历的方式访问到某一节点时,我们把该节点添加到路径上,更新目标值为目标值-当前节点值。。如果该节点为叶节点,并且路径中目标值为0,则该路径符合要求,加到结果中;如果该节点不是叶节点,则继续访问它的子节点。当前节点访问结束后,递归函数回到它的父结点。


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

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

    }

}
*/
public class Solution {
    ArrayList> result = new ArrayList>();
    ArrayList path = new ArrayList();
    public ArrayList> FindPath(TreeNode root,int target) {
        if(root==null)
            return result;
        FindPath(root,target,result,path);
        return result;
    }
    public void FindPath(TreeNode root,int target,ArrayList> result, ArrayList path){
         if (root == null) {
            return;
        }
 
        path.add(root.val);
        target -= root.val;
 
        if(target < 0){
            return;
        }
        //叶子节点,路径上节点的值等于输入的值
        if(root.left==null&&root.right==null&&target==0){
            result.add(path);
        }
        //不是叶节点,遍历它的子节点
        if(root.left!=null)
            FindPath(root.left,target,result,new ArrayList(path));
        if(root.right!=null)
            FindPath(root.right,target,result,new ArrayList(path));     
    }  
}

35.复杂链表的复制(中等),本题考查分解让复杂问题简单

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


一个复杂链表.PNG

思路:将整个复制过程分为三个步骤,为每个步骤定义一个函数

第一步:复制原始链表的任意结点N并创建新节点N*,再把新节点链接到N的后面


第一步.PNG

第二步:设置复制出来节点的random


第二步.PNG

第三步:拆分成两个链表,奇数位置的节点用next链接起来就是原始链表,偶数位置的节点用next链接起来就是复制出来的链表
第三步.PNG
/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        CloneNodes(pHead);
        ClonnectSiblingNodes(pHead);
        return ReconnectNodes(pHead);
    }
    //根据原始链表的每个节点N创建对应的N`,把N`链接在N的后边
    public void CloneNodes(RandomListNode pHead){
        RandomListNode pNode = pHead;
        while(pNode!=null){
            RandomListNode pCloned = new RandomListNode(pNode.label);
            pCloned.label = pNode.label;
            pCloned.next = pNode.next;
            pCloned.random = null;
            pNode.next = pCloned;
            pNode = pCloned.next;
        }
    }
    //设置random之后的链表
    public void ClonnectSiblingNodes(RandomListNode pHead){
        RandomListNode pNode = pHead;
        while(pNode!=null){
            RandomListNode pCloned = pNode.next;
            if(pNode.random!=null){
                pCloned.random = pNode.random.next;
            }
            pNode = pCloned.next;
        }
    }
    //拆分链表
    public static RandomListNode ReconnectNodes(RandomListNode pHead){
        RandomListNode pNode = pHead;
        RandomListNode pClonedHead = null;
        RandomListNode pClonedNode = null;
        
        if(pNode!=null){
            pClonedHead = pClonedNode = pNode.next;
            pNode.next = pClonedNode.next;
            pNode = pNode.next;
        }
        while(pNode!=null){
            pClonedNode.next = pNode.next;
            pClonedNode = pClonedNode.next;
            pNode.next = pClonedNode.next;
            pNode = pNode.next;
        }
        return pClonedHead;
    }
}

36.二叉搜索树与双向链表(中等),本题考查分解让复杂问题简单

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


二叉搜索树与转换之后的排序双向链表.PNG

思路:中序遍历

转自https://www.cnblogs.com/yi-hui/p/8983825.html
定义两个分别指向链表最左端和最右端的变量(或者说指针)。然后在中序遍历中不断的修改节点之间的指针指向,右指针不断的右移动,直到遍历结束。

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

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

    }

}
*/
public class Solution {
    TreeNode leftHead = null;//指向链表最左端的指针
    TreeNode rightHead = null;//指向链表最右端的指针
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree == null) 
            return  null;
        Convert(pRootOfTree.left);
        if (leftHead == null) {
            leftHead = pRootOfTree;
            rightHead = pRootOfTree;
        }
        else {
            rightHead.right = pRootOfTree;
            pRootOfTree.left = rightHead;
            rightHead = pRootOfTree;
        }
        Convert(pRootOfTree.right);
        return leftHead;
    }
}

37.序列化二叉树(困难),本题考查

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


二叉树.PNG

思路:递归分解,逐个解决

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

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

    }

}
*/
public class Solution {
    String Serialize(TreeNode root) {
        if (root == null) {
            return "#";
        } 
        else{
            return root.val + "," + Serialize(root.left) + "," + Serialize(root.right);
        }
    }
    int index = -1;
    TreeNode Deserialize(String str) {
        String[] s = str.split(",");//将字符串按照","割成字符数组
        index++;//使用index来设置树节点的val值
        if(s.length

38.字符串的排列,本题考查分解让复杂问题简单

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

思路:递归

转载自https://blog.csdn.net/zangdaiyang1991/article/details/88657331
采用递归方法,逐个交换char数组中的元素
如:将字符串abcdefg分成俩部分,a和bcdefg,然后将a和bcdefg各位不停的交换。bcdefg则继续进行相同的操作。最后进行一下排序。
1、对于无重复值的情况
固定第一个字符,递归取得首位后面的各种字符串组合;
再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合; *递归的出口,就是只剩一个字符的时候,
递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
2、假如有重复值呢?
由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。
例如abb,第一个数与后面两个数交换得bab,bba。然后abb中第二个数和第三个数相同,就不用交换了。
但是对bab,第二个数和第三个数不 同,则需要交换,得到bba。
由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。
换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,所以第一个数就不再用与第三个数交换了。再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!

import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;

public class Solution {
    public ArrayList Permutation(String str) {
        ArrayList ret = new ArrayList<>();
        if (str == null || str.isEmpty()) {
            return ret;
        }
        char[] arr = str.toCharArray();
        permutation(arr, 0, ret);
        Collections.sort(ret);
        return ret;
    }
    
    private void permutation(char[] arr, int begin, ArrayList cache) {
        if (begin == arr.length - 1) {
            cache.add(new String(arr));
            return;
        }
        
        int len = arr.length;
        for (int i=begin; i

39.数组中出现次数超过一半的数字(简单),本题考查时间效率

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

思路一:如果有符合条件的数字,那么排序之后位于数组中间的数字一定是我们要找的那个数字,排序的时间复杂度为O(nlogn)

数组中有一个数字出现的次数超过数组长度的一半。如果把这个数组排序,那么排序之后位于数组中间的数字一定是我们要找的那个数字,也是统计学上的中位数,即长度为n的数组中第n/2大的数字。找到中位数后,遍历数组,统计这个中位数出现的次数,如果次数大于n/2,就返回这个这个中位数;如果次数小于等于n/2,说明这个数字不存在,就返回0。

import java.util.Arrays;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int len = array.length;
         if(len<1)
            return 0;
        Arrays.sort(array);//数组排序
        int num = array[len/2];//最中间的一个数
        int count = 0;
        for(int i=0;i<=len-1;i++){
            if(array[i]==num)
                count++;
        }
        if(count<=len/2){
            return 0;
        }
    return num;
    }
}

思路二:如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。时间复杂度为O(n)

数组中有一个数字出现的次数超过数组长度的一半,那么其他数字出现的次数总和一定比这个数字出现的次数要小。遍历数组的时候保存两个值,一个是数组中的一个数字,另一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;不同则减一。如果次数为0,则保存下一个数字,并把次数设为1。遍历结束后,所保存的数字即为所求,然后再判断它是否符合条件。

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int len = array.length;
        if(len<=0)
            return 0;
        int result = array[0];
        int count = 1;//
        // 遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1
        for(int i=1;i

40.最小的k个数(简单),本题考查时间效率

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
输入:input= [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

思路一:排序

把输入的n个整数排序,排序之后位于最前面的k个数就是最小的k个数,但是时间复杂度是O(nlogn)。

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList list = new ArrayList<>();
        Arrays.sort(input);
        if(input.length<=0||input.length

思路二:使用大根堆完成n个整数中最小的K个数的查找****

创建一个大小为k的数组,遍历n个整数,如果遍历到的数小于大小为k的数组的最大值,则将此数与其最大值替换。由于每次都要拿n个整数和数组中的最大值比较,所以选择大根堆这一数据结构。(自己根本想不到哦我太辣鸡了)
来自牛客网的题解https://blog.nowcoder.net/n/8b24bf63a0ea4ca0b387794423df9d20?f=comment

import java.util.ArrayList;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList list = new ArrayList<>();
        if (input == null || input.length == 0 || k > input.length || k == 0)
            return list;
        int[] arr = new int[k + 1];//数组下标0的位置作为哨兵,不存储数据
        //初始化数组
        for (int i = 1; i < k + 1; i++)
            arr[i] = input[i - 1];
        buildMaxHeap(arr, k + 1);//构造大根堆
        for (int i = k; i < input.length; i++) {
            if (input[i] < arr[1]) {
                arr[1] = input[i];
                adjustDown(arr, 1, k + 1);//将改变了根节点的二叉树继续调整为大根堆
            }
        }
        for (int i = 1; i < arr.length; i++) {
            list.add(arr[i]);
        }
        return list;
    }
    
    public void buildMaxHeap(int[] arr, int length) {
        if (arr == null || arr.length == 0 || arr.length == 1)
            return;
        for (int i = (length - 1) / 2; i > 0; i--) {
            adjustDown(arr, i, arr.length);
        }
    }
    //堆排序中对一个子二叉树进行堆排序
    public void adjustDown(int[] arr, int k, int length) {
        arr[0] = arr[k];//哨兵
        for (int i = 2 * k; i <= length; i *= 2) {
            if (i < length - 1 && arr[i] < arr[i + 1])
                i++;//取k较大的子结点的下标
            if (i > length - 1 || arr[0] >= arr[i])
                break;
            else {
                arr[k] = arr[i];
                k = i; //向下筛选
            }
        }
        arr[k] = arr[0];
    }
}

你可能感兴趣的:(剑指Offer(四))