剑指offer(21-30)

21、栈的压入、弹出序列

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

思路:这道题的难点在于颠覆了我们原有认为的出栈顺序,使得刚看到的时候有点懵,假设序列1,2,3,4,5为某栈的压入顺序,我们传统的认为出栈顺序应该为5,4,3,2,1,但是题目中定义的是4,5,3,2,1。所以我们需要有一种算法来符合这种操作,我们可以借用一个辅助栈来遍历压栈顺序,算法的策略如下

先将第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1不等于4,所以我们继续压栈,直接相等以后开始出栈。出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环直到压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。

图解

秒杀剑指offer(21-30)_java
秒杀剑指offer(21-30)_java_02秒杀剑指offer(21-30)_java_03
秒杀剑指offer(21-30)_java_04



代码

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<Integer> stack=new Stack<Integer>();
        int index=0;
        for(int i=0;i<popA.length;i++){
            stack.push(pushA[i]);
            while(!stack.isEmpty()&&stack.peek()==popA[index]){
                stack.pop();
                index++;
            }
        }
        return stack.isEmpty()?true:false;
    }
}

22、从上往下打印二叉树

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

思路:这道题目定义了二叉树的打印顺序,即从上到下,同层结点从左到右,所以我们想到用双向队列Deque来保存二叉树中的结点值,即每一次打印一个结点的时候,如果该节点有子结点,则把该节点的子结点放到一个队列的尾部。接下来到队列的头部取出最早进入队列的结点放到ArrayList中,重复前面的操作,直至队列中的所有结点都存放到ArrayList中。

图解

秒杀剑指offer(21-30)_java_05

秒杀剑指offer(21-30)_java_06

把队列中的元素不断循环添加到数组中

代码

import java.util.ArrayList;
import java.util.Deque;
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> list=new ArrayList<Integer>();
        if(root==null){
            return list;
        }
        Deque<TreeNode> deque=new LinkedList<TreeNode>();
        deque.add(root);
        while(!deque.isEmpty()){
            TreeNode node=deque.pop();
            list.add(node.val);
            if(node.left!=null){
                deque.add(node.left);
            }
            if(node.right!=null){
                deque.add(node.right);
            }
            
        }
        return list;
    }
    //判断左/右子结点是否为空
}

23、二叉搜索树的后续遍历序列(23)

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

思路:这道题我们需要了解二叉搜索树和后序遍历的特点

  • 后序遍历的根结点为数组中的最后一个元素,以根结点分开的左右子树在数组中是连续的,如数组的后序遍历是4526731,那么根结点就是1,以1为根结点的左子树是452,右子树为673
  • 二叉搜索的左子结点<=根结点,右子结点>=根结点,

由于题目中的数组的任意两个数字都互不相同,所以左子结点<根结点,右子结点>根结点。

我们可以利用这两个特点采用递归的思想来解决。

代码

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length == 0return false;
        return IsTreeBST(sequence, 0, sequence.length-1);
    }
    public boolean IsTreeBST(int [] sequence,int start,int end ){
        if(end <= start) return true;
        int i = start;
        for (; i < end; i++) {
            if(sequence[i] > sequence[end]) break;
        }
        for (int j = i; j < end; j++) {
            if(sequence[j] < sequence[end]) return false;
        }
        return IsTreeBST(sequence, start, i-1) && IsTreeBST(sequence, i, end-1);
    }
 
}

24、二叉树中和为某一值的路径

题目:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

思路:这道题的思路借鉴了AnApplePie的博客,首先每条路径一定是从根结点到叶子结点,在数据结构中从根结点到叶子结点的遍历使用的是DFS,因此采用的是前序遍历的DFS,即根结点-->左子树-->右子树,随后考虑一次遍历完成后的处理,当一次遍历完成后,如果输入整数值恰好等于节点值之和,则输出这条路径并且回退一个节点;如果不等于则直接回退一个节点,即回退到当前节点的父节点,如果该父节点有右孩子,则继续遍历,否则继续回退。考虑回退到根节点,此时如果它有右孩子,则继续遍历,否则整个DFS结束。

代码

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<ArrayList<Integer>> pathList=new ArrayList<ArrayList<Integer>>();
    ArrayList<Integer> path=new ArrayList<Integer>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
       if(root==null){
           return pathList;
       }
        path.add(root.val);
        if(root.left==null&&root.right==null&&root.val==target){
            pathList.add(new ArrayList<Integer>(path));
        }
        if(root.val<=target&&root.left!=null){
            FindPath(root.left,target-root.val);
        }
        if(root.val<=target&&root.right!=null){
            FindPath(root.right,target-root.val);
        }
        path.remove(path.size()-1);
        return pathList;
    }
}

25、复杂链表的复制

题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路:这道题做的时候没有什么思路,查了一些资料,主要参考了牛客网chancy的解题思路,发现大部分人都是采用三次遍历链表的方式,其解题的主要思路如下

  • 在旧的链表中创建新的链表,此时不考虑新创建链表中的random指针的指向
秒杀剑指offer(21-30)_java_07
  • 根据旧链表的关系,为新的链表搭建random指针关系
秒杀剑指offer(21-30)_java_08
  • 将新构造的链表进行拆分,获取旧链表的一份拷贝
秒杀剑指offer(21-30)_java_09

代码

/*
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)
    
{
        if(pHead==null){
            return null;
        }
        RandomListNode cur=pHead;
        //克隆结点
        while(cur!=null){
            RandomListNode clone1=new RandomListNode(cur.label);
            clone1.next=cur.next;
            cur.next=clone1;
            cur=clone1.next;
        }
        //插入随机指针
        cur=pHead;
        while(cur!=null){
            RandomListNode clone2=cur.next;
            if(cur.random!=null){
                clone2.random=cur.random.next;
            }
            cur=clone2.next;
        }
        //分割链表
        cur=pHead;
            RandomListNode resultHead=cur.next;
        while(cur.next!=null){
            RandomListNode clone3=cur.next;
            cur.next=clone3.next;
            cur=clone3;
        }
        return resultHead;
    }
}

26、二叉搜索树与双向链表

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

思路:对于二叉搜索树,采用中序遍历就是按照结点值的从小到大的顺序遍历的,所以很容易想到使用中序遍历将二叉树中的结点存放到一个数组中,对存放数组中的结点循环遍历添加结点间的关系

秒杀剑指offer(21-30)_java_10

import java.util.List;
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 {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null){
            return null;
        }
        //中序遍历,并将值存入到一个List数组中
        ArrayList<TreeNode> list=new ArrayList<TreeNode>();
        addList(pRootOfTree,list);
        return convert(list);
    }
    public void addList(TreeNode pRootOfTree,ArrayList<TreeNode> list){
        if(pRootOfTree.left!=null){
            addList(pRootOfTree.left,list);
        }
        list.add(pRootOfTree);
        if(pRootOfTree.right!=null){
            addList(pRootOfTree.right,list);
        }
    }
    public TreeNode convert(ArrayList<TreeNode> list){
        for(int i=0;i<list.size()-1;i++){
            list.get(i).right=list.get(i+1);
            list.get(i+1).left=list.get(i);
        }
        return list.get(0);
    }
}

27、字符串的排列(没理解)

题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入一个字符串,长度不超过9(可能字符重复),字符只包括大小写字母

思路:这道题刚开始没有想到怎么解,参考了博文才做出来,采用的方式是回溯法,有图解的方式很容易解释,题目中要求是按字典序打印,字典序就是按字母的顺序排列,以a、b、c.....z排列。

秒杀剑指offer(21-30)_java_11
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class Solution {
    public ArrayList<String> Permutation(String str) {
    ArrayList<String> res=new ArrayList<String>();
        if(str!=null&&str.length()>0){
            process(str.toCharArray(),0,res);
            Collections.sort(res);
        }
        return res;
    }
    public void process(char[] str,int i,ArrayList<String> list){
        if(i==str.length-1){
            String val=String.valueOf(str);
            if(!list.contains(val)){
                list.add(val);
            }
        }else{
            for(int j=i;j<str.length;j++){
                swap(str,i,j);
                process(str,i+1,list);
                swap(str,i,j);
            }
        }
    }
    public void swap(char[] str,int i,int j){
        char temp=str[i];
        str[i]=str[j];
        str[j]=temp;
    }
}

28、数组中出现次数超过一半的数字(28)

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

思路:这道题可以先对数组中的元素进行排序,如果数组中有元素出现的次数大于数组长度的一半,则该元素必定value=array[length/2];我们可以定义一个计数变量count,循环判断数组中元素与value,相等时则count++,最后判断coount的大小,大于数组的一半长度时则返回value,否则返回0。

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        for(int i=0;i<array.length-1;i++){
            for(int j=0;j<array.length-1-i;j++){
                if(array[j]>array[j+1]){
                    swap(array,j,j+1);
                }
            }
        }
        int manyNumber=array[(array.length-1)/2];
        int count=0;
        for(int i=0;i<=array.length-1;i++){
            if(array[i]==manyNumber){
                count++;
            }
        }
        if(count>array.length/2){
            return manyNumber;
        }
        return 0;
        
    }
    public void swap(int[] array,int i,int j){
        int temp=0;
        temp=array[i];
        array[i]=array[j];
        array[j]=temp;
    }
}

29、最小的K个数

题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

思路:这道题的思路是先对数组进行从小到大排序,然后遍历排序后的数组,取出前K个返回。

代码

import java.util.List;
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result=new ArrayList<Integer>();
        //对数组进行排序
        if(k>input.length){
            return result;
        }
        for(int i=0;i<input.length-1;i++){
            int minSize=i;
            int min=input[minSize];
            for(int j=i+1;j<=input.length-1;j++){
                if(min>input[j]){
                    min=input[j];
                    input[j]=input[i];
                    input[i]=min;
                }
            }
            
        }
        for(int i=0;i<k;i++){
            result.add(input[i]);
        }
        return result;
    }
}

30、连续子数组的最大和

题目:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

思路:这道题可以动态规划来解决,最大子数组的和是由当前元素和之前最大连续子数组的和叠加在一起形成的,因此需要遍历n个元素,看看当前元素和其之前的最大连续子数组的和能否构造出最大值。

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int len = array.length;
        int[] dp = new int[len];
        int max = array[0];
        dp[0] = array[0];
        for(int i=1; i < len; i++){
            int newMax = dp[i-1] + array[i];
            if(newMax > array[i])
                dp[i] = newMax;
            else
                dp[i] = array[i];
            if(dp[i] > max)
                max = dp[i];
        }
        return max;
    }
}