【LeetCode】Sama的个人记录_16

 

 

【Q面试题64】(md) 求1+2+…_n
   
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
 
示例 1
   输入: n = 3
   输出: 6
 
示例 2
   输入: n = 9
   输出: 45
 
限制:1 <= n <= 10000

class Solution {
     
	/*
	 * 题设的限制非常多:
	 * 无法乘除 —— 高斯定理失效
	 * 无法for,while循环 —— 累加失效
	 * 无法使用if判断 —— 递归失效,因为递归需要用一个if来判断终止...
	 * 
	 * 等等!递归终止或许还有别的方法?
	 * 逻辑运算符具有短路特性!
	 */
    public int sumNums(int n) {
     
    	 boolean flag = n > 0 && (n += sumNums(n - 1)) > 0;		// flag是什么,后一个表达式是否真的大于0,我们压根不关心
         return n;
    }
    public int sumNum2(int n) {
     
    	boolean flag = n <= 0 || (n += sumNum2(n - 1)) > 0;
    	return n;
    }
    // 总结:我们有个作弊的小技巧...题设总如果限制的输入不能很大的话,多半是答案是【递归】或【(递归型)回溯】
}

 

 

【Q837】(md) 新21点
 
爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏,描述如下:
 
爱丽丝以 0 分开始,并在她的得分少于 K 分时抽取数字。 抽取时,她从 [1, W] 的范围中随机获得一个整数作为分数进行累计,其中 W 是整数。 每次抽取都是独立的,其结果具有相同的概率。
 
当爱丽丝获得不少于 K 分时,她就停止抽取数字。 爱丽丝的分数不超过 N 的概率是多少?
 
示例 1
 输入:N = 10, K = 1, W = 10
 输出:1.00000
 说明:爱丽丝得到一张卡,然后停止。
 
示例 2
 输入:N = 6, K = 1, W = 10
 输出:0.60000
 说明:爱丽丝得到一张卡,然后停止。 在 W = 10 的 6种可能下,她的得分不超过 N = 6 分。
 
示例 3:
 输入:N = 21, K = 17, W = 10
 输出:0.73278

class Solution {
     
    public double new21Game(int N, int K, int W) {
     
    	/*
    	 * 【动态规划】
    	 * 我们发现,在爱丽丝此时获得 K-1 分时,下一次抽卡的获得N分及以下的概率是可以确定的:N - (K-1) / M
    	 * 突然意识到,或许可以从 后向前构建这个dp表,返回的是dp[0]
    	 * 
    	 * 因为涉及到了一些边界问题,最好是根据例子写代码:
    	 * M = 10(爱丽丝从[1, 10]中有放回抽数)
    	 * K = 17(当爱丽丝累计17分以下时才可以抽数,也就是说她最后一次有可能抽数是在累计16时)
    	 * N = 21(爱丽丝的目的是累计在21点及以内)
    	 * 
    	 * 分析已知,爱丽丝在累计16时可以抽数,如果抽到了10,那么她可能累计的最大值就是 16 + 10 = 27
    	 * dp表值代表在累计总数为下标时,再抽一次数会胜利的概率
    	 * 初始化从17开始(16需要计算):
    	 * dp[27] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 ,0 ,0 ,0, 0, 0]
    	 * dp[16] 填入 1+1+1+1+1+0+0+0+0+0 / 10 = 0/5
    	 * dp[15] 填入 0.5+1+1+1+1+1+0+0+0+0 / 10 = ...
    	 * 
    	 * 也就是说,一个位置的dp表值,等于 这个位置之后10个位置的dp表值之和 /10
    	 * 最后返回dp[0]即可,dp[0]是整个游戏过程
    	 */
    	
    	if(K == 0) {
     	// 特殊情况:爱丽丝一次也不能抽,直接满足条件胜利
    		return 1.0;
    	}
    	double dp[] = new double[K + W];				// 注意dp表下标的最大值,就是alice有可能获得的最大累计分数
    	for(int k = K; k <= N && k < K + W; k++) {
     		// 初始化dp表:从K到N置1;多加一个 k < K+M 是因为N有可能大于 K+M
    		dp[k] = 1;
    	}
    	for(int i = K - 1; i >= 0; i--) {
     				// 填表:某处的值是其之后M个位置之和  再除以M(也就是它后面M和表值的平均数)
    		double tempSum = 0;
    		for(int j = i + 1; j < i + 1 + W; j++) {
     
    			tempSum += dp[j];
    		}
    		dp[i] = tempSum / W;
    	}
    	return dp[0];
    }
    // 这个【动态规划】着实和之前的不太一样:它自后向前填表
    // 等等!动态规划的核心思想不是从【从底层基础开始构建完整过程】吗,怎么能从后向前进行呢?
    // 实际上,因为这个dp表前面的值,是构建在它之后的M个值之上的,因此恰恰是【从底层基础开始构建完整过程】
    // 因此,【动态规划】填表的顺序不是关键,关键是 哪些值 是建立在 哪些值 上的,即【谁是谁的基础】
}
class Solution {
     
    public double new21Game(int N, int K, int W) {
     
    	/*
    	 * 可是这个dp表会超时,如何优化呢?
    	 * 
    	 * 超时的原因是每次我们都要算M个数的和(再取平均)
    	 * 以上例中的 M = 10为例 :
    	 *  dp[16] 填入 1+1+1+1+1+0+0+0+0+0 / 10 = 0/5
    	 * 	dp[15] 填入 0.5+1+1+1+1+1+0+0+0+0 / 10 = ...
    	 * 有9个数我们重复计算了它们的和!
    	 * 
    	 * 优化的思路很简单,就是用一个数记录这个值;
    	 * 每次求和的时候,我们就在这个和的基础上,去掉最后一个数,加上前面一个数
    	 */
    	if(K == 0) {
     
    		return 1.0;
    	}
    	double dp[] = new double[K + W];
    	for(int k = K; k <= N && k < K + W; k++) {
     
    		dp[k] = 1;
    	}
    	double tempSum = 0;	
    	for(int j = K; j < K + W; j++) {
     	// 这是填第 K - 1 位置的表需要的和
    		tempSum += dp[j];
    	}
    	for(int i = K - 1; i >= 0; i--) {
     
    		if(i != K - 1) {
     				// 当i = K-1时,我们不需要改变这个和;i之前的位置,不断修正这个和就可以了
    			tempSum -= dp[i + W + 1];
        		tempSum += dp[i + 1];
    		}
    		dp[i] = tempSum / W;
    	}
    	return dp[0];
    }
    // 分析:官方题解中的优化思路我认为比较一般:官解极力在声明dp表时在保证安全的情况下收缩它的长度
    // 既然超时的原因是重复求和,我们通过一个记录值优化这个求和过程就好了
}

 

 

【Q238】(md) 除自身以外数组的乘积
 
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
 
示例:
   输入: [1,2,3,4]
   输出: [24,12,8,6]
 
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
 
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
 
进阶: 你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

class Solution {
     
	/*
	 * 思路还是比较清晰的 :
	 * 	【前缀积】与【后缀积】两个数组对应位置相乘
	 * 
	 * demo:
	 * nums = [1, 2, 3, 4]
	 * 对应位置的值代表【该位置左侧所有数字的和】【该位置左侧所有数字的和】(都不包括这个位置的数字本身)
	 * leftSum  = [1,  1,  2, 6]		// 从前向后构建
	 * rigthSum = [24, 12, 4, 1]		// 从后向前构建
	 * resSum   = [24, 12, 8, 6]
	 */
    public int[] productExceptSelf(int[] nums) {
     
    	int len = nums.length;
    	int[] exceptSum = new int[len];
    	int[] leftSum = new int[len];
    	int[] rigthSum = new int[len];
    	leftSum[0] = 1;
    	rigthSum[len - 1] = 1;
    	for(int i = 1; i < len; i++) {
     
    		leftSum[i] = nums[i - 1] * leftSum[i - 1];
    		rigthSum[len - 1 - i] = nums[len - i] * rigthSum[len - i];
    	}
    	for(int j = 0; j < len; j++) {
     
    		exceptSum[j] = leftSum[j] * rigthSum[j];
    	}
    	return exceptSum;
    }
    // 时间复杂度:是线性的,O(2n) = O(n)
    // 空间复杂度:前缀积数组 和 后缀积数组 都是O(n),但题设说原数组不算,原数组的大小是O(n);所以最后是空间是O(n)
}
class Solution {
     
	/*
	 * 进阶中要求使用常量空间O(1)——因为题设说原数组不算空间复杂度,因此我们被允许只使用一个O(n)的数组
	 * 那就简单了,因为在上面的方法中,我们完全可以在leftSum数组的基础上,直接操作出exceptSum:
	 * leftSum  = [1,  1,  2, 6]
	 * 直接得到:    [24, 12, 8, 6]
	 * 利用一个记录值就可以了
	 */
    public int[] productExceptSelf(int[] nums) {
     
    	int len = nums.length;
    	int[] exceptSum = new int[len];
    	exceptSum[0] = 1;
    	for(int i = 1; i < len; i++) {
     
    		exceptSum[i] = nums[i - 1] * exceptSum[i - 1];
    	}
    	int temp = 1;
    	for(int j = len - 2; j >= 0; j--) {
     
    		temp *= nums[j + 1];
    		exceptSum[j] = exceptSum[j] * temp;		
    	}
    	return exceptSum;
    }
    // 这个优化其实没什么技术含量,完全是为了解题
}

 
 

 

 

 

 

 

 

 

 

 

Qs from https://leetcode-cn.com
♦ loli suki
♥ end

你可能感兴趣的:(Leetcode,算法,动态规划,面试,java)