ITPub博客

首页 > 应用开发 > IT综合 > LeetCode 1658详解:滑动窗口求减到零的最小操作数

LeetCode 1658详解:滑动窗口求减到零的最小操作数

投稿 IT综合 作者:bhluo 时间:2025-11-25 11:22:44 0 删除 编辑

**LeetCode 1658详解:滑动窗口求减到零的最小操作数**


LeetCode 1658题是一个典型的最优化问题,要求找到将x减到0的最小操作数。这个问题可以通过巧妙的数学转换和滑动窗口技术高效解决。


### 问题描述与理解


题目要求:给定一个整数数组nums和一个整数x,每次操作可以从数组的最左边或最右边移除一个元素,并减去该元素的值。返回将x恰好减到0的最小操作数,如果无法实现则返回-1。


```c

#include <stdio.h>

#include <stdlib.h>

#include <limits.h>


// 函数声明

int minOperations(int* nums, int numsSize, int x);

void testCases();


// 打印数组

void printArray(int* nums, int numsSize) {

    printf("[");

    for (int i = 0; i < numsSize; i++) {

        printf("%d", nums[i]);

        if (i < numsSize - 1) printf(", ");

    }

    printf("]");

}

```


### 问题分析与思路转换


关键思路:将问题转换为在数组中找到最长的子数组,使得子数组的和等于 `total_sum - x`。


```c

// 思路解析函数

void problemAnalysis() {

    printf("=== 问题分析 ===\n");

    printf("原始问题:从数组两端移除元素,使移除的元素和等于x\n");

    printf("等价转换:在数组中找到最长的子数组,使得子数组的和等于 total_sum - x\n");

    printf("原因:移除的元素和 = x ↔ 剩余的子数组和 = total_sum - x\n");

    printf("最小操作数 = 数组长度 - 最长子数组长度\n\n");

    

    printf("示例分析:\n");

    printf("nums = [1,1,4,2,3], x = 5\n");

    printf("total_sum = 11, target = 11 - 5 = 6\n");

    printf("最长子数组 [1,4,1] 和为6,长度=3\n");

    printf("最小操作数 = 5 - 3 = 2\n");

}

```


### 滑动窗口解法实现


滑动窗口是解决这类子数组和问题的经典方法。


```c

// 滑动窗口解法

int minOperations(int* nums, int numsSize, int x) {

    // 计算数组总和

    int total_sum <"7P.4961.HK">= 0;

    for (int i = 0; i < numsSize; i++) {

        total_sum += nums[i];

    }

    

    // 目标子数组和

    int target = total_sum - x;

    

    // 边界情况处理

    if (target < 0) return -1;      // x大于数组总和

    if (target == 0) return numsSize; // x等于数组总和,需要移除所有元素

    

    int left = 0;           // 窗口左边界

    int current_sum = 0;    // 当前窗口和

    int max_length = -1;    // 满足条件的最大子数组长度

    

    // 滑动窗口遍历

    for (int right = 0; right < numsSize; right++) {

        current_sum += nums[right];  // 扩展右边界

        

        // 收缩左边界直到窗口和小于等于target

        while (current_sum > target && left <= right) {

            current_sum -= nums[left];

            left++;

        }

        

        // 检查是否找到满足条件的子数组

        if (current_sum == target) {

            int current_length = right - left + 1;

            if (current_length > max_length) {

                max_length = current_length;

            }

        }

    }

    

    // 返回结果

    if (max_length == -1) {

        return -1;  // 没有找到满足条件的子数组

    } else {

        return numsSize - max_length;

    }

}

```


### 算法步骤详细解析


让我们逐步分析滑动窗口解法的执行过程:


```c

// 详细的步骤演示

void stepByStepDemo(int* nums, int numsSize, int x) {

    printf("\n=== 逐步演示 ===\n");

    printf("输入数组: ");

    printArray(nums, numsSize);

    printf(", x = %d\n", x);

    

    int total_sum = 0;

    for (int i = 0; i < numsSize; i++) {

        total_sum += nums[i];

    }

    printf("数组总和: %d\n", total_sum);

    

    int target = total_sum - x;

    printf("目标子数组和: %d\n", target);

    

    if (target < 0) {

        printf("x大于数组总和,无法实现\n");

        return;

    }

    

    int left = 0;

    int current_sum = 0;

    int max_length = -1;

    

    printf<"MX.0746.HK">("\n滑动窗口过程:\n");

    printf("右边界\t当前和\t左边界\t窗口状态\t操作\n");

    printf("------\t------\t------\t--------\t----\n");

    

    for (int right = 0; right < numsSize; right++) {

        current_sum += nums[right];

        printf("%d\t%d\t%d\t", right, current_sum, left);

        

        // 打印当前窗口

        printf("[");

        for (int i = left; i <= right; i++) {

            printf("%d", nums[i]);

            if (i < right) printf(",");

        }

        printf("]\t");

        

        // 收缩窗口

        while (current_sum > target && left <= right) {

            printf("收缩 ");

            current_sum -= nums[left];

            left++;

        }

        

        // 检查是否找到目标

        if (current_sum == target) {

            int current_length = right - left + 1;

            printf("找到目标! 长度=%d", current_length);

            if (current_length > max_length) {

                max_length = current_length;

                printf(" (新的最大值)");

            }

        } else {

            printf("继续");

        }

        printf("\n");

    }

    

    printf("\n结果: ");

    if (max_length == -1) {

        printf("-1 (无法实现)\n");

    } else {

        printf("%d (最小操作数 = %d - %d)\n", 

               numsSize - max_length, numsSize, max_length);

    }

}

```


### 完整测试用例


全面的测试用例验证算法正确性:


```c

// 测试用例函数

void testCases() {

    printf("=== LeetCode 1658 测试用例 ===\n\n");

    

    // 测试用例1:示例情况

    {

        int nums1[] = {1, 1, 4, 2, 3};

        int x1 = 5;

        printf("测试用例1: ");

        printArray(nums1, 5);

        printf(", x = %d\n", x1);

        int result1 = minOperations(nums1, 5, x1);

        printf("预期结果: 2, 实际结果: %d\n", result1);

        stepByStepDemo(nums1, 5, x1);

    }

    

    // 测试用例2:无法实现的情况

    {

        int nums2[] = {5, 6, 7, 8, 9};

        int x2 = 4;

        printf("\n测试用例2: ");

        printArray(nums2, 5);

        printf(", x = %d\n", x2);

        int result2 = minOperations(nums2, 5, x2);

        printf("预期结果: -1, 实际结果: %d\n", result2);

    }

    

    // 测试用例3:从单边移除

    {

        int nums3[] = {3, 2, 20, 1, 1, 3};

        int x3 = 10;

        printf("\n测试用例3: ");

        printArray(nums3, 6);

        printf(", x = %d\n", x3);

        int result3<"9B.8412.HK"> = minOperations(nums3, 6, x3);

        printf("预期结果: 5, 实际结果: %d\n", result3);

        stepByStepDemo(nums3, 6, x3);

    }

    

    // 测试用例4:需要从两边移除

    {

        int nums4[] = {1, 1, 1, 1, 1};

        int x4 = 5;

        printf("\n测试用例4: ");

        printArray(nums4, 5);

        printf(", x = %d\n", x4);

        int result4 = minOperations(nums4, 5, x4);

        printf("预期结果: 5, 实际结果: %d\n", result4);

    }

    

    // 测试用例5:空数组情况

    {

        int nums5[] = {};

        int x5 = 1;

        printf("\n测试用例5: 空数组, x = %d\n", x5);

        int result5 = minOperations(nums5, 0, x5);

        printf("预期结果: -1, 实际结果: %d\n", result5);

    }

    

    // 测试用例6:x等于0

    {

        int nums6[] = {1, 2, 3};

        int x6 = 0;

        printf("\n测试用例6: ");

        printArray(nums6, 3);

        printf(", x = %d\n", x6);

        int result6 = minOperations(nums6, 3, x6);

        printf("预期结果: 0, 实际结果: %d\n", result6);

    }

}

```


### 算法复杂度分析


```c

// 复杂度分析函数

void complexityAnalysis() {

    printf("\n=== 算法复杂度分析 ===\n");

    

    printf("时间复杂度: O(n)\n");

    printf("  - 计算数组总和: O(n)\n");

    printf("  - 滑动窗口遍历: O(n) - 每个元素最多被访问两次\n");

    printf("  - 总体: O(n)\n\n");

    

    printf("空间复杂度: O(1)\n");

    printf("  - 只使用了常数级别的额外空间\n");

    printf("  - 变量: total_sum, target, left, current_sum, max_length\n\n");

    

    printf("对比其他方法:\n");

    printf("1. 暴力解法: O(2^n) - 检查所有可能的移除组合\n");

    printf("2. 动态规划: O(n*x) - 可能超时且空间复杂度高\n");

    printf("3. 滑动窗口: O(n)   - 最优解\n");

}

```


### 边界情况处理


详细处理各种边界情况:


```c

// 边界情况处理函数

void handleEdgeCases() {

    printf("\n=== 边界情况处理 ===\n");

    

    // 情况1:x大于数组总和

    {

        int nums[] = {1, 2, 3};

        int x = 10;

        printf("情况1 - x大于数组总和: ");

        int result = minOperations(nums, 3, x);

        printf("结果: %d (正确应为-1)\n", result);

    }

    

    // 情况2:x等于数组总和

    {

        int nums[] = {1, 2, 3, 4};

        int x = 10;

        printf("情况2 - x等于数组总和: ");

        int result = minOperations(nums, 4, x);

        printf("结果: %d (正确应为4)\n", result);

    }

    

    // 情况3:空数组

    {

        int nums[] = {};

        int x = 5;

        printf("情况3 - 空数组: ");

        int result = minOperations(nums, 0, x);

        printf("结果: %d (正确应为-1)\n", result);

    }

    

    // 情况4:x等于0

    {

        int nums[] = {1, 2, 3};

        int x = 0;

        printf("情况4 - x等于0: ");

        int result = minOperations(nums, 3, x);

        printf("结果: %d (正确应为0)\n", result);

    }

    

    // 情况5:单个元素

    {

        int nums[] = {5};

        int x = 5;

        printf("情况5 - 单个元素且x等于该元素: ");

        int result = minOperations(nums, 1, x);

        printf("结果: %d (正确应为1)\n", result);

    }

}

```


### 滑动窗口技巧总结


```c

// 滑动窗口技巧总结

void slidingWindowTechniques<"2T.1853.HK">() {

    printf("\n=== 滑动窗口技巧总结 ===\n");

    

    printf("适用场景:\n");

    printf("1. 子数组/子字符串问题\n");

    printf("2. 需要找到满足某些条件的连续子序列\n");

    printf("3. 涉及数组和、乘积等累积操作的问题\n\n");

    

    printf("基本模板:\n");

    printf("int left = 0;\n");

    printf("for (int right = 0; right < n; right++) {\n");

    printf("    // 扩展右边界\n");

    printf("    // 更新状态变量\n");

    printf("    \n");

    printf("    while (/* 不满足条件 */) {\n");

    printf("        // 收缩左边界\n");

    printf("        // 更新状态变量\n");

    printf("    }\n");

    printf("    \n");

    printf("    // 检查是否找到解\n");

    printf("}\n\n");

    

    printf("本题的特殊转换:\n");

    printf("原问题: 移除两端元素和 = x\n");

    printf("转换后: 中间子数组和 = total - x\n");

    printf("最小操作数 = 总长度 - 最长子数组长度\n");

}

```


### 性能优化建议


```c

// 性能优化建议

void performanceOptimization() {

    printf("\n=== 性能优化建议 ===\n");

    

    printf("1. 提前终止检查:\n");

    printf("   if (target < 0) return -1;\n");

    printf("   if (target == 0) return numsSize;\n\n");

    

    printf("2. 使用局部变量:\n");

    printf("   将频繁访问的数组成员存储在局部变量中\n\n");

    

    printf("3. 避免不必要的计算:\n");

    printf("   只在必要时更新最大值\n\n");

    

    printf("4. 边界条件优先处理:\n");

    printf("   在主要逻辑之前处理明显的边界情况\n");

}


// 优化版本实现

int minOperationsOptimized(int* nums, int numsSize, int x) {

    // 快速边界检查

    if (numsSize == 0)<"m.6370.HK"> return x == 0 ? 0 : -1;

    

    // 计算总和

    int total_sum = 0;

    for (int i = 0; i < numsSize; i++) {

        total_sum += nums[i];

    }

    

    int target = total_sum - x;

    

    // 快速边界返回

    if (target < 0) return -1;

    if (target == 0) return numsSize;

    

    int left = 0;

    int current_sum = 0;

    int max_length = -1;

    

    for (int right = 0; right < numsSize; right++) {

        current_sum += nums[right];

        

        // 快速收缩

        while (current_sum > target && left <= right) {

            current_sum -= nums[left++];

        }

        

        if (current_sum == target) {

            int current_length = right - left + 1;

            if (current_length > max_length) {

                max_length = current_length;

            }

        }

    }

    

    return max_length == -1 ? -1 : numsSize - max_length;

}

```


### 完整可运行程序


```c

// 主函数 - 整合所有功能

int main() {

    printf("LeetCode 1658 - 将 x 减到 0 的最小操作数\n");

    printf("=========================================\n\n");

    

    // 问题分析

    problemAnalysis();

    

    // 测试用例

    testCases();

    

    // 算法分析

    complexityAnalysis();

    

    // 边界情况

    handleEdgeCases();

    

    // 技巧总结

    slidingWindowTechniques();

    

    // 性能优化

    performanceOptimization();

    

    printf("\n=== 程序结束 ===\n");

    return 0;

}

```


### 总结


LeetCode 1658题的滑动窗口解法展示了算法思维的精妙之处:


1. **问题转换**:将两端移除问题转换为寻找中间子数组问题

2. **滑动窗口**:高效找到满足条件的最大子数组

3. **数学关系**:`操作数 = 总长度 - 子数组长度`

4. **边界处理**:全面考虑各种特殊情况


这种方法的时间复杂度为O(n),空间复杂度为O(1),是解决该问题的最优方案。掌握这种问题转换思维和滑动窗口技巧,对于解决类似的数组优化问题具有重要意义。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3103322/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2015-11-01

  • 博文量
    895
  • 访问量
    933780