每日算法-250515
每日算法打卡 - 2025-05-15
今天我完成了以下三道 LeetCode 算法题,特此记录解题思路与代码。
2342. 数位和相等数对的最大和
题目描述
给你一个下标从 0 开始的数组 nums
,数组中的元素都是 正 整数。请你选出两个下标 i
和 j
(i != j
),且 nums[i]
的数位和 与 nums[j]
的数位和相等。
请你找出所有满足条件的下标对 (i, j)
中,nums[i] + nums[j]
的 最大值 ,如果不存在满足条件的数对,请返回 -1
。
思路与解题过程
核心思路:哈希表(数组模拟)
题目要求找到数位和相等的两个不同数字,并使它们的和最大。
- 数位和计算:首先,我们需要一个辅助函数
getDigit(int x)
来计算一个整数x
的所有数位之和。例如,getDigit(123)
返回1+2+3=6
。 - 存储与查找:
- 由于
1 <= nums[i] <= 10^9
,一个数的最大数位和是999,999,999
的数位和,即9 * 9 = 81
。因此,我们可以使用一个大小为82
(索引 0 到 81) 的数组map
来记录每个数位和对应的 当前遇到的最大数字。map[sum]
存储数位和为sum
的数字中,目前遍历到的最大的那个。 - 遍历
nums
数组中的每一个数x
:- 计算
x
的数位和digitSum = getDigit(x)
。 - 查看
map[digitSum]
中是否已经存储了值 (设为oldVal
)。- 如果
oldVal != 0
(或者一个特定的初始标记值,这里用 0 表示未存储,因为题目中nums
元素都是正整数),说明之前已经遇到过一个数位和也为digitSum
的数字oldVal
。那么x
和oldVal
就形成了一个数位和相等的数对。它们的和是x + oldVal
。我们用这个和去更新全局的最大和ret
。 - 无论
oldVal
是否为 0,我们都需要更新map[digitSum]
。为了确保将来能组成更大的和,map[digitSum]
应该存储数位和为digitSum
的数中,目前遇到的那个最大的数。所以,map[digitSum] = Math.max(map[digitSum], x)
。
- 如果
- 计算
- 由于
- 初始化:
ret
初始化为-1
,map
数组初始化为0
。
复杂度分析
- 时间复杂度: O ( N ⋅ log M ) O(N \cdot \log M) O(N⋅logM),其中 N N N 是
nums
数组的长度, M M M 是nums
中元素的最大值。计算每个数字的数位和需要 O ( log M ) O(\log M) O(logM) 的时间(因为数字的位数与 log M \log M logM 成正比)。 - 空间复杂度: O ( C ) O(C) O(C),其中 C C C 是最大可能的数位和。在这里 C = 81 C=81 C=81,所以是 O ( 1 ) O(1) O(1) 的常数空间。
代码实现
class Solution {public int maximumSum(int[] nums) {int ret = -1;int[] map = new int[82]; for (int x : nums) {int digitSum = getDigitSum(x);int oldVal = map[digitSum];if (oldVal != 0) { ret = Math.max(ret, (oldVal + x));}map[digitSum] = Math.max(oldVal, x); }return ret;}private int getDigitSum(int x) {int sum = 0;while (x > 0) {sum += x % 10;x /= 10;}return sum;}
}
121. 买卖股票的最佳时机
题目描述
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
思路与解题过程
核心思路:一次遍历,记录最低价格和最大利润
我们只需要遍历一次价格数组,在遍历过程中动态维护两个关键信息:
minPrice
:到目前为止遇到的最低股票价格。maxProfit
:到目前为止可以实现的最大利润。
遍历 prices
数组中的每个价格 currentPrice
:
- 计算潜在利润:如果
currentPrice > minPrice
,说明如果在minPrice
时买入,在currentPrice
时卖出,是可以盈利的。其利润为currentPrice - minPrice
。我们将这个潜在利润与maxProfit
比较,更新maxProfit = Math.max(maxProfit, currentPrice - minPrice)
。 - 更新最低价格:无论当前是否能盈利,我们都需要用
currentPrice
来更新minPrice
,即minPrice = Math.min(minPrice, currentPrice)
。这样做是为了确保minPrice
始终是历史最低点,为后续计算潜在利润做准备。
初始时,maxProfit
为 0
,minPrice
可以设置为 prices[0]
或一个极大值。
复杂度分析
- 时间复杂度: O ( N ) O(N) O(N), 其中 N N N 是
prices
数组的长度。我们只需要遍历数组一次。 - 空间复杂度: O ( 1 ) O(1) O(1), 我们只使用了几个额外的变量。
代码实现
class Solution {public int maxProfit(int[] prices) {int maxProfit = 0;int minPrice = prices[0]; for (int i = 0; i < prices.length; i++) {int currentPrice = prices[i];if (minPrice < currentPrice) {maxProfit = Math.max(maxProfit, (currentPrice - minPrice));}minPrice = Math.min(minPrice, currentPrice);}return maxProfit;}
}
76. 最小覆盖子串(复习)
题目描述
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
复习心得
这是第二次做这道题,对滑动窗口的理解更深了。核心在于如何判断当前窗口是否“合法”(即覆盖了 t
中所有字符及对应数量),以及在合法的基础上如何收缩窗口以找到最小长度。
详细题解之前写过,可参考:每日算法-250331
代码实现
class Solution {public String minWindow(String ss, String tt) {char[] s = ss.toCharArray();char[] t = tt.toCharArray();int retLen = s.length + 1;int[] map = new int[128];int kinds = 0;for (char c : t) {if (map[c] == 0) {kinds++;}map[c]++;}int kindAndCount = 0;int[] sMap = new int[128];int index = 0;for (int left = 0, right = 0; right < s.length; right++) {char in = s[right];if (++sMap[in] == map[in]) {kindAndCount++;}while (kindAndCount >= kinds) {if (retLen >= (right - left + 1)) {retLen = (right - left + 1);index = left;}char out = s[left];if (map[out] == sMap[out]--) {kindAndCount--;}left++;}}return retLen == (s.length + 1) ? "" : ss.substring(index, index + retLen);}
}