剑指offer题解61-67(Java)

文章目录

    • 61.序列化二叉树*
      • 思路
    • 62.二叉搜索树的第k个节点
    • 63.数据流中的中位数
    • 64.滑动窗口的最大值
    • 65.矩阵路径
    • 66.机器人的运动范围
    • 67.剪绳子*

61.序列化二叉树*

请实现两个函数,分别用来序列化和反序列化二叉树

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

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

思路

理解不了题意

但是回过头来一看还是挺简单的 但是他“以 ! 表示一个结点值的结束(value!)” 体现在哪?

链接:https://www.nowcoder.com/questionTerminal/cf7e25aa97c04cc1a68c8f040e71fb84?f=discussion
来源:牛客网

/*
    算法思想:根据前序遍历规则完成序列化与反序列化。所谓序列化指的是遍历二叉树为字符串;所谓反序列化指的是依据字符串重新构造成二叉树。
    依据前序遍历序列来序列化二叉树,因为前序遍历序列是从根结点开始的。当在遍历二叉树时碰到Null指针时,这些Null指针被序列化为一个特殊的字符“#”。
    另外,结点之间的数值用逗号隔开。
*/
public class Solution {
    int index = -1;   //计数变量
  
    String Serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        if(root == null){
            sb.append("#,");
            return sb.toString();
        }
        //先序遍历
        sb.append(root.val + ",");
        sb.append(Serialize(root.left));
        sb.append(Serialize(root.right));
        return sb.toString();
  }
    TreeNode Deserialize(String str) {
        index++;
        //int len = str.length();
        //if(index >= len){
        //    return null;
       // }
        String[] strr = str.split(",");
        TreeNode node = null;
        if(!strr[index].equals("#")){
            node = new TreeNode(Integer.valueOf(strr[index]));
            node.left = Deserialize(str);
            node.right = Deserialize(str);
        }
        return node;
  }
}

62.二叉搜索树的第k个节点

中序遍历,到第k个就结束遍历,k为0或k大于节点数就返回null

import java.util.*;

public class Solution {
	static ArrayList<TreeNode> nodes;
	static int count;
	static int k;

	public void midOrder(TreeNode root) {
		if (count == k) return;
		if (root == null) return;
		if (root.left != null) {
			midOrder(root.left);

		}
		nodes.add(root);
		count++;
		if (root.right != null) {
			midOrder(root.right);

		}
		return;
	}

	TreeNode KthNode(TreeNode pRoot, int k) {
		count = 0;
		nodes = new ArrayList<TreeNode>();
		if (k == 0) return null;
		this.k = k;
		midOrder(pRoot);
		if (k > count) return null;
		return nodes.get(k - 1);

	}
}

63.数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数

我的暴力写法

import java.util.*;
public class Solution {
   static ArrayList<Integer> nums=new ArrayList<Integer>();
    public void Insert(Integer num) {
        nums.add(num);
    }

    public Double GetMedian() {
        int len= nums.size();
      //每一次查询中位数,都需要先排序,根据这一特点其实可以考虚优先队列
        Collections.sort(nums);
        if(len%2==0){
            return (Double.valueOf(nums.get(len/2))+Double.valueOf(nums.get(len/2-1)))/2;
        }
        else{
            return Double.valueOf(nums.get(len/2));
        }
        
    }

}

参考

private int cnt = 0;
private PriorityQueue<Integer> low = new PriorityQueue<>();
// 默认维护小顶堆
private PriorityQueue<Integer> high = new PriorityQueue<>(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
});
 
public void Insert(Integer num) {
    // 数量++
    cnt++;
    // 如果为奇数的话
    if ((cnt & 1) == 1) {
        // 由于奇数,需要存放在大顶堆上
        // 但是呢,现在你不知道num与小顶堆的情况
        // 小顶堆存放的是后半段大的数
        // 如果当前值比小顶堆上的那个数更大
        if (!low.isEmpty() && num > low.peek()) {
            // 存进去
            low.offer(num);
            // 然后在将那个最小的吐出来
            num = low.poll();
        } // 最小的就放到大顶堆,因为它存放前半段
        high.offer(num);
    } else {
        // 偶数的话,此时需要存放的是小的数
        // 注意无论是大顶堆还是小顶堆,吐出数的前提是得有数
        if (!high.isEmpty() && num < high.peek()) {
            high.offer(num);
            num = high.poll();
        } // 大数被吐出,小顶堆插入
        low.offer(num);
    }
 
}
 
public Double GetMedian() {// 表明是偶数
    double res = 0;
    // 奇数
    if ((cnt & 1) == 1) {
        res = high.peek();
    } else {
        res = (high.peek() + low.peek()) / 2.0;
    }
    return res;
}

64.滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

我的代码,依旧暴力

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {  ArrayList<Integer> res=new ArrayList<Integer>();
        if(size<=0||size>num.length){
        return res;
    }
        int len = num.length;
      int start=0;
     int end=size-1;
     int flag=0;
    
     while(end<len){
              int max=num[start];
         for(int i=start;i<=end;i++){
             if(num[i]>max){
                 max=num[i];
             }
         }
             res.add(max);
         start++;
         end++;   
     }
     return res;
        
    }
}
import java.util.*;
//思路:用一个大顶堆,保存当前滑动窗口中的数据。滑动窗口每次移动一格,就将前面一个数出堆,后面一个数入堆。
public class Solution {
    public PriorityQueue<Integer> maxQueue = new PriorityQueue<Integer>((o1,o2)->o2-o1);//大顶堆
    public ArrayList<Integer> result = new ArrayList<Integer>();//保存结果
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        if(num==null || num.length<=0 || size<=0 || size>num.length){
            return result;
        }
        int count=0;
        for(;count<size;count++){//初始化滑动窗口
            maxQueue.offer(num[count]);
        }
        while(count<num.length){//对每次操作,找到最大值(用优先队列的大顶堆),然后向后滑动(出堆一个,入堆一个)
            result.add(maxQueue.peek());
            maxQueue.remove(num[count-size]);
            maxQueue.add(num[count]);
            count++;
        }
        result.add(maxQueue.peek());//最后一次入堆后没保存结果,这里额外做一次即可
 
        return result;
    }
}

65.矩阵路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

我的代码,只过了30%,其实跟参考的也是类似的,但就是不知道哪里不对


public class Solution {
    static  int flag;
    static int row;
    static  int col;
    static  boolean[][] vis;
    public  static void dfs(char[] matrix,int x,int y ,char[] str, int cur){
        int len=str.length;
        if(cur==len){
            flag=1;
            return;
        }
        if(flag==1){
            return;
            }
        if((x>=0&&x<row)&&(y<col&&y>=0)){
            if(!vis[x][y]&&matrix[x*col+y]==str[cur]){
                vis[x][y]=true;
                int[] x_index={0,0,-1,1};
                int[] y_index={1,-1,0,0};
                for(int i=0;i<4;i++){
                    dfs(matrix,x+x_index[i],y+y_index[i],str,cur+1);
                }
            }
        }
        return;
    }
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        flag=0;
        row=rows;
        col=cols;
        vis=new boolean[row][col];
        for (int i = 0; i <rows ; i++) {
            for (int j = 0; j <cols ; j++) {
                if(flag==1){
                    return  true;
                }
                if(matrix[i*cols+j]==str[0]){
                    dfs(matrix,i,j,str,0);
                }

            }
        }
        return  false;
    }
}

参考


public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        //标志位,初始化为false
        boolean[] flag = new boolean[matrix.length];
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                 //循环遍历二维数组,找到起点等于str第一个元素的值,再递归判断四周是否有符合条件的----回溯法
                 if(judge(matrix,i,j,rows,cols,flag,str,0)){
                     return true;
                 }
            }
        }
        return false;
    }
     
    //judge(初始矩阵,索引行坐标i,索引纵坐标j,矩阵行数,矩阵列数,待判断的字符串,字符串索引初始为0即先判断字符串的第一位)
    private boolean judge(char[] matrix,int i,int j,int rows,int cols,boolean[] flag,char[] str,int k){
        //先根据i和j计算匹配的第一个元素转为一维数组的位置
        int index = i*cols+j;
        //递归终止条件
        if(i<0 || j<0 || i>=rows || j>=cols || matrix[index] != str[k] || flag[index] == true)
            return false;
        //若k已经到达str末尾了,说明之前的都已经匹配成功了,直接返回true即可
        if(k == str.length-1)
            return true;
        //要走的第一个位置置为true,表示已经走过了
        flag[index] = true;
         
        //回溯,递归寻找,每次找到了就给k加一,找不到,还原
        if(judge(matrix,i-1,j,rows,cols,flag,str,k+1) ||
           judge(matrix,i+1,j,rows,cols,flag,str,k+1) ||
           judge(matrix,i,j-1,rows,cols,flag,str,k+1) ||
           judge(matrix,i,j+1,rows,cols,flag,str,k+1)  )
        {
            return true;
        }
        //走到这,说明这一条路不通,还原,再试其他的路径
        flag[index] = false;
        return false;
        }}

66.机器人的运动范围

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

//经典的dfs,反而在细节上踩了不少坑,醉了
public class Solution {
    static int count;
    static boolean[][] vis;
    static int row;
    static int col;

    public boolean judge(int x, int y, int threshold) {
        String s1 = String.valueOf(x);
        String s2 = String.valueOf(y);
        char[] c = (s1 + s2).toCharArray();
        int sum = 0;
        for (int i = 0; i < c.length; i++) {
            sum += Integer.parseInt(String.valueOf(c[i]));
        }

        if (sum > threshold) {
            return false;
        } else {
            return true;
        }
    }

    public void dfs(int x, int y, int threshold) {
        int[] x_index = {0, 0, -1, 1};
        int[] y_index = {-1, 1, 0, 0};
        if ((x >= 0 && x < row) && (y >= 0 && y < col)) {
            if (judge(x, y, threshold) && !vis[x][y]) {
                vis[x][y] = true;
                count++;
                for (int i = 0; i <4 ; i++) {
                    dfs(x+x_index[i],y+y_index[i],threshold);
                }
            }
        }
        return;
    }
    public int movingCount(int threshold, int rows, int cols) {
        row = rows;
        count=0;
        col = cols;
        vis = new boolean[rows][cols];
        dfs(0, 0, threshold);
        return count;
    }}

67.剪绳子*

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

public class Solution {
    public int cutRope(int n) {
       // n<=3的情况,m>1必须要分段,例如:3必须分成1、2;1、1、1 ,n=3最大分段乘积是2,
        if(n==2)
            return 1;
        if(n==3)
            return 2;
        int[] dp = new int[n+1];
        /*
        下面3行是n>=4的情况,跟n<=3不同,4可以分很多段,比如分成1、3,
        这里的3可以不需要再分了,因为3分段最大才2,不分就是3。记录最大的。
         */
        dp[1]=1;
        dp[2]=2;
        dp[3]=3;
        int res=0;//记录最大的
        for (int i = 4; i <= n; i++) {
            for (int j = 1; j <=i/2 ; j++) {
                res=Math.max(res,dp[j]*dp[i-j]);
            }
            dp[i]=res;
        }
        return dp[n];
    }
}

你可能感兴趣的:(剑指offer,二叉树,字符串,数据结构)