代码随想录算法训练营第41天 || 343. 整数拆分 || 96.不同的二叉搜索树

代码随想录算法训练营第41天 || 343. 整数拆分 || 96.不同的二叉搜索树

343. 整数拆分

题目介绍:

给定一个正整数 n ,将其拆分为 k正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积

示例 1:

输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

个人思路:

说实话,我的这种方法并不是动态规划,算是数学规律常识法,也可能有点贪心的思路吧。

不难发现,我们将一个数分成k份时,必然是分的数越平均,乘积越大(不证明了),不能直接完全均分,余数也要均分,然后再将所有数相乘即可得到分成k份的最大乘积值,例如将10分成3份可以10/3 = 3; 10%3 = 1;,所以,我们可以分成3 3 4,最大乘积为36

同时,我们也不难发现,k从2开始递增到n时,得到的每个最值乘积,必然呈现先增后减的单调性。所以我们只需要遍历到非递增的位置即可结束遍历,返回结果。

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n + 1];
        for (int i = 2; i < dp.length; i++) {
            dp[i] = 1;
            int num1 = n / i;//均分数
            int num2 = n % i;//均分后的余数
            //连乘i-num2个均分数
            for (int j = 0; j < i - num2; j++)
                dp[i] *= num1;
            //连乘num2个均分数+1的和
            for (int j = 0; j < num2; j++)
                dp[i] *= num1 + 1;
            //出现递减后面肯定都是递减,算是一种数学规律吧,先增后减
            if (dp[i] <= dp[i - 1])
                return dp[i - 1];
        }
        return dp[n];
    }
}

题解解析:

动规五部曲:

  1. 确定dp数组及其下标含义

    dp[i]:分拆数字i,得到的最大乘积dp[i]

  2. 确定递推公式

    dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

    表示拆分数字i,循环递增j,比较dp[i](i - j) * jdp[i - j] * j

    • dp[i]:过程中不断刷新的最大值
    • (i - j) * j:拆分成ji-j两个数的情况
    • dp[i - j] * j:拆分成ji-j能拆成的最大乘积组合(三个数及以上)

    如此遍历下来,就能遍历完所有情况固定一个数j,然后其余数找到它的最大乘积组合;也就是两个数的组合和多个数的组合

    一些疑问:

    • 为什么j不拆分呢?
      • j在遍历过程中已经都计算过了,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
    • 为什么要比较这两个,(i - j) * jdp[i - j] * j,代表什么含义呢
      • 前者表示两个数的组合乘积,后者表示至少三个数的最大乘积
      • 我们不要忘了,拆分至少分成两份
  3. 初始化dp数组

    下标0、1处没必要去初始化,没有意义(虽然也可以过)

    我们只需要初始化dp[2] = 1即可

  4. 确定遍历顺序

    由递推公式dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));可知我们正序遍历,内层也设置一个for循环遍历j即可

  5. 打印dp数组检验

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n + 1];
        //dp[i]表示i拆分得到的最大乘积数
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            for (int j = 1; j <= i / 2; j++) {//这里遍历到i/2即可,因为再往后不符合均分最大常识
                //分别表示遍历过的最大值、拆成两个数的最大乘积、拆成至少3个数的最大乘积
                dp[i] = Integer.max(dp[i], Integer.max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
}

96.不同的二叉搜索树

题目介绍:

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

代码随想录算法训练营第41天 || 343. 整数拆分 || 96.不同的二叉搜索树_第1张图片

输入:n = 3
输出:5

示例 2:

输入:n = 1
输出:1

个人思路:

先画出n = 1n = 4的二叉搜索树情况,分析规律

不难发现,n确定时,选择根节点不同,它的左右子树的节点数量就不同,然后一不小心发现,子树的搜索树数量可以用之前的dp数组得到,故得到递推公式dp[i] += dp[j] * dp[i - 1 - j];

动规五部曲:

  1. 确定dp数组及其下标含义

    int[] dp = new int[n + 1];
    //dp[i]表示i个结点有多少种二叉搜索树
    
  2. 确定递推公式

    //遍历左右子树不同数量的各种情况,子树的搜索树数量可以找之前的dp数组
    for (int j = 0; j < i; j++) 
        dp[i] += dp[j] * dp[i - 1 - j];//注意i-1
    
  3. 初始化dp数组

    dp[0] = 1;//相当于某一孩子结点为null,也就是它也算是一种情况
    
  4. 确定遍历顺序

    正常正序遍历即可

  5. 打印dp数组检验

代码:

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n + 1];//dp[i]表示i个结点有多少种二叉搜索树
        dp[0] = 1;
        for (int i = 1; i <= n; i++) {
            //遍历左右子树不同数量的各种情况,子树的搜索树数量可以找之前的dp数组
            for (int j = 0; j < i; j++) {
                dp[i] += dp[j] * dp[i - 1 - j];
//                System.out.println(i + "  " + dp[i]);
            }
        }
        return dp[n];
    }
}

= 0; j < i; j++) {
dp[i] += dp[j] * dp[i - 1 - j];
// System.out.println(i + " " + dp[i]);
}
}
return dp[n];
}
}




你可能感兴趣的:(算法,动态规划,leetcode)