力扣题解2576

大家好,欢迎来到无限大的频道。

今日继续给大家带来力扣题解。

题目描述:

给你一个下标从 0 开始的整数数组 nums 。

一开始,所有下标都没有被标记。你可以执行以下操作任意次:

选择两个 互不相同且未标记 的下标 i 和 j ,满足 2 * nums[i] <= nums[j] ,标记下标 i 和 j 。

请你执行上述操作任意次,返回 nums 中最多可以标记的下标数目。

解题思路:

算法的核心思路是:

  1. 对数组进行排序。

  2. 使用双指针的方式遍历排序后的数组,寻找符合条件的下标。

  3. 每当找到一对符合条件的下标时,就将它们标记

        分析题目和示例,我们以更深刻地理解下标标记的逻辑。原先整数数组的下标在被标记的之后,只是数据被标记,而非其固定的位置被标记,也就是说,是因为这个数据符合条件,从而标记该位置,而不是因为该位置符合条件,而去标记数据。

    题目的关键在于标记的是数组中元素的特性,而非其在数组中的位置固定性。(这个是我个人总结出来的,在很多的算法问题中都有所体现)

    ​所以当大家理解了这个条件之后,就可以把原先的数组进行排序,从而简化问题。

排序的经典函数如下

int compare(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}

之后便是在解决问题的函数中进行调用

    qsort(nums, numsSize, sizeof(int), compare);

在这之后,我们的数组num,就变成了一个非递减排列的数组。

排序之后,就是采用双指针遍历数组了(因为涉及两个变量的比较,所以我采用了双指针的方式)

int compare(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}
​
int maxMarkableIndices(int* nums, int size) {
    // 先对数组进行排序
    qsort(nums, size, sizeof(int), compare);
    
    int markedCount = 0;     // 标记的下标数量
    int i = 0;                // 指向较小数字的指针
    int j = 0;                // 指向较大数字的指针
​
    // 使用双指针遍历数组
    while (i < size && j < size) {
        // 找到一对符合条件的 (i, j)
        if (2 * nums[i] <= nums[j] && i != j) {
            markedCount += 2; // 每找到一对就标记两个下标
            i++;              // 移动指针 i
            j++;              // 移动指针 j
        } else {
            // 如果不符合条件,移动 j 指针
            j++;
        }
        
        // 如果 j 指针已经达到数组的末尾,上面条件再检查时需要重置 j,给下一个 i 对应的 j
        if (j == size && i < size) {
            j = i + 1; // 对下一个 i 找 j
        }
    }
​
    return markedCount; // 返回最多可以标记的下标数量
}
//双指针:使用两个指针 i 和 j,
//其中 i 指向较小的元素,j 指向较大的元素。
//每当找到符合条件的 (i, j) 对时,就将 markedCount 增加 2,
//并移动两个指针

但是写完之后发现,这个代码的时间复杂度过高,不在题目的考虑范围之内,所以在此基础上继续优化。

在长度为 n 的数组当中,最理想的情况下只会产生 n/2 个下标数目,因为题目要求“互不相同且未标记”

由此对数组进行排序后,我们将其一分为二,左侧的元素只与右侧的元素进行匹配,其余思路同上,得出代码

int compare(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}
​
int maxNumOfMarkedIndices(int* nums, int numsSize){
    qsort(nums, numsSize, sizeof(int), compare);
    int n = numsSize;
    int m = n / 2;
    int res = 0;
    for (int i = 0, j = m; i < m && j < n; i++) {
        while (j < n && 2 * nums[i] > nums[j]) {
            j++;
        }
        if (j < n) {
            res += 2;
            j++;
        }
    }
    return res;
}

时间复杂度:

1. 排序操作:

   - 使用 `qsort` 函数对数组进行排序,时间复杂度为 (O(n log n)),其中 (n) 是数组的大小。排序是整个算法中最耗时的部分。

2. 双指针遍历:

   - 在双指针的部分,外层循环的指针 `i` 最多会遍历 (n/2) 次(即左半边的元素),而内层循环的指针 `j` 也最多遍历 (n) 次。

   - 在最坏情况下,指针 `j` 可能会从中间走到数组的末尾,但每次内层的 `while` 循环只会向前移动 `j` 指针,因此每个元素最多只会被遍历一次。

   - 因此,双指针部分的时间复杂度为 (O(n))。

结合以上两部分,我们可以得出总的时间复杂度:

O(n log n) + O(n) = O(n log n)

 空间复杂度:

1. 排序的空间复杂度:

   - `qsort` 在排序过程中可能会使用额外的空间,具体取决于实现方式。通常情况下,快速排序的空间复杂度为 O(log n),用于递归调用的栈空间。

   - 但在某些实现中,可能会使用 O(n) 的额外空间来存储临时数组(例如归并排序),然而在这里我们使用的是快速排序。

2. 其他变量:

   - 除了排序外,其他变量(如 `n`, `m`, `res`, `i`, `j`)只占用常量空间 (O(1))。​

因此,整体的空间复杂度主要由排序决定:

O(log n)

 总结:

- 时间复杂度:(O(n log n))

- 空间复杂度:(O(log n))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值