由简单题一步步推导到动态规划

338比特位计数

在这里插入图片描述

链接:https://leetcode-cn.com/problems/counting-bits/solution/yi-bu-bu-fen-xi-tui-dao-chu-dong-tai-gui-3yog/

我的解法:
class Solution {
    public int[] countBits(int n) {
        int[] bits = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            bits[i]=Integer.bitCount(i);
        }
        return bits;
    }

}

时间复杂度: O(N),因为遍历了一次。

空间复杂度:O(1),返回结果占用的空间不计入空间复杂度中。

其中bitCount实现的功能是计算一个(byte,short,char,int统一按照int方法计算)int,long类型的数值在二进制下“1”的数量。

递归解法:

​ 如果 第 i 是偶数:

​ 那么它的二进制 1 的位数与 i / 2的二进制 1 的位数相等;因为偶数的二进制末尾是 0,右移一位等于 i / 2,而二进制中 1 的个数没有变化。
比如对于 4 和 2:

  数字	   二进制	          二进制中 1 的个数
	4		 100					  1
	2		 10						  1
如果 第i 是奇数:

​ 那么它的二进制 1 的位数 = i - 1 的二进制位数 + 1;因为奇数的二进制末尾是 1,如果把末尾的 1 去掉就等于 i - 1。又 i - 1 是偶数,所以奇数 i 的二进制 1 的个数等于 i/2中二进制 1 的位数 +1.
比如对于 5 和 4:

	  数字	   二进制	          二进制中 1 的个数
		5		 101					  2
		4		 100					  1

通过上面的分析我们可以看出可以根据递归解决:


class Solution {
    public int[] countBits(int n) {
        int[] bits = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            bits[i]=getnum(i);
        }
        return bits;
    }
    public int getnum(int num){
        if(num == 0){
            return 0;
        }else if(num&1== 0){
            //  >>相当于除2这样比直接/2快
            return getnum(num >> 1);
        }else{
            return getnum(num-1)+1;
        }
    }
}

  • 时间复杂度: O(N ^ 2),因为遍历了一次,每次求解最多需要递归N/2次。
  • 空间复杂度:O(N),递归需要调用系统栈,栈的大小最多为 N / 2。
方法三:记忆化搜索

在我们上面递归解法中,其实有很多重复的计算,比如当 i = 8 的时候,需要求 i = 4, 2, 1, 0 的情况,而这些取值已经计算过了,此时可以使用记忆化搜索。

所谓记忆化搜索,就是在每次递归函数结束的时候,把计算结果保存起来。这样的话,如果下次递归的时候遇到了同样的输入,则直接从保存的结果中直接查询并返回,不用再次递归。

class Solution {
    int res[];
    public int[] countBits(int n) {
        int[] bits = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            bits[i]=getnum(i);
        }
        return bits;
    }
     //试一下递归做法,就是操作res
    public int getnum(int num){
        if(num == 0){
            return 0;
        }
        //这里就是进行的记忆话搜索
        if(res[num]!=0){
            return res[num];
        }
        int ans=0;
        if(num&1 == 0){
            ans=getnum(num >> 1);
        }else{
            ans=getnum(num-1)+1;
        }
         //不能只读啊 写肯定也要
        res[n]=ans;
        return ans;
    }
}

时间复杂度: O(N),因为遍历了一次,每次求解都可以从之前的记忆化结果中找到

空间复杂度:O(N),用到了辅助的空间保存结果,空间的结果是 O(N)

方法四:动态规划

其实很多时候,动态规划的方法都是从记忆化搜索中优化出来的。本题也可以如此。

​ 方法三在记忆化搜索过程中,我们看到其实每次调用递归函数的时候,递归函数只会运行一次,就被 res[num] 捕获并返回了。那么其实可以去除递归函数,直接从 res 数组中查结果。

class Solution {
	 public int[] countBits(int num) {
		 int[] result=new int[num+1];
		 result[0]=0;
		 for(int i=1;i<num+1;i++){
        	 if(i&1==0) result[i]=result[i>>1];
        	 else result[i]=result[i-1]+1;
        }
	        return result; 
	 }
}
  • 时间复杂度: O(N),因为遍历了一次。
  • 空间复杂度:O(1),返回结果占用的空间不计入空间复杂度中。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态规划的最优二叉查找树问,通常也称为哈夫曼树问,它的递推公式可以使用以下方法推导得到: 假设给定一个有序序列 $K=\{k_1,k_2,\cdots,k_n\}$,对应的概率序列为 $p=\{p_1,p_2,\cdots,p_n\}$。我们需要构建一棵二叉查找树 $T$,使得查找所有元素的期望代价最小,其中期望代价为查找每个元素的代价乘以该元素被查找的概率之和。 我们定义 $w_{i,j}$ 表示从 $k_i$ 到 $k_j$ 的所有元素作为叶子节点构成的子树的概率之和,即: $$ w_{i,j} = \sum_{l=i}^j p_l $$ 我们同时定义 $e_{i,j}$ 表示在以 $k_i$ 到 $k_j$ 的元素为叶子节点的子树中进行一次查找的期望代价,即: $$ e_{i,j} = \begin{cases} q_{i-1}, & j=i-1 \\ \min\limits_{i\leqslant r \leqslant j}\{e_{i,r-1}+e_{r+1,j}+w_{i,j}\}, & i\leqslant j \end{cases} $$ 其中 $q_{i-1}$ 表示在查找失败时返回 $k_{i-1}$ 的代价,即叶子节点的左侧边界的值的代价。 根据上述定义,我们可以得到最终的递推公式: $$ e_{i,j} = \begin{cases} q_{i-1}, & j=i-1 \\ \min\limits_{i\leqslant r \leqslant j}\{e_{i,r-1}+e_{r+1,j}+w_{i,j}\}, & i\leqslant j \end{cases} $$ 这个公式的含义是:对于每个子序列 $K_{i,j}$,我们枚举其中一个元素 $k_r$ 作为根节点,然后将其左侧子序列 $K_{i,r-1}$ 和右侧子序列 $K_{r+1,j}$ 作为 $k_r$ 的左右子树,此时期望代价为左右子树期望代价之和加上 $w_{i,j}$,选择其中最小的作为 $e_{i,j}$。最终,我们可以得到整个序列 $K$ 的最优二叉查找树的期望代价为 $e_{1,n}$。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值