力扣周赛329题解
2042. 检查句子中的数字是否递增
这道题目比较简单,只需要遍历句子中的每个单词,如果是数字,就和上一个数字比较,如果不是递增,就返回false。如果遍历完没有发现不递增的情况,就返回true。
代码如下:
class Solution {
public boolean areNumbersAscending(String s) {
// 上一个数字,初始为-1
int prev = -1;
// 按空格分割单词
String[] words = s.split(" ");
// 遍历每个单词
for (String word : words) {
// 如果是数字
if (Character.isDigit(word.charAt(0))) {
// 转换为整数
int num = Integer.parseInt(word);
// 如果不是递增,返回false
if (num <= prev) {
return false;
}
// 更新上一个数字
prev = num;
}
}
// 遍历完没有发现不递增的情况,返回true
return true;
}
}
2043. 简易银行系统
这道题目需要实现一个简易银行系统的类,支持以下几种操作:
- Bank(long[] balance) 使用给定的初始余额数组初始化该银行系统。
- boolean transfer(int account1, int account2, long money) 从编号为 account1 的账户向编号为 account2 的账户转帐 money 美元。如果交易成功,返回 true ,否则,返回 false 。
- boolean deposit(int account, long money) 向编号为 account 的账户存款 money 美元。如果交易成功,返回 true ;否则,返回 false 。
- boolean withdraw(int account, long money) 从编号为 account 的账户取款 money 美元。如果交易成功,返回 true ;否则,返回 false 。
为了实现这些操作,我们可以使用一个数组来存储每个账户的余额,并在构造函数中初始化。然后,在每个操作中,我们需要检查账户编号是否合法(在1到n之间),以及余额是否足够(不小于0)。如果满足条件,就更新余额并返回true;否则,不更新余额并返回false。
代码如下:
class Bank {
// 存储每个账户的余额
private long[] balance;
public Bank(long[] balance) {
// 初始化余额数组
this.balance = balance;
}
public boolean transfer(int account1, int account2, long money) {
// 检查账户编号是否合法
if (account1 < 1 || account1 > balance.length || account2 < 1 || account2 > balance.length) {
return false;
}
// 检查转出账户余额是否足够
if (balance[account1 - 1] < money) {
return false;
}
// 更新余额并返回true
balance[account1 - 1] -= money;
balance[account2 - 1] += money;
return true;
}
public boolean deposit(int account, long money) {
// 检查账户编号是否合法
if (account < 1 || account > balance.length) {
return false;
}
// 更新余额并返回true
balance[account - 1] += money;
return true;
}
public boolean withdraw(int account, long money) {
// 检查账户编号是否合法
if (account < 1 || account > balance.length) {
return false;
}
// 检查取款账户余额是否足够
if (balance[account - 1] < money) {
return false;
}
// 更新余额并返回true
balance[account - 1] -= money;
return true;
}
}
2044. 统计按位或能得到最大值的子集数目
这道题目给定一个整数数组nums,要求统计有多少个非空子集,满足子集中的元素按位或的结果等于数组中所有元素按位或的结果。例如,如果nums = [3,1],那么所有元素按位或的结果是3,只有子集[3]和[3,1]满足条件,所以答案是2。
这道题目的难点在于如何高效地枚举所有子集,并判断它们是否满足条件。如果直接使用回溯或者位运算的方法,时间复杂度会很高,可能会超时。为了优化时间复杂度,我们可以利用一些数学和位运算的性质,来简化问题。
首先,我们可以观察到,如果一个子集满足条件,那么它的补集也一定满足条件。因为如果一个子集中的元素按位或的结果等于所有元素按位或的结果,那么补集中的元素按位或的结果一定是0。反之亦然。所以,我们可以只考虑包含最高位1的子集,然后将答案乘以2。
其次,我们可以观察到,如果一个子集包含最高位1,那么它只需要考虑最高位以下的位数是否满足条件。因为最高位已经确定了,所以不会影响最终的结果。所以,我们可以将问题转化为求解最高位以下的位数有多少种组合方式。
最后,我们可以观察到,如果一个子集包含最高位1,并且最高位以下的位数也满足条件,那么它必须包含所有元素中在某一位上为1而其他元素在该位上为0的元素。因为这些元素是唯一能影响该位结果的元素,如果不包含它们,就无法得到最大值。所以,我们可以将问题转化为求解除去这些必须包含的元素之外,剩下的元素有多少种选择方式。
综上所述,我们可以使用以下的算法来解决这道题目:
- 首先,计算数组中所有元素按位或的结果max,并找出它的最高位high。
- 然后,遍历数组中的每个元素,如果它们在high以下的某一位上为1而其他元素在该位上为0,就将它们加入到必须包含的集合must中。
- 最后,计算除去must中的元素之外,剩下的元素有多少种选择方式。这个可以用组合数公式来计算:C(n,k) = n!/(k!*(n-k)!)。其中n是剩下元素的个数,k是任意选择的个数(从0到n)。由于结果可能很大,需要对一个大整数mod取模。
代码如下:
class Solution {
// 定义一个大整数mod
private static final int mod = (int)1e9 + 7;
public int countMaxOrSubsets(int[] nums) {
// 计算数组中所有元素按位或的结果max
int max = 0;
for (int num : nums) {
max |= num;
}
// 找出max的最高位high
int high = 31;
while (((max >> high) & 1) == 0) {
high--;
}
// 创建一个集合must,用于存储必须包含的元素
Set<Integer> must = new HashSet<>();
// 遍历数组中的每个元素
for (int num : nums) {
// 如果它们在high以下的某一位上为1而其他元素在该位上为0,就加入到must中
for (int i = 0; i < high; i++) {
if (((num >> i) & 1) == 1) {
boolean unique = true;
for (int other : nums) {
if (other != num && ((other >> i) & 1) == 1) {
unique = false;
break;
}
}
if (unique) {
must.add(num);
break;
}
}
}
}
// 计算除去must中的元素之外,剩下的元素有多少种选择方式
int n = nums.length - must.size();
int ans = 0;
// 遍历任意选择的个数k,从0到n
for (int k = 0; k <= n; k++) {
// 计算组合数C(n,k),并累加到答案中
ans += combination(n, k);
ans %= mod;
}
// 将答案乘以2,因为补集也满足条件
ans *= 2;
ans %= mod;
// 返回答案
return ans;
}
// 计算阶乘n!
private int factorial(int n) {
int res = 1;
for (int i = 2; i <= n; i++) {
res *= i;
res %= mod;
}
return res;
}
// 计算组合数C(n,k)
private int combination(int n, int k) {
// 使用公式C(n,k) = n!/(k!*(n-k)!)
int a = factorial(n);
int b = factorial(k);
int c = factorial(n - k);
// 使用费马小定理求逆元,即(a/b)%mod = a*(b^(mod-2))%mod
int d = (int)((long)b * c % mod);
int e = pow(d, mod - 2);
return (int)((long)a * e % mod);
}
// 计算快速幂x^n
private int pow(int x, int n) {
long res = 1;
long base = x;
while (n > 0) {
if ((n & 1) == 1) {
res *= base;
res %= mod;
}
base *= base;
base %= mod;
n >>= 1;
}
return (int)res;
}
}
2045. 第二小的节点
这道题目给定一棵有根的二叉树,要求找出第二小的节点的值。如果不存在第二小的节点,就返回-1。题目保证树中至少有两个节点,并且每个节点的值都不同。
这道题目的思路是,首先找出树中最小的节点的值,然后再遍历树中的每个节点,找出比最小值大的最小值,就是第二小的节点的值。为了方便,我们可以使用一个全局变量来存储最小值和第二小值,并在遍历过程中更新它们。
代码如下:
class Solution {
// 定义一个全局变量min,用于存储最小值
private int min;
// 定义一个全局变量secondMin,用于存储第二小值
private int secondMin;
public int findSecondMinimumValue(TreeNode root) {
// 如果根节点为空,返回-1
if (root == null) {
return -1;
}
// 初始化最小值和第二小值为根节点的值
min = root.val;
secondMin = root.val;
// 遍历树中的每个节点,更新最小值和第二小值
traverse(root);
// 如果第二小值没有变化,说明不存在第二小的节点,返回-1
if (secondMin == min) {
return -1;
}
// 否则,返回第二小值
return secondMin;
}
// 遍历树中的每个节点的辅助函数
private void traverse(TreeNode node) {
// 如果节点为空,直接返回
if (node == null) {
return;
}
// 如果节点的值比最小值小,更新最小值和第二小值
if (node.val < min) {
secondMin = min;
min = node.val;
}
// 如果节点的值比最小值大且比第二小值小,更新第二小值
else if (node.val > min && node.val < secondMin) {
secondMin = node.val;
}
// 递归遍历左右子树
traverse(node.left);
traverse(node.right);
}
}