接下来会以刷常规题为主 ,周赛的难题想要独立做出来还是有一定难度的,需要消耗大量时间
比赛地址
public class Solution {
public int minimumCost(int[] nums) {
if (nums.length < 3) {
// 数组长度小于3时,无法分割成3个子数组
return -1;
}
int minCost = Integer.MAX_VALUE;
int n = nums.length;
// 第一个分割点至少在索引1,第二个分割点至少在索引2
for (int i = 1; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
int cost = nums[0] + nums[i] + nums[j];
minCost = Math.min(minCost, cost);
}
}
return minCost;
}
}
冒泡排序
class Solution {
public boolean canSortArray(int[] nums) {
int n = nums.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (Integer.bitCount(nums[j]) == Integer.bitCount(nums[j + 1]) && nums[j]>nums[j + 1]) {
// 如果前一个元素的1的数量大于后一个元素的1的数量,交换它们
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
// 遍历完后,检查数组是否有序
for (int i = 0; i < n - 1; i++) {
if (nums[i] > nums[i + 1]) {
return false;
}
}
return true;
}
}
当 x 用 minNum表示数组 nums 中的最小元素 用 minCount 表示数组 nums 中的 minNum 的出现次数 分别考虑 minCount=1 和 minCount>1 的情况。 则可以每次选择 minNum 和另一个元素,由于 minNum 一定小于另一个元素,因此总是可以删除另一个元素,保留 minNum,直到数组 nums 中只有一个元素 minNum,数组 nums的最小长度是 1。 这种方法会超时 效率和之前的方法相差无几 这道题目的思路是利用滑动窗口结合两个堆(优先队列)来找出序列中指定数量(`k-1`)的最小数的和,它们是从序列的某个区间(该区间长度由`dist`决定)中选择出来的。这个序列中的第一个数 (`nums[0]`) 是固定的,所以总是被包含在结果中。 下面是详细的解题步骤: 初始化两个堆:一个小顶堆 使用 HashMap 进行延迟删除:为了实现有效地从堆中删除特定的非堆顶元素,创建两个 填充初始窗口:从 从 滑动窗口:在数组中滑动窗口,并动态维护这两个堆以保持正确的最小 调整堆:当窗口滑动导致元素移出窗口时,更新 处理新进入窗口的元素:窗口滑动时,可能会有新的元素进入。这些新元素需要加入到 求解最终结果:在滑动窗口过程中,每次窗口更新后,计算此时的窗口总和加上 这个方法高效地使用了堆结构来保持每次窗口移动后,都能快速地选择出当前窗口中的k-2个最小数,而HashMap的标记删除机制则可以绕过优先队列不支持直接删除的限制。通过这个算法,你可以在移动窗口的过程中,不断更新当前窗口的最小值和,最终得到包含`nums[0]`在内的最小成本和。 思考1:为什么要用大顶堆只用小顶堆会怎么样? 因为小顶堆只能让您迅速访问堆中的最小值,而不是最大值。因此,如果窗口中有一个更小的数字需要加入到已满的小顶堆中(这时候我们需要替换掉小顶堆中最大的数字),您需要一种方式来找到小顶堆中的最大值,而大顶堆允许我们做到这一点。 思考2:bigMark.merge(tmp, -1, Integer::sum)这个是干什么 在Java中的
class Solution:
def minimumArrayLength(self, nums: List[int]) -> int:
min_val = min(nums)
count_min = nums.count(min_val)
for num in nums:
if num % min_val != 0:
return 1 # 产生了新的更小值
# 没有产生新的最小值,计算最小值的数量
return (count_min ) // 2 +1 if count_min % 2 != 0 else count_min // 2
100178. 将数组分成最小总代价的子数组 II
一、直接用滑动窗口求解
class Solution:
def minimumCost(self, nums: List[int], k: int, dist: int) -> int:
first = nums[0] # 初始元素的代价
window_size = dist + 1 # 窗口大小
minimumCost = float('inf') # 初始化最小代价为无穷大
# 遍历数组,寻找除第一个和最后一个元素之外的最小的 k-1 个元素
for start in range(1, len(nums) - window_size + 1):
window = nums[start:start + window_size]
sorted_window = sorted(window)
# 获取除第一个的 k-1 个最小元素的和
window_cost = sum(sorted_window[:k-1])
# 更新最小代价
minimumCost = min(minimumCost, window_cost)
# 最终的最小总代价是第一个元素的代价加上最小窗口代价
return first + minimumCost
二、引入堆的代码实现
class Solution:
def minimumCost(self, nums: List[int], k: int, dist: int) -> int:
first = nums[0]
n = len(nums)
minimumCost = float('inf')
for start in range(1, n - dist):
# 维护一个大小为 dist + 1 的最小堆
min_heap = nums[start:start + dist + 1]
heapq.heapify(min_heap)
window_cost = 0
# 弹出最小的 k-1 个元素并计算它们的和
for _ in range(k-1):
if min_heap:
window_cost += heapq.heappop(min_heap)
minimumCost = min(minimumCost, window_cost)
return first + minimumCost
三、大小顶堆、延迟删除、滑动窗口
small
来保存当前窗口中的最小的 k-2
个数,以及一个大顶堆 big
来保存窗口内剩余的数。HashMap
(smallMark
和 bigMark
) 来标记堆中元素是否已经被 "删除"。该删除实际上是延迟执行的,即直到这个元素出现在堆顶时才真正被排除。nums
数组的第二个元素开始,将 dist+1
长度内的元素放入 big
堆。big
中取出 k-2
个最小元素:这 k-2
个元素是将要加入 small
的,记录这 k-2
个数的和作为窗口的当前总和。k-2
个数的总和。small
堆以保持其有效性,并进行相应的调整。如果移出的元素当前在 small
中,则它需要被标记为已删除;如果它在 big
中,则直接标记为已删除。big
堆中。从 big
中取出的最小元素会放入 small
堆,并更新当前窗口总和(sum
)。nums[0]
(固定加入)。所有窗口中总和的最小值即为所求问题的答案。class Solution {
// small是小顶堆 维护前k-2小的数
// big是大顶堆 维护窗口内剩下的数
PriorityQueue
PriorityQueue
并没有提供直接删除特定元素的操作,而是只提供了删除堆顶元素的操作。为了解决这个问题,bigMark
的用途是实现“延迟删除”,这个技巧通常在优先队列中删除非顶部元素时使用。
bigMark
是一个 HashMap
,它的键是元素值,值是该元素被标记删除的次数。big
中删除一个元素时,我们不能直接删除它,因为它可能不在堆顶。bigMark
中对这个元素的删除次数加一。这个标记表示元素已经被逻辑上删除,尽管它仍在优先队列中。merge
方法是一个合并函数,它会检查 HashMap
中是否存在键 tmp
:
Integer::sum
将当前值与给定值相加。tmp
,它会插入键值对 tmp -> -1
。merge
方法用 -1
更新 tmp
的删除次数。每次 tmp
出现在堆顶时,这个标记都会被检查。如果标记表示该元素被删除(即删除计数大于零),这个元素将会从堆中弹出,同时更新它在 bigMark
中的标记。// 假设堆中有一个元素值为 5,现在我们要删除它:
int tmp = 5;
bigMark.merge(tmp, 1, Integer::sum); // 标记 tmp 为已删除
// 当我们后续从堆中得到堆顶元素时:
updateBigPeek(); // 在访问堆顶前更新堆
// updateBigPeek 的实现会检查堆顶元素是否被标记为已删除,如果是,就将其从堆中移除,
// 并在 bigMark 中更新其计数:
public void updateBigPeek() {
while (bigSiz > 0 && bigMark.getOrDefault(big.peek(), 0) > 0) {
int tmp = big.poll(); // 弹出堆顶元素
bigMark.merge(tmp, -1, Integer::sum); // 更新 bigMark,减少删除计数
}
}