剑指Offer(java答案)(21-30)

21、包含min函数的栈

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

import java.util.Stack;
 
/*思路:用一个栈data保存数据,用另外一个辅助栈min保存依次入栈最小的数
比如,data中依次入栈,5,  4,  3, 8, 10, 11, 12, 1
       则min依次入栈,5,  4,  3,3,3, 3, 3, 1
每次入栈的时候,如果入栈的元素比min中的栈顶元素小或等于则入栈,否则不如栈。
*/ 
public class Solution {
    Stack data=new Stack();
    Stack min=new Stack();
    
    public void push(int node) {
        if(min.empty()){
            min.push(node);
        }else{
            int top=(int)min.peek();
            if(node<top){
               min.push(node);
            }else{
                min.push(top);
            }
        }
        data.push(node);
    }
     
    public void pop() {
        if(!(data.empty())){
            data.pop();
            min.pop();
        }
    }
     
    public int top() {
        return (int)data.peek();
    }
     
    public int min() {
       if(min.empty()){
           return 0;
       }
       return (int)min.peek();    
    }
}
复制代码

22、栈的压入、弹出序列

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

【思路】借用一个辅助的栈,push序列依次入栈,每次都判断,栈顶元素和pop序列是否相等,相等则弹出栈,不相等,则push序列继续入栈,最后判断栈是否为空 举例: 入栈1,2,3,4,5 出栈4,5,3,2,1 首先1入辅助栈,此时栈顶1≠4,继续入栈2 此时栈顶2≠4,继续入栈3 此时栈顶3≠4,继续入栈4 此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3 此时栈顶3≠5,继续入栈5 此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3 …. 依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。

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> s = new Stack<Integer>();
        //用于标识弹出序列的位置
        int popIndex = 0;
        for(int i = 0; i< pushA.length;i++){
            s.push(pushA[i]);
            //如果栈不为空,且栈顶元素等于弹出序列
            while(!s.empty() &&s.peek() == popA[popIndex]){
                //出栈
                s.pop();
                //弹出序列向后一位
                popIndex++;
            }
        }
        return s.empty();
    }
}
复制代码

23、从上往下打印二叉树——层次遍历

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

思路:一个队列容器,每次打印节点的时候把此节点的左右子节点加入进去

import java.util.*;
/**
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;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode treeNode = queue.poll();
            if (treeNode.left != null) {
                queue.offer(treeNode.left);
            }
            if (treeNode.right != null) {
                queue.offer(treeNode.right);
            }
            list.add(treeNode.val);
        }
        return list;
    }
}
复制代码

24、二叉搜索树的后序遍历序列

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

public class Solution {
    public static boolean VerifySquenceOfBST(int[] sequence) {
        if(sequence.length ==0){
            return false;
        }
        return VerifySquenceOfBST1(sequence,0,sequence.length-1);
    }
   
    public static boolean VerifySquenceOfBST1(int[] sequence,int start,int end) {
        
        if(start > end)
            return true;
        int root=sequence[end];//后序遍历最后一个节点为根节点
       
       //在二叉搜索树中左子树节点小于根节点
        int i=0;
        for(;i<end;i++){
            if(sequence[i]>root){
                break;
            }
        }
       
        //在二叉搜索树中右子树节点大于根节点
        int j=i;
        for(;j<end;j++){
            if(sequence[j]<root)
                return false;
        }
        boolean left=true;
        boolean right=true;
        if(i>start){
            left=VerifySquenceOfBST1(sequence,start,i-1);
        }
        if(i<sequence.length-1)
            right=VerifySquenceOfBST1(sequence,i,end-1);
        return (left&&right);
 
    }
}
复制代码

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

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

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

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

    }

}
*/

public class Solution {
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
        ArrayList<ArrayList<Integer>> pathList=new ArrayList<ArrayList<Integer>>();
        if(root==null)
            return pathList;
        Stack<Integer> stack=new Stack<Integer>();
        FindPath(root,target,stack,pathList );
        return pathList;
         
    }
    private void FindPath(TreeNode root, int target, Stack<Integer> path, ArrayList<ArrayList<Integer>> pathList) {
        if(root==null)
            return;
        //如果是叶子节点,判断值是否是目标值
        if(root.left==null&&root.right==null){
            if(root.val==target){
                ArrayList<Integer> list=new ArrayList<Integer>();
                for(int i:path){
                    list.add(new Integer(i));
                }
                list.add(new Integer(root.val));
                pathList.add(list);
            }
        }
        else{//不是叶子节点就遍历其子节点
            path.push(new Integer(root.val));
            //是按照前序遍历的方式查找路径,如果向上退出到父节点时,要回到target值,而不是target-root.val
            FindPath(root.left, target-root.val, path, pathList);
            FindPath(root.right, target-root.val, path,  pathList);
            path.pop();
        }
         
    }
}

复制代码

26、复杂链表的复制

题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。

方法一:用hashMap映射原链表,牺牲O(N)空间换来时间

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.HashMap; 
public class Solution {
 
    public RandomListNode Clone(RandomListNode pHead)
 
    {
 
        if(pHead == null) return null;
 
        HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>();
 
        RandomListNode newHead = new RandomListNode(pHead.label);//复制链表的头结点
 
        RandomListNode pre = pHead, newPre = newHead; 
        map.put(pre, newPre);
 
        //第一步,hashMap保存,原链表节点映射复制链表节点
        while(pre.next != null){ 
            newPre.next = new RandomListNode(pre.next.label); 
            pre = pre.next; 
            newPre = newPre.next; 
            map.put(pre, newPre); 
        }
 
        //第二步:找到对应的random
        pre = pHead; 
        newPre = newHead;
 
        while(newPre != null){ 
            newPre.random = map.get(pre.random); 
            pre = pre.next; 
            newPre = newPre.next; 
        }
 
        return newHead; 
    } 
}
复制代码

方法二:不借用辅助空间

/*
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 pCur = pHead;
        //第一步:复制next 如原来是A->B->C 变成A->A'->B->B'->C->C'
        while(pCur!=null){
            RandomListNode node = new RandomListNode(pCur.label);
            node.next = pCur.next;
            pCur.next = node;
            pCur = node.next;
        }
        
        //第二步
        pCur = pHead;
        //复制random pCur是原来链表的结点 pCur.next是复制pCur的结点
        while(pCur!=null){
            if(pCur.random!=null)
                pCur.next.random = pCur.random.next;
            pCur = pCur.next.next;
        }
        
        //第三步
        RandomListNode head = pHead.next;//复制链表的头结点
        RandomListNode cur = head;//偶数位置为复制链表
        pCur = pHead;//奇数位置为原链表
        //拆分链表
        while(pCur!=null){
            pCur.next = pCur.next.next;
            if(cur.next!=null)//注意最后一个复制节点的时候就没有next的next
                cur.next = cur.next.next;
            cur = cur.next;
            pCur = pCur.next;
        }
        return head;       
    }
}
复制代码

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

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

方法一:递归中序遍历

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

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
//直接用中序遍历
public class Solution {
    TreeNode head = null;
    TreeNode realHead = null;//双向链表的头结点
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null) return null;
        Convert(pRootOfTree.left);
        if (head == null) {
            head = pRootOfTree;
            realHead = pRootOfTree;
        } else {
            head.right = pRootOfTree;
            pRootOfTree.left = head;
            head = pRootOfTree;
        }
        Convert(pRootOfTree.right);
        return realHead;
    }
}
复制代码

方法二:

/** 非递归 */
import java.util.Stack;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null) return pRootOfTree;
         
        TreeNode list = null;
        Stack<TreeNode> s = new Stack<>();
        while(pRootOfTree != null || !s.isEmpty()){
            if(pRootOfTree != null) {
                s.push(pRootOfTree);
                pRootOfTree = pRootOfTree.right;
            } else {
                pRootOfTree = s.pop();
                if(list == null)
                    list = pRootOfTree;
                else {
                    list.left = pRootOfTree;
                    pRootOfTree.right = list;
                    list = pRootOfTree;
                }
                pRootOfTree = pRootOfTree.left;
            }
        }
         
        return list;
    }
}
复制代码

28、字符串的排列

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

扩展:求字符串的全组合

如:abc,全组合为:a,b,c,ab,ac,bc,abc

public final class PermutationCombinationHolder {

    /** 1、数组元素的全组合 */
  public  static void combination(char[] chars) {
        char[] subchars = new char[chars.length]; //存储子组合数据的数组
        //全组合问题就是所有元素(记为n)中选1个元素的组合, 加上选2个元素的组合...加上选n个元素的组合的和
        for (int i = 0; i < chars.length; ++i) {
            final int m = i + 1;
            combination(chars, chars.length, m, subchars, m);
        }
    }

    /**
     *  n个元素选m个元素的组合问题的实现. 原理如下:
     *  从后往前选取, 选定位置i后, 再在前i-1个里面选取m-1个.
     *  如: 1, 2, 3, 4, 5 中选取3个元素.
     *  1) 选取5后, 再在前4个里面选取2个, 而前4个里面选取2个又是一个子问题, 递归即可;
     *  2) 如果不包含5, 直接选定4, 那么再在前3个里面选取2个, 而前三个里面选取2个又是一个子问题, 递归即可;
     *  3) 如果也不包含4, 直接选取3, 那么再在前2个里面选取2个, 刚好只有两个.
     *  纵向看, 1与2与3刚好是一个for循环, 初值为5, 终值为m.
     *  横向看, 该问题为一个前i-1个中选m-1的递归.
     */
    public static void combination(char[] chars, int n, int m, char[] subchars, int subn) {
        if (m == 0) { //出口
            for (int i = 0; i < subn; ++i) {
                System.out.print(subchars[i]);
            }
            System.out.println();
        } else {
            for (int i = n; i >= m; --i) { // 从后往前依次选定一个
                subchars[m - 1] = chars[i - 1]; // 选定一个后
                combination(chars, i - 1, m - 1, subchars, subn); // 从前i-1个里面选取m-1个进行递归
            }
        }
    }
///
///


    /** 2、数组元素的全排列 */
   public static void permutation(char[] chars) {
        permutation(chars, 0, chars.length - 1);
    }

    /** 数组中从索引begin到索引end之间的子数组参与到全排列 */
   public static void permutation(char[] chars, int begin, int end) {
        if (begin == end) { //只剩最后一个字符时为出口
            for (int i = 0; i < chars.length; ++i) {
                System.out.print(chars[i]);
            }
            System.out.println();
        } else {
            for (int i = begin; i <= end; ++i) { //每个字符依次固定到数组或子数组的第一个
                if (canSwap(chars, begin, i)) { //去重
                    swap(chars, begin, i); //交换
                    permutation(chars, begin + 1, end); //递归求子数组的全排列
                    swap(chars, begin, i); //还原
                }
            }
        }
    }

    public static void swap(char[] chars, int from, int to) {
        char temp = chars[from];
        chars[from] = chars[to];
        chars[to] = temp;
    }

    //判断去重
  public static boolean canSwap(char[] chars, int begin, int end) {
        for (int i = begin; i < end; ++i) {
            if (chars[i] == chars[end]) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        final char[] chars = new char[] {'a', 'b', 'c'};
        permutation(chars);
        System.out.println("===================");
        combination(chars);
    }
}
复制代码

方法二:DFS

 import java.util.*;
 
public class Solution {
    private char [] seqs;
    private Integer [] book;
    //用于结果去重
    private HashSet<String> result = new HashSet<String>();
    /**
     * 输入一个字符串,按字典序打印出该字符串中字符的所有排列。
     * 例如输入字符串abc,则打印出由字符a,b,c
     * 所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 结果请按字母顺序输出。
       输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。\
     * @param str
     * @return
     */
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> arrange = new ArrayList<String>();
        if(str == null || str.isEmpty()) return arrange;
        char[] strs = str.toCharArray();
        seqs = new char[strs.length];
        book = new Integer[strs.length];
        for (int i = 0; i < book.length; i++) {
            book[i] = 0;
        }
        dfs(strs, 0);
        arrange.addAll(result);
        Collections.sort(arrange);
        return arrange;
    }
 
    /**
     * 深度遍历法
     */
    private void dfs(char[] arrs, int step){
        //走完所有可能 记录排列
        if(arrs.length == step){
            String str = "";
            for (int i = 0; i < seqs.length; i++) {
                str += seqs[i];
            }
            result.add(str);
            return; //返回上一步
        }
        //遍历整个序列,尝试每一种可能
        for (int i = 0; i < arrs.length; i++) {
            //是否走过
            if(book[i] == 0){
                seqs[step] = arrs[i];
                book[i] = 1;
                //下一步
                dfs(arrs, step + 1);
                //走完最后一步 后退一步
                book[i] = 0;
            }
        }
    }
}
复制代码

方法三:字典序算法

 import java.util.*;

//步骤如下:
//1.从这个序列中从右至左找第一个左邻小于右邻的字符,记录下标为index1 ,如果找不到,说明求解完成。
//2.从这个序列中从右至左找第一个大于str[index1]的字符,记录下标为index2
//3.交换index1和index2的字符,对index1+1后的所有字符进行升序排序,此时得到的即为str按字典序的下一个排列
//4. 重复1~3的步骤,直到全部找完 
 
public class Solution {
    public ArrayList<String> Permutation(String str) {
           ArrayList<String> res = new ArrayList<>();

            if (str != null && str.length() > 0) {
                char[] seq = str.toCharArray();
                Arrays.sort(seq); //排列
                res.add(String.valueOf(seq)); //先输出一个解

                int len = seq.length;
                while (true) {
                    int p = len - 1, q;
                    //从后向前找一个seq[p - 1] < seq[p]
                    while (p >= 1 && seq[p - 1] >= seq[p]) --p;
                    if (p == 0) break; //已经是“最小”的排列,退出
                    //从p向后找最后一个比seq[p]大的数
                    q = p; --p;
                    while (q < len && seq[q] > seq[p]) q++;
                    --q;
                    //交换这两个位置上的值
                    swap(seq, q, p);
                    //将p之后的序列倒序排列
                    reverse(seq, p + 1);
                    res.add(String.valueOf(seq));
                }
            }

            return res;
        }

        public static void reverse(char[] seq, int start) {
            int len;
            if(seq == null || (len = seq.length) <= start)
                return;
            for (int i = 0; i < ((len - start) >> 1); i++) {
                int p = start + i, q = len - 1 - i;
                if (p != q)
                    swap(seq, p, q);
            }
        }

        public static void swap(char[] cs, int i, int j) {
            char temp = cs[i];
            cs[i] = cs[j];
            cs[j] = temp;
        }
}
复制代码

29、数组中出现次数超过一半的数字

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

思路:O(n)的思想是,定义两个变量result 和count,每次循环时,如果array[i]的值等于result ,则count自增,如不等并且count>0,则count自减,count==0,重新对temp赋值为当前array[i],count赋值为1。 如存在大于一半的数,直接返回result 就是了,但测试数据中有不存在的情况,所以最后又来了一遍校验,检查当前result 值是否出现过一半以上。

public class Solution {
   public int MoreThanHalfNum_Solution(int [] array) {
       if(array==null || array.length <= 0){
           return 0;
       }
       
       int result = array[0];
       int count = 1;
       for (int i = 1; i < array.length; i++) {
           if (array[i] == result) {
               count++;
           } else if (count > 0 ) {
               count--;
           } else if(count == 0){
               result = array[i];
               count = 1;
           }
       }
       //验证
       count=0;
       for(int i=0;i<array.length;i++){
           if(array[i]==result)
           		count++;
       }
       return count > array.length/2 ? result : 0;
   }
}
复制代码

30、最小的K个数

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

思路1: 经典常用的算法,快速排序的精髓利用快速排序划分的思想,每一次划分就会有一个数字位于以数组从小到达排列的的最终位置index;

位于index左边的数字都小于index对应的值,右边都大于index指向的值;

所以,当index > k-1时,表示k个最小数字一定在index的左边,此时,只需要对index的左边进行划分即可;

当index < k - 1时,说明index及index左边数字还没能满足k个数字,需要继续对k右边进行划分;

//这种方式的优点是时间复杂度较小为O(n),缺点就是需要修改输入数组。
import java.util.ArrayList;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList aList = new ArrayList();
        if(input.length == 0 || k > input.length || k <= 0)
            return aList;
        int low = 0;
        int high = input.length-1;
        int index = Partition(input,k,low,high);
        while(index != k-1){
            if (index > k-1) {
                high = index-1;
                index = Partition(input,k,low,high);
            }else{
                low = index+1;
                index = Partition(input,k,low,high);
            }
        }
        for (int i = 0; i < k; i++)
            aList.add(input[i]);
        return aList;
    }
     
    //快速排序的分段,小于某个数的放在左边,大于某个数的移到右边
    public int Partition(int[] input,int k,int low,int high){
        int pivotkey = input[k-1];
        swap(input,k-1,low);
        while(low < high){
            while(low < high && input[high] >= pivotkey)
                high--;
            swap(input,low,high);
            while(low < high && input[low] <= pivotkey)
                low++;
            swap(input,low,high);
        }
        return low;
    }
 
 
    private void swap(int[] input, int low, int high) {
        int temp = input[high];
        input[high] = input[low];
        input[low] = temp;
    }
}
复制代码

思路2

  • 可以先创建一个大小为k的数据容器来存储最小的k个数字,从输入的n个整数中一个一个读入放入该容器中,如果容器中的数字少于k个,按题目要求直接返回空;

  • 如果容器中已有k个数字,而数组中还有值未加入,此时就不能直接插入了,而需要替换容器中的值。按以下步骤进行插入:

  • 1、先找到容器中的最大值;

  • 2、将待查入值和最大值比较,如果待查入值大于容器中的最大值,则直接舍弃这个待查入值即可;如果待查入值小于容器中的最小值,则用这个待查入值替换掉容器中的最大值;

  • 3、重复上述步骤,容器中最后就是整个数组的最小k个数字。

-对于这个容器的实现,我们可以使用最大堆的数据结构,最大堆中,根节点的值大于它的子树中的任意节点值。Java中的TreeSet类实现了红黑树的功能,它底层是通过TreeMap实现的,TreeSet中的数据会按照插入数据自动升序排列(按自然顺序)。因此我们直接将数据依次放入到TreeSet中,数组就会自动排序。

public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> leastNum = new ArrayList<Integer>();
        if (input == null || input.length < 1 || k < 1 || k > input.length)
            return leastNum;
        TreeSet<Integer> kSet = new TreeSet<Integer>();
        for (int i = 0; i < input.length; i++) {
            if (kSet.size() < k) {
                kSet.add(input[i]);
            } else {
                if (input[i] < kSet.last()) {
                    kSet.remove(kSet.last());
                    kSet.add(input[i]);
                }
            }
        }
        Iterator<Integer> it = kSet.iterator();
        while (it.hasNext()) {
            leastNum.add(it.next());
        }
 
        return leastNum;
    }
复制代码

声明:此文章为本人原创,如有转载,请注明出处

如果您觉得有用,欢迎关注我的公众号,我会不定期发布自己的学习笔记、AI资料、以及感悟,欢迎留言,与大家一起探索AI之路。

转载于:https://juejin.im/post/5ce8c05551882533161350a9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值