脑筋急转弯
- [1. 两数之和](https://leetcode.cn/problems/two-sum/)
- 2380. 二进制字符串重新安排顺序需要的时间
- [75. 颜色分类](https://leetcode.cn/problems/sort-colors/)
- [29. 两数相除](https://leetcode.cn/problems/divide-two-integers/)
- [380. O(1) 时间插入、删除和获取随机元素](https://leetcode.cn/problems/insert-delete-getrandom-o1/)
- [754. 到达终点数字](https://leetcode.cn/problems/reach-a-number/)
- [808. 分汤](https://leetcode.cn/problems/soup-servings/)
- [1758. 生成交替二进制字符串的最少操作数](https://leetcode.cn/problems/minimum-changes-to-make-alternating-binary-string/)
1. 两数之和
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
d = defaultdict(int)
for i, x in enumerate(nums):
y = target - x
if y in d: return [d[y], i]
d[x] = i
2380. 二进制字符串重新安排顺序需要的时间
class Solution:
def secondsToRemoveOccurrences(self, s: str) -> int:
t = cnt = 0
for c in s:
if c == '0': cnt += 1
elif cnt: t = max(t + 1, cnt) # 对于每一个 1 前面有 cnt 个 0 至少需要移动(或反转) cnt 次。
return t
# 001011
ans = 0
while '01' in s:
ans += 1
s = s.replace('01', '10')
return ans
75. 颜色分类
class Solution:
def sortColors(self, nums: List[int]) -> None:
def f(i, j):
nums[i], nums[j] = nums[j], nums[i]
# i 跟 0 后, j 跟 1 后
i = j = 0
for k, x in enumerate(nums):
if x == 1:
f(j, k) # j 跟踪 1 快指针
j += 1
elif x == 0:
f(i, k) # i 跟踪 0 慢指针
if i < j: # 0, 1 交换,还需要 1, 2 交换
f(j, k)
i += 1
j += 1
29. 两数相除
class Solution:
def divide(self, dividend: int, divisor: int) -> int:
if dividend == -2147483648 and divisor == -1: # -2 ** 31 / -1 = 2147483648 溢出处理
return 2147483647
a, b, ans = abs(dividend), abs(divisor), 0
for i in range(31, -1, -1):
# 从 b 的最大倍数 2 ^ i 开始尝试。
# b * 2^i <= a 换句话说 a/b = 2^i + (a-2^i*b)/b
if (b << i) <= a:
ans += 1 << i # 2 ** i
a -= b << i
return -ans if (dividend > 0) ^ (divisor > 0) else ans
380. O(1) 时间插入、删除和获取随机元素
变长数组可以在 O(1) 的时间内(通过下标)完成获取随机元素操作,但无法在 O(1) 的时间内判断元素是否存在,因此不能在 O(1) 的时间内完成插入和删除操作。哈希表可以在 O(1) 的时间内完成插入和删除操作,但无法根据下标定位到特定元素,因此不能在 O(1) 的时间内完成获取随机元素操作。结合变长数组和哈希表,变长数组中存储元素,哈希表中存储每个元素在变长数组中的下标。
class RandomizedSet:
def __init__(self):
self.q = []
self.d = {}
def insert(self, val: int) -> bool:
if val in self.d: return False
self.d[val] = len(self.q)
self.q.append(val)
return True
def remove(self, val: int) -> bool:
if val not in self.d: return False
i = self.d[val]
self.q[i] = self.q[-1] # 用末尾元素替换要删除的元素
self.d[self.q[i]] = i # 加入字典
self.q.pop() # 列表中删除
del self.d[val] # 字典中删除
return True
def getRandom(self) -> int:
return choice(self.q)
754. 到达终点数字
class Solution:
def reachNumber(self, target: int) -> int:
# 先假设 target > 0,如果不回头地往终点走 n 步,并恰好能走到终点,那么答案就是 n。
# 如果 target < 0,把每一步取反,所以只需考虑大于 0 的情况。
target = abs(target)
s = i = 0
# 超过终点,如果相距为偶数,则前面该偶数的一半时取反;如果是奇数再继续相加后的数一定会变成相距为偶数。
while s < target or (s - target) % 2:
i += 1
s += i
return i
808. 分汤
假设 dp[a][b] 为 A 剩下 a 份, B 剩下 b 份时,求的 A 先分完的概率 + A 和 B 同时分完的概率 / 2 (也可以理解为对答案的贡献)
因为存在 4 种分配操作,那么当前情况下要求的概率就等于当前情况进行 4 种分配后得到的概率之和除以 4
dp[a][b] =( dp[a - 100][b] + dp[a - 75][b - 25] + dp[a - 50][b - 50] + dp[a - 25][b - 75] ) / 4
dp[n][n] 为答案
边界条件
dp[0][0] 代表着 A 和 B 都剩下 0 份,也就是同时分完的情况
注意 : 这种情况 A 先分完的概率为 0 ,同时分完的概率为 1
对答案的贡献为 0 + 1/2 也就是 0.5
dp[0][0] = 0.5
dp[0][y]( y≠0 y ≤ n )代表着 A 剩下 0 份时 B 还没分完的情况
这种情况 A 先分完的概率为 1 ,同时分完的概率为 0
对答案的贡献为 1 + 0/2 也就是 1
dp[0][y] = 1
dp[x][0]( x≠0 x ≤ n )代表着 A 剩下 x 份时 B 已经分完的情况
这种情况 A 先分完的概率为 0 ,同时分完的概率为 0
对答案的贡献为 0 + 0/2 也就是 0
dp[x][0] = 0
优化
因为 4 种分配操作都是等概率的,所以在一次分配中
A 平均被分出 E(A)=(4+3+2+1)/4= 2.5 份
B 平均被分出 E(B)=(0+1+2+3)/4= 1.5 份
所以 n 越大时 A 先分完的概率越接近 1 ,也就是 dp[n][n] 越接近 1
因为题目给出误差可为 10^-5 ,也就是说当答案大于 0.99999 时可以直接返回 1
public class Solution{
public static void main(String[] args){
for(int i = 500; i < 5000; i++){
if(soupServings(i) > 0.99999){
System.out.println(i);
System.out.println(soupServings(i));
break;
}
}
}
}
运行结果为
4451
0.9999902113072546
所以当输入大于 4450 时,直接返回 1
public class Solution{
public static double soupServings(int n) {
if(n > 4450) return 1;
n = (int) Math.ceil(n / 25d);
double[][] dp = new double[n + 1][n + 1];
dp[0][0] = 0.5;
for(int i = 1; i <= n; i++){
dp[0][i] = 1;
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
dp[i][j] = 0.25 * (dp[Math.max(0, i - 4)][j] + dp[Math.max(0, i - 3)][Math.max(0, j - 1)] + dp[Math.max(0, i - 2)][Math.max(0, j - 2)] + dp[Math.max(0, i - 1)][Math.max(0, j - 3)]);
}
}
return dp[n][n];
}
}
方法二 : 动态规划(自顶向下)(优化)
在方法一中,对 dp 矩阵中的每个元素都进行了计算
但这实际上很多元素对于最终的答案都是无用的
那么自顶向下从 dp[n][n] 开始递推,就可以避免计算这些无用的值
首先,出现 dp[n][n] 的概率为 1 (因为我们从 dp[n][n] 开始)
dp[n][n] 进行 4 种分配后,每种情况对于开始时出现的概率都为 dp[n][n] / 4
再一次进行分配后依然如此
所以状态转移方程为
dp[a - 4][b] = dp[a][b] / 4
dp[a - 3][b - 1] = dp[a][b] / 4
dp[a - 2][b - 2] = dp[a][b] / 4
dp[a - 1][b - 3] = dp[a][b] / 4
最后,由于要求 A 先分完的概率 + A 和 B 同时分完的概率 / 2
A 和 B 同时分完的概率等于 dp[0][0]
A 先分完的概率等于 dp[0][y]( y≠0 y ≤ n ) 之和
根据要求计算结果即可
public class Solution{
public static double soupServings(int n){
if(n > 4450) return 1;
n = (int) Math.ceil(n / 25d);
double[][] dp = new double[n + 1][n + 1];
dp[n][n] = 1;
double temp;
for(int i = n; i > 0; i--){
for(int j = n; j > 0; j--){
if(dp[i][j] == 0) continue;
temp = 0.25 * dp[i][j];
dp[Math.max(0, i - 4)][j] += temp;
dp[Math.max(0, i - 3)][Math.max(0, j - 1)] += temp;
dp[Math.max(0, i - 2)][Math.max(0, j - 2)] += temp;
dp[Math.max(0, i - 1)][Math.max(0, j - 3)] += temp;
}
}
dp[0][0] /= 2;
for(int i = 1; i <= n; i++)
dp[0][0] += dp[0][i];
return dp[0][0];
}
}
方法三 : 记忆化dfs(自顶向下)
用深度优先搜索进行遍历,并记忆化以避免重复计算
public class Solution{
static double[][] dp;
public static double soupServings(int n){
if(n > 4450) return 1;
n = (int) Math.ceil(n / 25d);
dp = new double[n + 1][n + 1];
return dfs(n, n);
}
public static double dfs(int a, int b){
if(a <= 0 && b <= 0) return 0.5;
else if(a <= 0) return 1;
else if(b <= 0) return 0;
if(dp[a][b] == 0)
dp[a][b] = 0.25 * (dfs(a - 4, b) + dfs(a - 3, b - 1) + dfs(a - 2, b - 2) + dfs(a - 1, b - 3));
return dp[a][b];
}
}
作者:joneli
链接:https://leetcode.cn/problems/soup-servings/solution/by-joneli-ts7a/
1758. 生成交替二进制字符串的最少操作数
class Solution:
def minOperations(self, s: str) -> int:
'''
a, b = 0, 0
for i, c in enumerate(s):
x = int(c)
a += x ^ (i & 1) # 偶数 1 + 奇数 0
b += x ^ 1 ^ (i & 1) # 偶数 0 + 奇数 1
return min(a, b);
'''
a = b = flag = 0
for c in s:
if int(c) == flag:
a += 1
else:
b += 1
flag ^= 1
return min(a, b)
2549. 统计桌面上的不同数字
class Solution {
public int distinctIntegers(int n) {
// n 在桌面上, 下一轮 n - 1 就在桌面了。
return n == 1 ? 1 : n - 1;
}
}