剑指Offer/21-30


21. 栈的压入、弹出序列

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

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA.length == 0 || popA.length == 0 || popA.length != pushA.length){
            return false;
        }
        Stack<Integer> stack = new Stack<>();
        int i = 0;//指针i指向pushA第一个元素
        int j = 0;//指针j指向popA第一个元素
        for( ; i<pushA.length; i++){
            stack.push(pushA[i]);
            while(!stack.isEmpty() && stack.peek() == popA[j]){
                stack.pop();
                j++;
            }
        }
        return stack.isEmpty();
    }

}

22. 从上往下打印二叉树

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

/**
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<>();
        if(root == null){
            return result;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(! queue.isEmpty()){
            TreeNode temp = queue.poll();
            result.add(temp.val);
            if(temp.left != null){
                queue.offer(temp.left);
            }
            if(temp.right != null){
                queue.offer(temp.right);
            }
        }
        return result;
    }
}

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

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

public class Solution {
    public boolean helpVerify(int[] sequence, int start, int root){
        if(start >= root){
            return true;
        }
        int i;
        int key = sequence[root];//根节点的值
        //找到左右子树的分界点
        for(i = 0; i < root; i++){
            if(sequence[i] > key){
                break;
            }//跳出循环时i指向的是大于根节点值的第一个数
        }
        //判断右子树中是否存在小于根节点的数,如果有,直接返回false
        for(int j = i; j < root; j++){
            if(sequence[j] < key){
                return false;
            }
        }
        return helpVerify(sequence, start, i-1) && helpVerify(sequence, i, root-1);
    }
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence == null || sequence.length == 0){
            return false;
        }
        return helpVerify(sequence, 0, sequence.length - 1);
    }
}

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

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

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    private ArrayList<ArrayList<Integer>> result = new ArrayList<>();
    private ArrayList<Integer> list = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root == null){
            return result;
        }
        list.add(root.val);
        target -= root.val;
        if(target == 0 && root.left == null && root.right == null){
            result.add(new ArrayList<Integer>(list));
        }
        ArrayList<ArrayList<Integer>> result1 = FindPath(root.left, target);
        ArrayList<ArrayList<Integer>> result2 = FindPath(root.right, target);
        list.remove(list.size()-1);
        return result;
    }
}

25. 复杂链表的复制

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

/*
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 currentNode = pHead;
        //1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面
        while(currentNode != null){
            RandomListNode cloneNode = new RandomListNode(currentNode.label);
            RandomListNode nextNode = currentNode.next;
            currentNode.next = cloneNode;
            cloneNode.next = nextNode;
            currentNode = nextNode;
        }
        
        currentNode = pHead;
        //2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next
        while(currentNode != null){
            currentNode.next.random = currentNode.random == null ? null: currentNode.random.next;
            currentNode = currentNode.next.next;
        }
        
        //3、拆分链表,将链表拆分为原链表和复制后的链表
        currentNode = pHead;
        RandomListNode pCloneHead = pHead.next;
        while(currentNode != null){
            RandomListNode cloneNode = currentNode.next;
            currentNode.next = cloneNode.next;
            cloneNode.next = cloneNode.next == null ? null: cloneNode.next.next;
            currentNode = currentNode.next;
        }
        return pCloneHead;
     }
}

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

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

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
import java.util.ArrayList;
public class Solution {
    TreeNode current = null;//定义链表当前结点
    TreeNode root = null;//定义链表头部的结点
    //中序遍历修改指针
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null){
            return null;
        }
        Convert(pRootOfTree.left);//左
        if(current == null){//根节点
            current = pRootOfTree;
            root = pRootOfTree;
        }else{
            current.right = pRootOfTree;
            pRootOfTree.left = current;
            current = pRootOfTree;
        }
        Convert(pRootOfTree.right);//右
        return root;
    }
}

27. 字符串的排列

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

解题思路:
把字符串分为两部分:一部分是字符串的第一个字符,另一部分是第一个字符以后的所有字符。
(1) 求所有可能出现在第一个位置的字符,即把第一个字符和后面所有字符交换。(for循环、交换操作)
(2) 第二步是固定住第一个字符,求后面所有字符的排列。(递归)
(3) 而“求后面所有字符的排列”即可按照上面的思路递归进行。
实现借助一个char[],通过交换元素得到不同的排列,在递归返回时将其装入ArrayList。

经验:
(1) DFS的思想,递归过程中数组是如何完成交换和恢复的
(2) 全排列的思想,先确定第i个,然后与后序元素的全排列进行拼接
(3) 去重
(4) Collections的sort方法,对传入的 lis t进行排序,直接改变原 list,无返回值。

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public ArrayList<String> Permutation(String str){
        ArrayList<String> list = new ArrayList<>();
        if(str == null || str.length() == 0){
            return list;
        }
        char[] strs = str.toCharArray();
        helperPermutation(list, strs, 0);
        //排序
        Collections.sort(list);
        return list;
    }
    public void helperPermutation(ArrayList<String> list, char[] strs, int cur){
        //递归终止条件
        if(cur == strs.length-1){
            String result = String.valueOf(strs);
            list.add(result);
        }
        for(int i = cur; i < strs.length; i++){
            if(i != cur && strs[i] == strs[cur]){//遇到重复的字符直接continue
                continue;
            }else{
                //交换
                swap(strs, cur, i);
                //确定好cur位置,对strs[cur+1 ~ length-1]范围内的字符数组完成全排列
                helperPermutation(list, strs, cur+1);
                //恢复原数组
                swap(strs, cur, i);
            }
        }
    }
    public void swap(char[] strs, int cur, int i){
            char temp = strs[cur];
            strs[cur] = strs[i];
            strs[i] = temp;
    }
}

28. 数组中出现次数超过一半的数字

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

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
//         //方法1:使用HashMap
//         HashMap<Integer, Integer> map = new HashMap<>();
//         for(int i = 0; i < array.length; i++){
//             if(map.get(array[i]) == null){
//                 map.put(array[i], 1);
//             }else{
//                 map.put(array[i], map.get(array[i])+1);
//             }
//         }
//         for(int i = 0; i < array.length; i++){
//             if(2 * map.get(array[i]) > array.length){
//                 return array[i];
//             }
//         }
//         return 0;
        if(array.length == 0 || array == null)
            return 0;
        int vote = array[0];
        int count = 1;
        for(int i = 1; i < array.length; i++){
            if(array[i] == vote){
                count++;
            }else{
                count--;
                if(count == 0){
                    vote = array[i];
                    count = 1;
                }
            }
        }
        //需要重新统计一遍  以核实出现的次数是否大于数组长度的一半
        count = 0;
        for(int i = 0; i < array.length; i++){
            if(vote == array[i])
                count++;
        }
        if(count > array.length/2){
            return vote;
        }else{
            return 0;
        }
    }
}

29. 最小的K个数

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

方法1:建立大顶堆
方法2:采用快速排序

//方法1:建堆
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list  = new ArrayList<>();
        int len = input.length;
        if(len <  k || k == 0){
            return list;
        }
       // 新建一个大顶堆  初始容量大小为k
        // java优先队列默认是小顶堆,我们要设置为大顶堆 这里用的lambda表达式实现比较器接口
        PriorityQueue<Integer> pq = new PriorityQueue<>(k,(a, b)->(b-a));
        for(int i = 0; i < len; i++){
            // 队列没满时,需要加满
            if(pq.size() < k){
                pq.add(input[i]);
            }else{
                // 队列满了即i>=k时,需要开始判断,当前值小于堆顶时需要加入队列
                if(input[i] < pq.peek()){
                    pq.poll();//删除队首元素,即堆顶元素
                    pq.add(input[i]);
                }
            }
        }
        list.addAll(pq);
        return list;
    }
}
//方法2:快速排序
//对数组[left,right]进行快排切分后得到基准值的索引index,由此有三个区间[left, index), index, [index+1, right)。[left,index)为小于等于index的值,[index+1,right)为大于等于index的值。

//若index==k-1,则说明在index之前的数就是前k大的数;
//若index<k-1,则说明第k大的在index右边 [index+1,r];
//若index>k-1,则答案在左边 [left,index]。
public class Solution{
        public int partition(int[] input, int left, int right){
            int pivot = input[left];
            while(left < right){
                while(left < right && input[right] >= pivot){
                    right--;
                }
                input[left] = input[right];
                while(left < right && input[left] <= pivot){
                    left++;
                }
                input[right] = input[left];
            }
            input[left] = pivot;
            return left;
        }
        public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
            ArrayList<Integer> result = new ArrayList<>();
            if(input == null || input.length == 0 || input.length < k || k == 0){
                return result;
            }
            int left = 0;
            int right = input.length - 1;
            while(left < right){
                int index = partition(input, left, right);
                //当找到第k大的数时,index+1=right 是因为第k大的数可能在最后
                if(index == k-1 || index+1 == right){
                    for(int i = 0; i < k; i++){
                        result.add(input[i]);
                    }
                    return result;
                }else if(index < k){//当前的数在第k大的左边
                    left = index+1;
                }else{//第k大的数在当前数的左边
                    right = index;
                }
            }
            return result;
    }
}

注:依照这种解题方式,对于第k小的数这类题,我们也可以利用上述方法判断得出。

30. 连续子数组的最大和

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

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
//        //方法2:空间复杂度为O(1)
        int result = array[0];
        int temp = 0;
        for(int k: array){
            if(temp + k < 0){
                temp = 0;
            }else{
                temp += k;
            }
            result = Math.max(result, temp);
        }
        if(result != 0){
            return result;
        }
        // result <= 0 的情况
        // 说明数组都是负数,选取一个最大值为答案。
        Arrays.sort(array);
        return array[array.length-1];
        
//         //方法1:动态规划  时间复杂度和空间复杂度均为O(n)
//         int[] dp = new int[array.length + 1];
//         dp[0] = 0;
//         int result = array[0];
//         for(int i = 1; i <= array.length; i++){
//             dp[i] = Math.max(array[i-1], dp[i-1] + array[i-1]);
//             result = Math.max(result, dp[i]);
//         }
//         return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值