【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