文章目录
- 一.剑指 Offer 10- I. 斐波那契数列----2021/1/8
- 二.938. 二叉搜索树的范围和----2021/1/9
- 三.剑指 Offer 10- II. 青蛙跳台阶问题----2021/1/9
- 四.70. 爬楼梯----2021/1/9
- 五.563. 二叉树的坡度----2021/1/9
- 六.1137. 第 N 个泰波那契数----2021/1/10
- 七.面试题 16.11. 跳水板----2021/1/10
- 八.110. 平衡二叉树----2021/1/10
- 九.104. 二叉树的最大深度---2021/1/10
- 十.面试题 17.12. BiNode--2021/1/10
- 十一.783. 二叉搜索树节点最小距离----2021/1/10
- 十二.21. 合并两个有序链表----2021/1/10
- 十三.897. 递增顺序查找树--2021/1/10
- 十四.面试题 08.06. 汉诺塔问题--2021/1/11
一.剑指 Offer 10- I. 斐波那契数列----2021/1/8
解法1-----递归
class Solution {
int constant = 1000000007;
public int fib(int n) {
if(n==0){
return 0;
}
if(n==1){
return 1;
}
return (fib(n-1)+fib(n-2))%constant ;
}
}
或者
class Solution {
int constant = 1000000007;
public int fib(int n) {
if(n==0){
return 0;
}
if(n==1){
return 1;
}
return (fib(n-1)%constant+fib(n-2)%constant)%constant ;
}
}
证明: 要证(a+b)%c = (a%c+b%c)%c, 即证a+b与a%c+b%c对c同余,则有c能整除(a+b-a%c-b%c) 设a=mc+p,b=nc+q, 则(a+b-a%c-b%c)=(m+n)c+p+q-p-q=(m+n)c ,则证a+b与a%c+b%c对c同余,证毕。
注: 这道题,直接用递归的话,会很大可能超时,所以推荐下面的动态规划。
解法2----动态规划
class Solution {
int constant = 1000000007;
public int fib(int n) {
if(n<=1){
return n;
}
int[]arr = new int[n+1];
arr[0] = 0;
arr[1] = 1;
for(int i = 2 ;i<=n;i++){
arr[i] = arr[i-1] + arr[i-2];
arr[i] = arr[i] % constant;
}
return arr[n];
}
}
注: 这里需要的是每次得到一个值,要进行%取模操作,因为,如果最后取模,中间可能会溢出,而为什么中间不断取模不会影响最后取模的值,需要证明(a+b)%c = (a%c+b%c)%c,,证明见上面!
另外,java中科学计数法1e9+7默认是double,如果使用的话要注意强转类型。
二.938. 二叉搜索树的范围和----2021/1/9
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int sum = 0;
public int rangeSumBST(TreeNode root, int low, int high) {
if(root == null){
return 0;
}
rangeSumBST(root.left,low,high);
rangeSumBST(root.right,low,high);
if(root.val>=low && root.val<=high){
sum += root.val;
}
return sum;
}
}
思路: 递归遍历–这里选取的是后序遍历,其实前序遍历、中序遍历等都可以,因为这里目标就是把所有的节点都要过一遍,然后比较是否符合条件,如果符合条件则进行累加即可,可以看到这里的返回sum,其实在递归过程的返回sum是没有意义的,但是最后一步的返回sum则是最终的值。
待: 可以看到这棵树是二叉搜索树,其每个节点的左子树必定小于该节点,而右子树必定大于根节点,所以应该会有简化的操作,这个之后如果有能力在补充。
三.剑指 Offer 10- II. 青蛙跳台阶问题----2021/1/9
解法1-----递归
class Solution {
public final int num = 1000000007;
public int numWays(int n) {
if(n<=1)
return 1;
if(n==2){
return 2;
}
return (numWays(n-1)+numWays(n-2))%num;
}
}
解法2-----动态规划
class Solution {
public final int num = 1000000007;
public int numWays(int n) {
int []dp = new int[n+1];
if(n<=1)
return 1;
if(n==2){
return 2;
}
dp[0] = dp[1] = 1;
dp[2] = 2;
for(int i = 3;i<=n ; i++){
dp[i] = dp[i-1] + dp[i-2];
dp[i] = dp[i] % num;
}
return dp[n];
}
}
注: 这里递归和动态规划是和第一题一样的,就不多赘述,需要注意的是递归还是超时!!!
四.70. 爬楼梯----2021/1/9
注: 这道题和第三题一样,只是少了条件0,就不再多加赘述了。
五.563. 二叉树的坡度----2021/1/9
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int sum = 0;
int calcTilt(TreeNode root){
if(root == null){
return 0;
}
int left = calcTilt(root.left);
int right =calcTilt(root.right);
int dif = Math.abs(left-right);
sum += dif;
return root.val + left + right;
}
public int findTilt(TreeNode root) {
calcTilt(root);
return sum;
}
}
思路: 这道题可以借鉴一下上面的第二题,可以看出,对于每个节点要计算其左子树的和以及其右子树的和,所以这里也采用后序遍历,但是不同的是这里多出了一个变量就是要累计所有的差的绝对值作为最后的结果,所以这里需要引入一个函数,在这个函数中进行递归,其中分别返回左子树的和、右子树的和,然后加上本节点的和作为返回值,而针对每个左子树以及右子树,分别计算差值累加存储到全局变量sum中即可。
六.1137. 第 N 个泰波那契数----2021/1/10
class Solution {
public int tribonacci(int n) {
if(n == 0){
return 0;
}
if(n == 1){
return 1;
}
if(n == 2){
return 1;
}
int[] dp= new int[n+1];
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
for(int i=3;i<=n;i++){
dp[i] = dp[i-1] + dp[i-2] + dp[i-3];
}
return dp[n];
}
}
注: 这里递归和动态规划是和第一题一样的,就不多赘述,只写了动态规划!
七.面试题 16.11. 跳水板----2021/1/10
class Solution{
public int index= 0;
public int[] divingBoard(int shorter, int longer, int k) {
if(k==0){
return new int[0];
}
int[] a = new int[k+1];
for(int i=0;i<=k;i++){
if(index == 0){
a[index++] = longer*i + shorter*(k-i);
}else{
int next = longer*i + shorter*(k-i);
if(next!=a[index-1]){
a[index++] = next;
}
}
}
int[] b= new int[index];
for(int i =0;i<index;i++){
b[i] = a[i];
}
return b;
}
}
注: 这道题竟然在递归里,上面必须动态规划的我忍了,这道题明显找规律,循环一遍就可以了!!!!
思路: 因为从小到大排序,所以循环的时候索引以最长的为主,即longer从0-k个,对应的shorter从k-0个,但是需要注意的是就是k=0的时候;以及不要有重复,这里的重复主要是因为shorter和longer相等时导致的,所以有一个优化方案,就是单独处理一下longer和shorter相等的时候,这样可以避免我这个大难的多个数组!
八.110. 平衡二叉树----2021/1/10
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int max = 0;
public boolean pl = true;
public int difDepth(TreeNode root){
if(pl ==false){
return 0;
}
if(root == null){
return 0;
}
int leftDepth = difDepth(root.left) + 1;
int rightDepth = difDepth(root.right) + 1;
max = Math.abs(leftDepth-rightDepth);
if(max>1)
pl = false;
return Math.max(leftDepth,rightDepth);
}
public boolean isBalanced(TreeNode root) {
if(root == null){
return true;
}
difDepth(root);
return pl;
}
}
思路: 其实本质是求每个节点的左子树和右子树的高度,这里还是从递归出发,利用后序遍历,比较左子树和右子树的高度差,如果大于1,就直接返回一个全局变量false,然后在每次递归的时候判断这个变量,如果为false,什么都不用干,直接返回;否则,就返回左子树和右子树的最大值,作为该节点的高度/深度。 可以对比着第5题看!
九.104. 二叉树的最大深度—2021/1/10
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxresult = 0;
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
int left = maxDepth(root.left) + 1;
int right = maxDepth(root.right) + 1;
return left>right?left:right;
}
}
思路: 如果理解了第八题,这道题就信手拈来了,只是单纯的求最大深度,其实第八题,已经求了,并且更复杂的加了一个判断平衡二叉树的条件。
十.面试题 17.12. BiNode–2021/1/10
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
TreeNode f;
public void dfs(TreeNode root){
if(root==null){
return;
}
dfs(root.left);
f.right = root;
root.left = null;
f = root;
dfs(root.right);
}
public TreeNode convertBiNode(TreeNode root) {
f = new TreeNode(Integer.MIN_VALUE);
//保持最初的节点
TreeNode pre = f;
dfs(root);
return pre.right;
}
}
思路: 这道题首先需要明白题意,看了大佬的讲解发现意思是:将树转化成链表; 具体为左指针指向空; 右指针指向下一个节点;然后链表顺序就是从小到大;因为是二叉搜索树 所以有几条特性!
所以这次利用的是中序遍历,保证了其从小到大排序,并且需要在原地址基础上修改,就要修改每个节点的left以及right,而val保持原来的即可,left默认修改为null,而right从根据中序遍历将所有的节点链接起来,其中利用一个节点f不断的往后链接,但是需要保存这个节点的开始位置,这里用的是pre!
十一.783. 二叉搜索树节点最小距离----2021/1/10
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int minValue = Integer.MAX_VALUE;
public int last = 0;
public void dfs(TreeNode root){
if(root==null){
return;
}
dfs(root.left);
if(last!=0)
minValue = Math.min(minValue,root.val-last);
last = root.val;
dfs(root.right);
}
public int minDiffInBST(TreeNode root) {
dfs(root);
return minValue;
}
};
思路: 刚开始以为是相邻节点,最后发现是任意节点,然后借鉴了题解中思路,因为是二叉搜索树,所以利用中序遍历+递归,因为中序遍历后是从小到大排序的,所以只需要比较每个节点和之前的节点的差值即可,因为这样是相邻的。
十二.21. 合并两个有序链表----2021/1/10
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null)
return l2;
if(l2 == null)
return l1;
if(l1.val<=l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
思路: 其实这道题本身来说,是比较容易理解的,但是通过代码来写的话感觉就费事了,这是参考大佬写的,就是每次选出最小的,其他的继续递归,这里需要注意的是返回值,不断递归,直到最后一步返回最后的节点,然后不断的往回走,直到最初的节点,中间利用next链接其了所有节点。
十三.897. 递增顺序查找树–2021/1/10
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode f;
public void dfs(TreeNode root){
if(root==null){
return;
}
dfs(root.left);
f.right = root;
root.left = null;
f = root;
dfs(root.right);
}
public TreeNode increasingBST(TreeNode root) {
TreeNode pre = new TreeNode(0);
f = new TreeNode(0);
pre = f;
dfs(root);
return pre.right;
}
}
注: 和第十题一样,就不加赘述了!
十四.面试题 08.06. 汉诺塔问题–2021/1/11
注: 在讲这道题之前,先说一下汉诺塔问题;(设三个柱子分别是A、B、C)
要把最左面(A)全部移动到最右面©,分为三步骤:
1)先把上面两个从A移到B
2)把第三个从A移到C
3)把B的两个从B移到C
而步骤1)和步骤3)就是递归,又可以分为三个步骤,这里以步骤1)–把上面两个从A移到B为例:
1)先把上面的一个从A移到C
2)把第二个(也就是最后一个)从A移到B
3)把C的那一个移到B
步骤3)和其类似,从上面可以看出移动三个木块,其实需要两次移动两个木块来完成,当然中途也需要不是递归的一步,所以推导公式:step(n) = 2*step(n-1) + 1,其中step(n)表示移动n个木块的步骤;接下来就是找最终的终止条件:就是只有一个木块的时候,这个明显的就是直接移动即可!所以代码可以如下,声明一个变量step,统计所有的步数。
补充: 其实上面在移动的时候,如A–C需要借助B,这时候从代码中就是变量参数的顺序,如(String a,String b,String c)就是从A借住B移动到C;然后还有的是为什么递归中移动两个不会影响移动三个木块,因为在移动两个的时候,第三个木块是最大的,放在下面,所以就等于没有一样,不起作用。
代码:
package com.leetcode;
public class hnooi {
private static int step = 0;
private static void hnooi(Integer n,String a,String b, String c){
if(n==1) {
System.out.println(a + "移动到" + c);
step++;
}
else {
hnooi(n - 1, a, c, b);
System.out.println(a + "移动到" + c);
step++;
hnooi(n - 1, b, a, c);
}
}
public static void main(String[] args) {
hnooi(3,"塔A","塔B","塔C");
System.out.println("步数:" + step);
}
}
参考视频:https://www.bilibili.com/video/BV1Hk4y1k7KL?from=search&seid=3869671113360747464
图片来源:https://www.zhihu.com/question/24385418
在有了前面的基础,然后看这道题,直接套上面的模板了,思路看上面即可,这里最后处理的结果就是C=A,所以你可以直接取巧,但是这就考的不是算法了!!!
class Solution {
void move(int n,List<Integer> A, List<Integer> B, List<Integer> C){
if(n==1){
C.add(A.remove(A.size()-1));
}else{
move(n-1,A,C,B);
C.add(A.remove(A.size()-1));
move(n-1,B,A,C);
}
}
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
move(A.size(),A,B,C);
}
}