剑指offer (JZ21—JZ30)

JZ21 栈的压入、弹出序列

中等  通过率:30.88%  时间限制:1秒  空间限制:64M

知识点:

描述

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

示例1

输入:

[1,2,3,4,5],[4,3,5,1,2]

返回值:

false

思路:模拟压栈出栈过程就行

import java.util.ArrayList;
import java.util.*;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        Stack<Integer> stack = new Stack();
//         stack.push(pushA[0]);
        int temp = 0;
        int count = 0;
        int i=0,j=0,k=0;
        while(i<pushA.length){
            if(!stack.isEmpty()){
                temp = stack.peek();
                if(temp==popA[j]){
                    stack.pop();
                    count++;
                    j++;
                }
                else{
                    stack.push(pushA[i++]);
                }
            }
            else{
                stack.push(pushA[i++]);
            }
            
        }
//         int c;
        while(!stack.isEmpty()){
            if(popA[count]==stack.pop()) count++;
        }
        if(count==popA.length) return true;
        else return false;
    }
}

JZ22 从上往下打印二叉树

困难  通过率:28.88%  时间限制:1秒  空间限制:64M

知识点:队列

描述

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

示例1

输入:

{5,4,#,3,#,2,#,1}

返回值:

[5,4,3,2,1]

思路:广度优先遍历,用队列

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> ans = new ArrayList();
        if(root==null) return ans;
        Queue<TreeNode> Q = new LinkedList();
        Q.offer(root);
        while(!Q.isEmpty()){
            TreeNode node = Q.poll();
            if(node!=null){
                ans.add(node.val);
                TreeNode left = node.left;
                TreeNode right = node.right;
                if(left!=null) Q.offer(left);
                if(right!=null) Q.offer(right);
            }
        }
        return ans;
    }
}

JZ23 二叉搜索树的后序遍历序列

较难  通过率:24.94%  时间限制:1秒  空间限制:64M

知识点:

描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。(ps:我们约定空树不是二叉搜索树)

示例1

输入:

[4,8,6,12,16,14,10]

返回值:

true

思路:后序遍历,左右根。左子树小于根结点,右子树大于根结点。数组的最后一位就是根结点。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length==0) return false;
        return check(sequence, 0, sequence.length-1);
    }
    
    public boolean check(int[] sequence, int l, int r){
        if(l>=r) return true;//当前子树只有一个结点
        int root = sequence[r];
        int j = r-1;
        while(j>=0 && sequence[j]>root) j--;//找出右子树部分,大于根结点的是右子树,j右子树的起始位置
        
        for(int i=l; i<j; i++){//判断左子树是否有大于根结点的,有就不满足后续遍历
            if(sequence[i]>root) return false;
        }
        return check(sequence, l, j) && check(sequence, j+1, r-1);
    }
}

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

较难  通过率:27.68%  时间限制:1秒  空间限制:64M

知识点:

描述

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

示例1

输入:

{10,5,12,4,7},22

返回值:

[[10,5,7],[10,12]]

示例2

输入:

{10,5,12,4,7},15

返回值:

[]

思路:深度优先遍历

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>> ans = new ArrayList<ArrayList<Integer>>();
    
    public void DFS(TreeNode root, int target, ArrayList<Integer> list, int sum){
        if(root==null) return;
        list.add(root.val);
        sum += root.val;
        if(sum==target && root.left==null && root.right==null){
//             list.add(root.val);
//             System.out.println(root.val);
            ans.add(new ArrayList<>(list));//这里一定要注意,要new一个新对象
            list.remove(list.size()-1);
            return;
//             sum -= root.val;
        }
        
        DFS(root.left, target, list, sum);
        DFS(root.right, target, list, sum);
        if(list.size()>0) list.remove(list.size()-1);
        
        return;
        
    }
    
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root==null) return ans;
        ArrayList<Integer> list = new ArrayList();
        int sum = 0;
        DFS(root, target, list, sum);
        return ans;
    }
}

JZ25 复杂链表的复制

较难  通过率:22.37%  时间限制:1秒  空间限制:64M

知识点:链表

描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。

示例:

输入:{1,2,3,4,5,3,5,#,2,#}

输出:{1,2,3,4,5,3,5,#,2,#}

解析:我们将链表分为两段,前半部分{1,2,3,4,5}为ListNode,后半部分{3,5,#,2,#}是随机指针域表示。

以上示例前半部分可以表示链表为的ListNode:1->2->3->4->5

后半部分,3,5,#,2,#分别的表示为

1的位置指向3,2的位置指向5,3的位置指向null,4的位置指向2,5的位置指向null

如下图:

示例1

输入:

{1,2,3,4,5,3,5,#,2,#}

返回值:

{1,2,3,4,5,3,5,#,2,#}

思路:两次遍历链表,第一次复制next指针,同时将<原始节点,拷贝节点>存放到hashMap中,供后续random指针的时候定位用。

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

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.*;
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead==null) return null;
        RandomListNode ans = new RandomListNode(0);
        RandomListNode cur = ans;
        HashMap<Integer, RandomListNode> map = new HashMap();
        RandomListNode tHead1 = pHead;
        RandomListNode tHead2 = pHead;
        while(tHead1!=null){
            int val = tHead1.label;
            RandomListNode temp = new RandomListNode(val);
            map.put(val, temp);
            cur.next = temp;
            cur = cur.next;
            tHead1 = tHead1.next;
        }
        cur = ans.next;
        while(tHead2!=null){
            RandomListNode temp = tHead2.random;
            if(temp==null) cur.random = null;
            else cur.random = map.get(temp.label);
            cur = cur.next;
            tHead2 = tHead2.next;
        }
        
        return ans.next;
    }
}

JZ26 二叉搜索树与双向链表

中等  通过率:29.49%  时间限制:1秒  空间限制:64M

知识点:分治

描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示

注意:

1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构

4.你不用输出或者处理,示例中输出里面的英文,比如"From left to right are:"这样的,程序会根据你的返回值自动打印输出

示例:

输入: {10,6,14,4,8,12,16}

输出:From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;

解析:

输入就是一棵二叉树,如上图,输出的时候会将这个双向链表从左到右输出,以及

从右到左输出,确保答案的正确
 

示例1

输入:

{10,6,14,4,8,12,16}

返回值:

From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;

示例2

输入:

{5,4,#,3,#,2,#,1}

返回值:

From left to right are:1,2,3,4,5;From right to left are:5,4,3,2,1;

说明:

                    5
                  /
                4
              /
            3
          /
        2
      /
    1
树的形状如上图  

 思路:中序遍历二叉树,难点是回溯过程中保存上一次遍历的结点。

1.二叉搜索树的中序遍历,得到的正好为有序的列表

可以对应双向链表。

2.怎么获得中序遍历中上层节点的引用

只能等到回溯的时候,那个递归形参的root就是中序遍历中的上层(遍历前)的节点。

之前遍历过的节点用的话,可以在遍历的时候用属性变量进行保存

二叉搜索树简单修改就可以变为双向链表是因为是按照有序的顺序遍历的,所以每个节点修改的指向是一样的

/**
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 temp = null;
//     TreeNode temp = null;//存放上一次的结点点
    public void PreTree(TreeNode pRootOfTree){
        if(pRootOfTree==null){
            return;
        }
        PreTree(pRootOfTree.left);
        //回溯了
        if(head==null){//找到头结点,最左边的第一个结点
            head = pRootOfTree;
        }
        else{//最左边的第一个结点没有前驱,从第二个结点开始
            temp.right = pRootOfTree;//上一个结点的后驱是当前结点,temp是上一个结点
            pRootOfTree.left = temp;//当前结点的前驱是上一个结点。由于左子树被遍历完了,所以指针可以修改
        }
        temp = pRootOfTree;//保存回溯过程中上一个结点
        PreTree(pRootOfTree.right);
        
    }
    
    public TreeNode Convert(TreeNode pRootOfTree) {
        PreTree(pRootOfTree);
        return head;
    }
}

JZ27 字符串的排列

较难  通过率:22.78%  时间限制:1秒  空间限制:64M

知识点:字符串,回溯

描述

输入一个字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:

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

示例1

输入:

"ab"

返回值:

["ab","ba"]

说明:

返回["ba","ab"]也是正确的   

示例2

输入:

"aab"

返回值:

["aab","aba","baa"]

复制

示例3

输入:

"abc"

返回值:

["abc","acb","bac","bca","cab","cba"]

思路:深度搜索遍历,注意访问过的结点应该被标记,防止这条路径中再次访问,回溯的时候将当前结点标记去掉,例外的路径可以访问。一个字符串中会出现相同的字符,所以得到的排列使用Set去重。

一个关于String的小知识:两个String连接的时候回产生新对象在常量池

 

import java.util.*;
public class Solution {
    
    public HashSet<String> set = new LinkedHashSet();
    
    public void DFS(String str, String temp, int[] mark){
        if(temp.length()==str.length()){
            set.add(temp);//每个temp都是新的对象,在常量池中,不同的地址
//             set.add(new String(temp));
            return;
        }
        for(int i=0; i<str.length(); i++){
            if(mark[i]==1) continue;
            mark[i]=1;
            DFS(str, temp+str.charAt(i), mark);//String + String 会产生新对象,所以回溯后不需要remove最后一个结点
            mark[i]=0;
//             System.out.println(temp.length());
//             temp = temp.substring(0,temp.length()-1);
        }
        return;
    }
    
    public ArrayList<String> Permutation(String str) {
        int[] mark = new int[str.length()];
        DFS(str, "",mark);
        ArrayList<String> ans = new ArrayList();
        for(String s : set){
            ans.add(s);
        }
        return ans;
    }
}

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

简单  通过率:31.09%  时间限制:1秒  空间限制:64M

知识点:哈希数组

描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。你可以假设数组是非空的,并且给定的数组总是存在多数元素。1<=数组长度<=50000,0<=数组元素<=10000

示例1

输入:

[1,2,3,2,2,2,5,4,2]

返回值:

2

示例2

输入:

[3,3,3,3,2,2,2]

返回值:

3

示例3

输入:

[1]

返回值:

1

思路:利用hash表,统计每个数字在数组中出现的次数,判断出现最多次的是否大于数组长度的一般。

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        Map<Integer, Integer> hashMap = new HashMap<>();
        int max = 0;
        int ans = 0;
        for(int i=0; i<array.length; i++){
            if(hashMap.get(array[i])==null){
                hashMap.put(array[i],1);
                if(1>max){
                    ans = array[i];
                    max = 1;
                }
            }
            else{
                int value = hashMap.get(array[i])+1;
                hashMap.put(array[i],value);
                if(value>max){
                    ans = array[i];
                    max = value;
                }
            }
        }
        return max>array.length/2? ans:0;
    }
}

JZ29 最小的K个数

中等  通过率:25.59%  时间限制:1秒  空间限制:64M

知识点:排序分治

描述

给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

  • 0 <= k <= input.length <= 10000
  • 0 <= input[i] <= 10000

示例1

输入:

[4,5,1,6,2,7,3,8],4 

返回值:

[1,2,3,4]

说明:

返回最小的4个数即可,返回[1,3,2,4]也可以    

示例2

输入:

[1],0

返回值:

[]

示例3

输入:

[0,1,2,1,2],3

返回值:

[0,1,1]

思路:堆排序,可以自己写一个堆排序,也可以使用优先队列。

import java.util.ArrayList;

public class Solution {
    public void shif(int [] input, int root, int last){ //筛选
        /**
        last 是最后一个元素将移到根的位置,和根元素的孩子作比较
        **/
        boolean flag = false;
        int root_value = input[root];//先把根存起来
        int lchild = root*2+1;//从0开始(2*root+1),从1开始root*2;
        while(lchild<last && !flag){//判断是否需要调整堆
            int min = 0;//记录左右孩子中较小的那一个
            if(input[lchild]<input[last]&&input[lchild]<input[lchild+1]){
                min = lchild;//左孩子调到根上
            }
            else{
                min = lchild+1;//有孩子调到根上
            }
            
            if(input[min]<root_value){
                input[root] = input[min];//把较小的元素调整到根的位置
                root = min;//沿着这个分支往下走
                lchild = root*2+1;//
            }
            else{
                flag = true;//根元素比左右孩子小,就不用调整了
            }
            input[root] = root_value;
//             root_value = input[root];//再次记录根元素,即,分支的根
        }
    }
    
    public void BuildHeap(int [] input){
        //input是无序的,本函数将它建成一个堆
        //对于有n个结点的完全二叉树,它的第一个非叶子结点在n/2处(数组从1开始)。如果从0开始(n-1)/2
        int n = input.length;
        for(int i=(n-1)/2; i>=0; i--){
            shif(input,i,n-1);//i是待建堆的根(子分支),input[i+1,...]已经是堆
        }
    }
    
    public ArrayList<Integer> HeapWrite(int [] input, int k){
        //input[0,..]已经是一个堆,r[0]是它的根位置,r[length-1]是最后一个元素
        ArrayList<Integer> ans = new ArrayList<Integer>();
        int n = input.length;
        for(int i=0; i<k; i++){//保存前k个最小的
//             ans.add(input[i]);
            ans.add(input[0]);//输出堆顶元素,也可以把它保存到另一个数组
            input[0] = input[n-i-1];// 把堆中最后一个元素放到根位置
            shif(input,0,n-i-2);//调整剩余元素,使它们仍然为堆
        }
        return ans;
    }
    
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> ans = new ArrayList<Integer>();
        if(k<=input.length&&k>0){
            BuildHeap(input);
            ans = HeapWrite(input,k);
        }
        return ans;
    }
}

JZ30 连续子数组的最大和

简单  通过率:38.27%  时间限制:1秒  空间限制:64M

知识点:分治动态规划

描述

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).

示例1

输入:

[1,-2,3,10,-4,7,2,-5]

返回值:

18

说明:

输入的数组为{1,-2,3,10,—4,7,2,一5},和最大的子数组为{3,10,一4,7,2},因此输出为该子数组的和 18。 

思路:

状态定义:dp[i]表示以i结尾的连续子数组的最大和。所以最终要求dp[n-1]
状态转移方程:dp[i] = max(array[i], dp[i-1]+array[i])
解释:如果当前元素为整数,并且dp[i-1]为负数,那么当然结果就是只选当前元素

最大的dp[i]就是结果。

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int[] dp = new int[array.length+1];
        dp[0] = array[0];
        int maxsum = Integer.MIN_VALUE;
        for(int i=1; i<array.length; i++){
            dp[i] = Math.max(dp[i-1]+array[i], array[i]);
            maxsum = Math.max(maxsum, dp[i]);
        }
        return maxsum;
    }
}

主页有后续更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值