算法优化:LeetCode第122场双周赛解题策略与技巧_将数组分成最小总代价的子数组 ii

前端框架

前端框架太多了,真的学不动了,别慌,其实对于前端的三大马车,Angular、React、Vue 只要把其中一种框架学明白,底层原理实现,其他两个学起来不会很吃力,这也取决于你以后就职的公司要求你会哪一个框架了,当然,会的越多越好,但是往往每个人的时间是有限的,对于自学的学生,或者即将面试找工作的人,当然要选择一门框架深挖原理。

以 Vue 为例,我整理了如下的面试题。

Vue部分截图

如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

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;
}

}


## 100181. 将数组分成最小总代价的子数组 I


当 x<y 时,x  mod  y=x 因此如果选择数组中的两个不相等的元素,则可以删除较大元素,保留较小元素。



> 
> 用 minNum表示数组 nums 中的最小元素
> 
> 
> 用 minCount 表示数组 nums 中的 minNum 的出现次数
> 
> 
> 分别考虑 minCount=1 和 minCount>1 的情况。
> 
> 
> 


* 如果 minCount=1



> 
> 则可以每次选择 minNum  和另一个元素,由于 minNum 一定小于另一个元素,因此总是可以删除另一个元素,保留 minNum,直到数组 nums  中只有一个元素 minNum,数组 nums的最小长度是 1。
> 
> 
> 


* 如果 minCount>1



> 
> 1. 如果数组 nums 中存在一个元素 num 满足 num mod minNum≠0 ,记 newNum= (num  mod  minNu)
> 2. 则必有 0<newNum<minNum 可以在一次操作中选择 num 和 minNum ,删除这两个元素并添加元素newNum。
> 3. 由于 newNum < minNum ,因此 newNum 成为数组 nums 中的新的最小元素且最小元素唯一,之后可以每次选择 newNum 和另一个元素,其效果是删除另一个元素,保留 newNum ,直到数组 nums 中只有一个元素 newNum ,数组 nums 的最小长度是 1
> 4. 如果数组 nums 中不存在元素 num 满足 num  mod  minNum ≠ 0 ,则无法通过操作得到小于 minNum 的元素,因此在将所有大于 minNu 的元素删除之后,剩余 minCount 个元素 minNum 。由于每次可以选择 2 个元素 minNum 执行操作得到元素 0 无法继续操作,因此 minCount 个元素 minNum 的最多操作次数可以根据count\_min的奇偶性判断
> 
> 
> 



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

### 三、大小顶堆、延迟删除、滑动窗口


这道题目的思路是利用滑动窗口结合两个堆(优先队列)来找出序列中指定数量(`k-1`)的最小数的和,它们是从序列的某个区间(该区间长度由`dist`决定)中选择出来的。这个序列中的第一个数 (`nums[0]`) 是固定的,所以总是被包含在结果中。


下面是详细的解题步骤:


1. **初始化两个堆**:一个小顶堆 `small` 来保存当前窗口中的最小的 `k-2` 个数,以及一个大顶堆 `big` 来保存窗口内剩余的数。
2. **使用 HashMap 进行延迟删除**:为了实现有效地从堆中删除特定的非堆顶元素,创建两个 `HashMap` (`smallMark` 和 `bigMark`) 来标记堆中元素是否已经被 "删除"。该删除实际上是延迟执行的,即直到这个元素出现在堆顶时才真正被排除。
3. **填充初始窗口**:从 `nums` 数组的第二个元素开始,将 `dist+1` 长度内的元素放入 `big` 堆。
4. **从 `big` 中取出 `k-2` 个最小元素**:这 `k-2` 个元素是将要加入 `small` 的,记录这 `k-2` 个数的和作为窗口的当前总和。
5. **滑动窗口**:在数组中滑动窗口,并动态维护这两个堆以保持正确的最小 `k-2` 个数的总和。
6. **调整堆**:当窗口滑动导致元素移出窗口时,更新 `small` 堆以保持其有效性,并进行相应的调整。如果移出的元素当前在 `small` 中,则它需要被标记为已删除;如果它在 `big` 中,则直接标记为已删除。
7. **处理新进入窗口的元素**:窗口滑动时,可能会有新的元素进入。这些新元素需要加入到 `big` 堆中。从 `big` 中取出的最小元素会放入 `small` 堆,并更新当前窗口总和(`sum`)。
8. **求解最终结果**:在滑动窗口过程中,每次窗口更新后,计算此时的窗口总和加上 `nums[0]`(固定加入)。所有窗口中总和的最小值即为所求问题的答案。



class Solution {
// small是小顶堆 维护前k-2小的数
// big是大顶堆 维护窗口内剩下的数

PriorityQueue<Integer> small, big;
// 标记当前元素是否已经被删除以及被删除的个数
HashMap<Integer, Integer> smallMark, bigMark;
// samll和big当前未被删除的元素个数
int smallSiz, bigSiz;
long sum;

public long minimumCost(int[] nums, int k, int dist) {
    // k个 除掉第一个 还要选k-1个
    // 枚举第2个 nums[i] nums[i+1]... nums[i+dist] 里选k-2个最小的数
    // nums[i+1] nums[i+k-2]
    small = new PriorityQueue<>(Collections.reverseOrder());
    smallSiz = 0;
    smallMark = new HashMap<>();
    big = new PriorityQueue<>();
    bigSiz = 0;
    bigMark = new HashMap<>();
    // 当前小顶堆的和 也就是前k-2小的和
    sum = 0;
    int n = nums.length;
    // 把nums[1+1]...nums[1+dist]里的数加入到big里
    for (int i = 2; i <= Math.min(n-1, dist+1); i++) {
        big.add(nums[i]);
        bigSiz++;
    }
    // 取出前k-2小的数放入small
    for (int i = 0; i < k-2; i++ ) {
        int tmp = big.poll();
        bigSiz--;
        sum += tmp;
        small.add(tmp);
        smallSiz++;
    }
    long res = nums[0] + nums[1] + sum;
    // 枚举第二个数的位置
    // 枚举的位置从i-1变成i时 nums[i]离开了窗口 nums[i+dist]进入了窗口
    for (int i = 2; i + k-2 < n; i++) {
        // 移除nums[i]
        // 因为要访问small.peek() 为了确保small.peek()是未被删除的元素 需要先更新small
        updateSmallPeek();
        // nums[i]在前k-2小里
        if (smallSiz > 0 && small.peek() >= nums[i]) {
            // 因为nums[i] 是可能小于small.peek()的 我们没法直接删除nums[i] 所以要标记一下
            smallMark.merge(nums[i], 1, Integer::sum);
            // 从small里删除nums[i]
            smallSiz--;
            sum -= nums[i];
        } else {
            // nums[i]不在前k-2小里 
            bigMark.merge(nums[i], 1, Integer::sum);
            bigSiz--;
            // 这里是为了使得small的数量变成k-3个 也就是还差一个才够k-2个
            // 是为了方便后面的操作
            // 从small里选一个放到big里
            int tmp = small.poll();

最后

正值招聘旺季,很多小伙伴都询问我有没有前端方面的面试题!

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端资料图.PNG


// 从small里选一个放到big里
int tmp = small.poll();

最后

正值招聘旺季,很多小伙伴都询问我有没有前端方面的面试题!

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

[外链图片转存中…(img-Sd0dqUHK-1715273172741)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值