每日算法-250504
每日算法 - 250504
记录今天解决的几道 LeetCode 算法题。
605. 种花问题
题目描述:
思路
贪心
这道题可以使用贪心策略。核心思想是:当我们遍历花坛时,一旦遇到一个可以种花的位置,就立即种花。因为在一个位置种花不会影响到更左边的位置是否能种花,而尽早种花可能会让右边的位置有更多种花的机会(或者说,不会让右边的情况变得更糟)。所以,局部最优(能种就种)可以导致全局最优(种最多的花)。
解题过程
遍历
flowerbed
数组,检查每个位置i
是否可以种花。
- 位置
i
必须是0
(没有花)。- 位置
i
的左边必须是0
(或者i
是第一个位置)。- 位置
i
的右边必须是0
(或者i
是最后一个位置)。同时满足这三个条件,就可以在位置
i
种一朵花,并将flowerbed[i]
更新为1
,同时将需要种植的花的数量k
减一。为了简化边界判断,可以在原数组两端各补一个
0
,但这会增加空间复杂度。另一种方法是在循环中处理边界情况:
- 当
i = 0
时,只需检查flowerbed[i]
和flowerbed[i+1]
。- 当
i = n - 1
时,只需检查flowerbed[i-1]
和flowerbed[i]
。- 对于中间位置
0 < i < n - 1
,需要检查flowerbed[i-1]
,flowerbed[i]
, 和flowerbed[i+1]
。需要特别处理
n = 1
的情况。如果只有一个位置,能种花的条件是该位置为0
,最多能种 1 朵。
复杂度
- 时间复杂度: O ( n ) O(n) O(n), 其中 n n n 是
flowerbed
的长度。我们只需要遍历数组一次。 - 空间复杂度: O ( 1 ) O(1) O(1), 我们只使用了常数级别的额外空间(修改是在原数组上进行的)。
Code
class Solution {public boolean canPlaceFlowers(int[] flowerbed, int k) {int n = flowerbed.length;if (n == 1) {return (flowerbed[0] == 0 ? true : k == 0);}for (int i = 0; i < n && k > 0; i++) {if (flowerbed[i] == 0) {if (i == 0) {if (flowerbed[i + 1] == 0) {flowerbed[i] = 1;k--;}} else if (i == n - 1) {if (flowerbed[i - 1] == 0) {flowerbed[i] = 1;k--;} } else {if (flowerbed[i - 1] == 0 && flowerbed[i + 1] == 0) {flowerbed[i] = 1;k--;}}}}return k == 0;}
}
3111. 覆盖所有点的最少矩形数目
题目描述:
思路
贪心
由于矩形的高度没有限制,我们只需要关注点的横坐标 x
。为了用最少的矩形覆盖所有点,我们应该让每个矩形的宽度 w
尽可能地覆盖更多的点。
解题过程
- 将所有点
points
按照横坐标x
从小到大排序。这使得我们可以线性地处理点。- 初始化所需矩形数量
ret = 1
(至少需要一个矩形来覆盖第一个点)。- 维护一个当前矩形能覆盖的最左边的点的索引
left
(初始为0
)。- 遍历排序后的点(从第二个点开始,索引为
right
)。- 对于每个点
points[right]
,计算它与当前矩形最左点points[left]
的横坐标之差points[right][0] - points[left][0]
。- 如果这个差值大于
w
,说明当前点points[right]
无法被当前的矩形覆盖。因此,我们需要一个新的矩形来覆盖points[right]
以及后续的点。此时,增加矩形数量ret++
,并将points[right]
作为新的矩形的起始点,即更新left = right
。- 继续遍历,直到所有点都被检查过。
- 最终的
ret
就是所需的最小矩形数量。
复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN), 主要是排序所需的时间。遍历过程是 O ( N ) O(N) O(N)。
- 空间复杂度: O ( log N ) O(\log N) O(logN) 或 O ( N ) O(N) O(N), 取决于排序算法使用的额外空间。如果忽略排序空间或者排序是原地进行的,可以认为是 O ( 1 ) O(1) O(1)。
Code
class Solution {public int minRectanglesToCoverPoints(int[][] points, int w) {Arrays.sort(points, (arr1, arr2) -> Integer.compare(arr1[0], arr2[0]));int ret = 1;for (int left = 0, right = 0; right < points.length; right++) {if (points[right][0] - points[left][0] > w) {ret++;left = right;}}return ret;}
}
2957. 消除相邻近似相等字符
题目描述:
思路
贪心
我们要最小化修改次数。考虑相邻的两个近似相等字符 s[i-1]
和 s[i]
。我们必须修改其中一个。
- 如果修改
s[i-1]
,可能会导致s[i-2]
和修改后的s[i-1]
近似相等。 - 如果修改
s[i]
,它会同时解决s[i-1]
和s[i]
之间的问题。并且,修改s[i]
后,它与s[i+1]
是否近似相等是后续需要考虑的问题。
贪心策略:从左到右遍历字符串。当发现 s[i]
和 s[i-1]
近似相等时,选择修改 s[i]
。因为修改 s[i]
可以确保 s[i-1]
和 s[i]
不再近似相等。修改 s[i]
后,s[i]
和 s[i+1]
之间的关系就与 s[i-1]
无关了。为了确保修改后的 s[i]
不会与 s[i+1]
冲突,并且尽可能少地影响后续决策,一个有效的贪心做法是:修改 s[i]
并跳过对 s[i+1]
的检查。因为我们修改了 s[i]
,所以 s[i]
和 s[i+1]
肯定不会是需要处理的“原始”近似相等对了。我们只需要统计修改次数,不需要实际执行修改。
解题过程
- 将字符串转换为字符数组
s
(方便操作,虽然也可以直接用charAt
)。- 初始化修改次数
ret = 0
。- 从第二个字符开始遍历 (
i = 1
到s.length - 1
)。- 检查
s[i]
和s[i-1]
是否近似相等(Math.abs(s[i] - s[i-1]) <= 1
)。- 如果近似相等,说明需要进行一次修改。增加
ret++
。因为我们选择修改s[i]
,那么无论s[i]
修改成什么,它都不会再和s[i-1]
近似相等。同时,为了避免这次修改影响s[i]
和s[i+1]
的判断,我们直接跳过下一个字符的检查,即i++
。- 如果
s[i]
和s[i-1]
不近似相等,继续检查下一个位置i+1
。- 返回最终的修改次数
ret
。
复杂度
- 时间复杂度: O ( n ) O(n) O(n), 其中 n n n 是字符串
word
的长度。我们只需要遍历字符串(或字符数组)一次。 - 空间复杂度: O ( n ) O(n) O(n) 如果使用了
toCharArray()
。如果直接使用word.charAt(i)
进行遍历,则空间复杂度为 O ( 1 ) O(1) O(1)。
Code
class Solution {public int removeAlmostEqualCharacters(String word) {char[] s = word.toCharArray();int ret = 0;for (int i = 1; i < s.length; i++) {if (Math.abs(s[i] - s[i - 1]) <= 1) {ret++;i++;}}return ret;}
}
162. 寻找峰值(复习)
题目描述:
这是第二次写这道题了,写的还不错,就不再多说了。
详细题解见之前的笔记:每日算法-250414
Code
class Solution {public int findPeakElement(int[] nums) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (mid != nums.length - 1 && nums[mid] < nums[mid + 1]) {left = mid + 1;} else {right = mid - 1;}}return left;}
}