算法练习day20——190411(重建二叉树、斐波那契数列、跳台阶、矩形覆盖、变态跳台阶、旋转数组的最小数字、矩阵中的路径)

1、重建二叉树

根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

preorder = [3,9,20,15,7]、inorder = [9,3,15,20,7]

1.1 分析

前序遍历的第一个值为根节点的值使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。

1.2 代码实现

static class TreeNode{
	int value;
	TreeNode left;
	TreeNode right;
	
	public TreeNode(int value) {
		this.value=value;
	}
}
private Map<Integer, Integer> indexForInOrders = new HashMap<>(); 
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
	for (int i = 0; i < in.length; i++)
		indexForInOrders.put(in[i], i);//将前缀序列放入Map中,便于后面依据值得到它的index
	return reConstructBinaryTree(pre, 0, pre.length - 1, 0);
} 
private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
	if (preL > preR)
		return null;
	TreeNode root = new TreeNode(pre[preL]);
	int inIndex = indexForInOrders.get(root.value);
	int leftTreeSize = inIndex - inL;
	root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL);
	root.right = reConstructBinaryTree(pre, preL + leftTreeSize+ 1, preR, inL + leftTreeSize + 1);
	return root;
}

2.斐波那契数列

求斐波那契数列的第 n 项,n <= 39。

2.1 分析

如果使用递归求解,会重复计算一些子问题。例如,计算 f(10) 需要计算 f(9) 和f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了。

2.2 代码实现

2.2.1 递归

递归是将一个问题划分成多个子问题求解

public static int process(int i) {
	if(i==0||i==1)
		return i;
	return process(i-1)+process(i-2);
}

2.2.2 动态规划

动态规划也是将一个问题划分成多个子问题求解,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。

public static int process1(int n) {
	if (n <= 1)
		return n;
	int[] fib = new int[n + 1];//0~n,第n+1个即fib[n]存的是最终的结果
	fib[1] = 1;
	for (int i = 2; i <= n; i++)
		fib[i] = fib[i - 1] + fib[i - 2];
	return fib[n];		
}

2.2.3 改进1

考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i项,从而将空间复杂度由 O(N) 降低为 O(1)

public int process2(int n) {
	if (n <= 1)
		return n;
	int pre2 = 0, pre1 = 1;
	int fib = 0;
	for (int i = 2; i <= n; i++) {
		fib = pre2 + pre1;
		pre2 = pre1;
		pre1 = fib;
	} 
	return fib;
}

2.2.4 改进2

由于待求解的 n 小于 40,因此可以将前 40 项的结果先进行计算,之后就能以O(1) 时间复杂度得到第 n 项的值了。

public class Solution {
    private int[] fib = new int[40];
    public Solution() {
        fib[1] = 1;
        fib[2] = 2;
        for (int i = 2; i < fib.length; i++)
            fib[i] = fib[i - 1] + fib[i - 2];
    } 
    public int Fibonacci(int n) {
        return fib[n];
    }
}

3.跳台阶

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

3.1 分析

总共有n级台阶,f(n)中跳法。如果第一次跳1级,那么f(n)=f(n-1);若跳2级,则f(n)=f(n-2)。总:f(n)=f(n-1)+f(n-2)——斐波那契数列

3.2 代码实现

public int JumpFloor(int n) {
    if (n <= 2)
        return n;
    int pre2 = 1, pre1 = 2;
    int result = 1;
    for (int i = 2; i < n; i++) {
        result = pre2 + pre1;
        pre2 = pre1;
        pre1 = result;
    } 
    return result;
}

4.矩形覆盖

我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。

请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,总共有多少种方法?

4.1 分析

将2*n这个大矩形,看成一个n个矩形线性排列,然后一次可以用一个小1*1的矩形覆盖(即:2*1的矩形竖着),也可以用2*1的矩形覆盖(2*1的矩形横着)。这就和跳台阶一样。如图所示:

4.2 代码实现

public int RectCover(int n) {
    if (n <= 2)
        return n;
    int pre2 = 1, pre1 = 2;
    int result = 0;
    for (int i = 3; i <= n; i++) {
        result = pre2 + pre1;
        pre2 = pre1;
        pre1 = result;
    } 
    return result;
}

5.变态跳台阶

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

5.1 分析(原博

用f(n)表示青蛙跳上n阶台阶的跳法数,设定f(0) = 1;

当n = 1 时,只有一种跳的方式,一阶跳,f(1) = f(0) =1;

当n = 2 时,有两种跳的方式,一阶跳和两阶跳,f(2) = f(1) + f(0) = 2;

当n = 3 时,有三种跳的方式,第一次跳出一阶后,后面还有f(3-1)中跳法; 第一次跳出二阶后,后面还有f(3-2)中跳法;第一次跳出三阶后,后面还有f(3-3)中跳法,f(3) = f(2) + f(1) + f(0) = 4;

当n = n 时,第一次跳出一阶后,后面还有f(n-1)中跳法; 第一次跳出二阶后,后面还有f(n-2)中跳法......第一次跳出n阶后,后面还有 f(n-n)中跳法,即:

f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-n) = f(0) + f(1) + f(2) + ... + f(n-1)

又因为 f(n-1) = f(0) + f(2) + f(3) + ... + f(n-2)

两式相减得:f(n) = 2 * f(n-1)    ( n >= 2)

                 |  0,n = 0

f(n)   =       |  1, n = 1

                 |  2 * f(n-1) , n >= 2

5.2 代码实现

public static int JumpFloor1(int target) 
    if(target<=1)
	return target;
    int result=1;

    for (int i = 2; i <= target; i++)
    	result+=result;
    return result;
}

6.旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转

输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。

例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。

6.1 分析

在一个有序数组中查找一个元素可以用二分查找,二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度都为 O(logN)

本题可以修改二分查找算法进行求解:

  • 当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m;
  • 否则解在 [m + 1, h] 之间,令 l = m + 1。

6.2 代码实现

public class minNumber {
	public static void main(String[] args) {
		int [] arr= {3,4,5,1,2};
		
		System.out.print(findMinNumber(arr));
	}
	
	public static int findMinNumber(int[] arr) {
		if(arr.length==0||arr==null)
			return -1;
		
		int l=0;
		int h=arr.length-1;
		
		while(l<h) {
			int m=(l+h)/2;
			if(arr[m]<=arr[h])
				h=m;
			else
				l=m+1;
		}
		
		return arr[l];
	}
}

6.3 允许重复元素

如果数组元素允许重复的话,那么就会出现一个特殊的情况:nums[l] == nums[m]== nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。例如对于数组 {1,1,1,0,1},l、m 和 h 指向的数都为 1,此时无法知道最小数字 0 在哪个区间。

public static int findMinNumber(int[] arr) {
	if(arr.length==0||arr==null)
		return -1;
	
	int l=0;
	int h=arr.length-1;
	
	while(l<h) {
		int m=(l+h)/2;
		if (arr[l] == arr[m] && arr[m] == arr[h])
			return process(arr, l, h);
		else if(arr[m]<=arr[h])
			h=m;
		else
			l=m+1;
	}		
	return arr[l];
}

public static int process(int[] nums,int l,int h) {
    for (int i = l; i < h; i++)
        if (nums[i] > nums[i + 1])//出现前大于后的情况,后者肯定为最小
            return nums[i + 1];
    return nums[l]
}

7.矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径

路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

例如下面的矩阵包含了一条 bfce 路径:

7.1 分析(原博

这是一个可以用回溯法解决的典型问题。

首先,遍历这个矩阵matrix,我们很容易就能找到与字符串str中第一个字符相同的矩阵元素ch。

然后遍历ch的上下左右四个字符,如果有和字符串str中下一个字符相同的,就把那个字符当作下一个字符(下一次遍历的起点),如果没有,就需要回退到上一个字符,然后重新遍历

为了避免路径重叠,需要一个辅助矩阵来记录路径情况。

当矩阵坐标为(row,col)的格子和路径字符串中下标为pathLength的字符一样时,从4个相邻的格子(row,col-1)、(row-1,col)、(row,col+1)以及(row+1,col)中去定位路径字符串中下标为pathLength+1的字符。

如果4个相邻的格子都没有匹配字符串中下标为pathLength+1的字符,表明当前路径字符串中下标为pathLength的字符在矩阵中的定位不正确,我们需要回到前一个字符串(pathLength-1),然后重新定位

一直重复这个过程,直到路径字符串上所有字符都在矩阵中找到格式的位置(此时str[pathLength] == '\0')。

7.2 代码实现(原博

public class StringPathInMatrix {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        if (matrix == null || rows < 1 || cols < 1 || str == null) {
            return false;
        }
        boolean[] isVisited = new boolean[rows * cols];
        for (boolean v : isVisited) {
            v = false;
        }
        int pathLength = 0;
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                if (hasPathCore(matrix, rows, cols, row, col, str, pathLength, isVisited))
                    return true;
            }
        }
        return false;
    }
 
    private boolean hasPathCore(char[] matrix, int rows, int cols, int row, int col, char[] str, int pathLength,
            boolean[] isVisited) {
        if (row < 0 || col < 0 || row >= rows || col >= cols || isVisited[row * cols + col] == true
                || str[pathLength] != matrix[row * cols + col])
            return false;
        if (pathLength == str.length - 1)
            return true;
        boolean hasPath = false;
        isVisited[row * cols + col] = true;
        hasPath = hasPathCore(matrix, rows, cols, row - 1, col, str, pathLength + 1, isVisited)
                || hasPathCore(matrix, rows, cols, row + 1, col, str, pathLength + 1, isVisited)
                || hasPathCore(matrix, rows, cols, row, col - 1, str, pathLength + 1, isVisited)
                || hasPathCore(matrix, rows, cols, row, col + 1, str, pathLength + 1, isVisited);
 
        if (!hasPath) {
            isVisited[row * cols + col] = false; 
        }
        return hasPath;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值