C语言中的窗口滑动技术
学习文章:C语言中的窗口滑动技术
滑动窗口法
- C语言中的窗口滑动技术
循环几乎是每个复杂问题的一部分。太多的循环/嵌套循环会增加所需的时间,从而增加程序的时间复杂性。窗口滑动技术是一种计算技术,用于减少程序中使用的嵌套循环的数量,通过用单个循环代替嵌套循环来提高程序的效率。
如果你熟悉计算机网络中的滑动窗口协议,这种技术也很类似,本教程通过不同的例子解释了这种技术的使用方法。
一般来说,当我们使用这样的嵌套循环时 。
for(i = 1; i <= n; i++)
{
for(j = i; j < k; j++)
{
}
}
迭代: 外循环执行\(n\)次;每执行一次外循环,内循环就执行\((k-i)\)次。执行整个循环所需的平均时间大约为$O(N^2) $。因此,开发人员不建议使用循环。
让我们举一个例子来清楚地理解这个概念 。
假设我们要找到一个数组中\(’k’\)个连续元素的最大和,用户提供\(k\)值:
首先,如果我们使用传统的方法,对于数组中的每个元素\(i\),我们将从\(i+1\)到\(n-1\)遍历数组,其中\(n\)是数组的大小,我们需要对每个元素都这样做,然后比较总和,得到最大总和。
- 暴力方法
#include<stdio.h>
int main()
{
/*
找到一个数组中’k’个连续元素的最大和
*/
int n, size, sum=0, max = 0, j;
printf("Enter the size of the array: ");
scanf("%d", &n);
int arr[n], i;
printf("Enter the elements of the array: ");
for(i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
printf("Enter the size of sub-array: ");
scanf("%d", &size);
for(i = 0; i < (n - size) + 1; i++)
{
/*
其实也是滑动窗口的思想
*/
sum = 0;
for(j = i; j < i + size; j++)
{
sum = sum + arr[j];
}
if(sum > max)
{
max = sum;
}
}
printf("The maximum sum of %d consecutive elements of the array: %d\n", size, max);
}
输出 :
Enter the size of the array: 10
Enter the elements of the array: 8 2 1 7 3 2 5 8 1 3
Enter the size of the sub-array: 3
The maximum sum of 3 consecutive elements of the array: 20
现在,滑动窗口技术来了
这里的概念是,我们创建一个大小为\(k\)的窗口,我们将通过一个单位指数不断地滑动它。在这里,窗口并不是什么技术术语。我们不是像在循环中那样使用一个单一的值,而是在每次迭代中同时使用多个元素。
比如说给定一个大小为10的数组:

假设我们需要3个连续索引的最大和,创建一个3个大小的窗口,并在整个数组中不断滑动(遍历)它。这里有一个形象的表示。
迭代1:

迭代2 :

迭代3:

迭代4:

迭代5:

迭代6:

迭代7:

迭代8:

- 使用这种方法,将没有内循环,一个单循环的迭代次数将是\((n – k + 1)\),在这种情况下是8。
- 所以,滑动窗口是一种用于减少嵌套循环的技术,用一个单循环代替它,以减少总的时间复杂性。
- 请注意,在每次迭代中,当窗口滑动到下一个索引时, 我们都会删除前一个窗口的第一个元素,并添加一个新的元素,即下一个继任索引。
下面是代码:
#include <stdio.h>
int maxsum(int a[], int k, int n);
int main()
{
int n, i, k;
printf("Enter the size of the array: ");
scanf("%d", &n);
int arr[n];
printf("Enter the elements: ");
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
printf("Enter the value of k: ");
scanf("%d", &k);
int max = maxsum(arr, k, n);
printf("The maximum sum of %d consecutive elements in the array: %d\n", k, max);
}
int maxsum(int a[], int k, int n)
{
int i, sum, maxm = 0;
for (i = 0; i < k; i++)
{
maxm = maxm + a[i]; //记录第一个窗口元素和
}
sum = maxm; //初始化sum
for (i = k; i < n; i++)
{
sum += a[i] - a[i - k]; //当窗口滑动到下一个索引时, 我们都会删除前一个窗口的第一个元素,并添加一个新的元素
if (sum > maxm)
{
maxm = sum;
}
}
return maxm;
}
输出:
Enter the size of the array: 10
Enter the elements: 8 2 1 7 3 2 5 8 1 3
Enter the value of k: 3
The maximum sum of 3 consecutive elements in the array: 15
- 暴力方法在两个嵌套循环中需要\(O(k*n)\)时间。
- 通过使用滑动窗口技术,时间复杂度降低到\(O(n)\)。
以下是将该技术应用于手头任何问题的步骤:
- 首先,我们必须看到,窗口的大小是恒定的,不应该改变。我们可以只对这样的问题使用该技术。
- 在确保窗口大小没有变化后,计算第一个窗口的结果,与数组其他部分的计算结果进行比较。
- 现在,用一个循环来逐个滑动窗口的索引,直到最后,不断更新所需的值。
题目1

方法
- 双层循环【暴力破解】
我们需要两层循环,第一层循环遍历字符串、并且记录第二层循环开始的位置。
①创建一个新的数组;
②从第一个字符开始遍历,不重复的字符就将它放到新的数组中,遇到重复的就停止,计算该子串的长度;
③开始下一次循环,直到遍历到字符串结束。

代码:C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int lengthOfLongestSubstring(char *s)
{
if (s == NULL) // 判断空指针
return 0;
int len = strlen(s);
int maxlength = 0; // 记录最长长度
int i = 0;
for (i = 0; i < len; i++) // 外层循环遍历字符串
{
int curlength = 0; // 记录每一次的长度
char *tmp = (char *)calloc(len, 1); // 用来存放子串
int t = 0;
int j = i; // 从i开始遍历
while (s[j] && !strchr(tmp, s[j])) // 要防止子串出现越界情况
{
tmp[t++] = s[j++]; // 字符不存在就保存
}
curlength = t; // 子串的长度
if (maxlength < curlength) // 找最长
{
maxlength = curlength;
}
free(tmp);
}
return maxlength;
}
int main()
{
char *s = "abcabcdb";
int k = lengthOfLongestSubstring(s);
printf("k=%d\n", k);
return 0;
}
- 滑动窗口法
上面暴力破解的时间复杂度很高:\(O(n^2)\),我们可以发现内循环每进行一次都会有数据被重复移动,这个操作消耗了大量的时间,在「C语言中的窗口滑动技术」中,我们了解滑动窗口法可以减少循环次数,提升程序效率,下面我们通过滑动窗口的方法来进行化简。

代码:C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int lengthOfLongestSubstring(char *s)
{
if (s == NULL)
return 0;
int len = strlen(s);
char *tmp = (char *)calloc(len, 1);
int right = 0; // 有效字符串长度
int maxlength = 0; // 记录最长长度
int i = 0;
for (i = 0; i < len; i++)
{
char *p = strchr(tmp, s[i]);
tmp[right] = s[i]; //先插入tmp
right++;
if (p) // 如果该字符已存在,就跳到该位置
{
right = right - (p - tmp) - 1; // 更新长度,指针相减(p - tmp)表示指针指向位置相隔的元素个数
char *eff = (char *)malloc(right);
// 跳过中间重复的部分
memcpy(eff, p + 1, right); // 先将有无重复的字符串保存
memset(tmp, '0', (p - tmp + 1 + right)); // 清空原字符串
memcpy(tmp, eff, right); // 将新字符串重新拷贝回去
free(eff);
}
if (maxlength < right) // 找最长
{
maxlength = right;
}
}
return maxlength;
}
int main()
{
char *s = "abcabcdb";
int k = lengthOfLongestSubstring(s);
printf("k=%d\n", k);
return 0;
}
- 滑动窗口法(改进)
上面我们通过字符的复制和删除来省略了多次遍历的过程,不过,我们在删除字符的时候需要将后面全部的字符都往前移,这样也浪费一些时间,那么我们是否可以通过找到最长子串的具体位置,来省去删除字符的过程呢?答案肯定是可以的,下面就让我们开始实际操作。

程序:C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int lengthOfLongestSubstring(char* s) {
if (s == NULL)
return 0;
int len = strlen(s);
char* tmp = (char*)calloc(len, 1);
int right = 0; // 当前字符串长度
int left = 0; // 控制子串起始位置
int maxlength = 0; // 记录最长长度
for (int i = 0; i < len; i++)
{
char* p = strchr(tmp + left, s[i]);//这里需要注意的一点是字符必须先判断后复制
tmp[right] = s[i]; //逐个遍历,存储
right++;
if (p) // 如果该字符已存在,就跳到前面重复字符的下一个位置
{
left = p - tmp + 1; //p指针后一位
}
int curlength = right - left; // 当前子串长度
if (maxlength < curlength)// 找最长
{
maxlength = curlength;
}
}
free(tmp);
return maxlength;
}
int main()
{
char *s = "abcabcdb";
int k = lengthOfLongestSubstring(s);
printf("k=%d\n", k);
return 0;
}
题目2

方法
题目要求找一段连续子数组,并且该子数组的和sum要\(>=\)target,那么我们首先就是要遍历数组求和,直到\(sum>=target\)。
下面我们直接使用滑动窗口的思想进行破题: 那么我们在这里会遇到哪些特殊情况呢?
特例1:整个数组的和都小于target,不过当窗口的右边界越界的时候我们已经跳出循环了,所以也就不需要再做特殊处理;
特例2:有一个数组元素特别大,我们减去前面的一个元素后,sum依然大于target,这时我们就需要先将sum的值不断减小之后才能继续扩大右边界。
程序:C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int minSubArrayLen(int target, int *nums, int numsSize)
{
int minlen = numsSize + 1; // 最短长度
int sum = 0; // 求和
int left = 0; // 记录左边界
int right = 0;
while (right < numsSize) // 右边界改变
{
sum += nums[right++]; // 这里需要更新right,否则结果会少一
while (sum >= target) // 特例1
{
minlen = minlen > right - left ? right - left : minlen;
sum -= nums[left++]; // 左下标需要右移
}
}
return minlen == numsSize + 1 ? 0 : minlen; // 特例2
}
int main()
{
int target = 7, nums[] = {2, 3, 1, 2, 4, 3};
int k = minSubArrayLen(target, nums, sizeof(nums) / sizeof(nums[0]));
printf("k=%d\n", k);
return 0;
}
C语言中的窗口滑动技术的更多相关文章
- sed修炼系列(三):sed高级应用之实现窗口滑动技术
html { font-family: sans-serif } body { margin: 0 } article,aside,details,figcaption,figure,footer,h ...
- C语言中的内存压缩技术
C语言中的内存压缩技术 前言 在整个研究生阶段我都在参与一个LTE协议栈实现的项目,在这个项目中,我们利用一个自己编写的有限状态机框架将协议栈中每一层实现为一个内核模块.我们知道,在编写内核代码时需要 ...
- TCP的窗口滑动机制
TCP的滑动窗口主要有两个作用,一是提供TCP的可靠性,二是提供TCP的流控特性.同时滑动窗口机制还体现了TCP面向字节流的设计思路. 可靠:对发送的数据进行确认 流控制:窗口大小随链路变化. 一.t ...
- Java语言中使用OpenMP
从去年年中,开始学习Java,主要是维护公司用Java编写的服务器软件.目前,该服务器软件遇到一个问题,在下载大文件时,如果同时下载的用户很多, 服务器软件工作会出现异常,有的用户无法下载.服务器硬件 ...
- 如何在Exe和BPL插件中实现公共变量共享及窗口溶入技术Demo源码
如何在Exe和BPL插件中实现公共变量共享及窗口溶入技术Demo源码 1.Delphi编译方式介绍: 当我们在开发一个常规应用程序时,Delphi可以让我们用两种方式使用VCL,一种是把VCL中的申明 ...
- 8-Flink中的窗口
戳更多文章: 1-Flink入门 2-本地环境搭建&构建第一个Flink应用 3-DataSet API 4-DataSteam API 5-集群部署 6-分布式缓存 7-重启策略 8-Fli ...
- 在C语言中函数及其调用过程
目录 函数 C语言中的变参函数 函数的本质是什么 内存区域的区分技巧 函数的调用过程 栈帧的概念 调用过程细节 按照约定传参 函数 如果一个函数有声明没实现,那么就会出现链接错误: 以上代码会出现链接 ...
- Java语言中的面向对象特性总结
Java语言中的面向对象特性 (总结得不错) [课前思考] 1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类? 2. 面向对象编程的特性有哪三个?它们各自又有哪些特性? 3. 你知 ...
- 【转】代码中特殊的注释技术——TODO、FIXME和XXX的用处
(转自:http://blog.csdn.net/reille/article/details/7161942) 作者:reille 本博客网址:http://blog.csdn.net/reille ...
- C语言中 *.c和*.h文件的区别!
C语言中 *.c和*.h文件的区别! http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/ ...
随机推荐
- KindleVocab 教程,Kindle导出查词记录成文本文档,Kindle导出查询单词记录导入Anki
程序功能 因本人使用Kindle Mate导出觉得复杂,特意写了个自用的导出程序(有linux版本和win两个版本). 所以 KindleVocab 只有一个作用:导出Kindle查询过的生词和生词所 ...
- PTA题目集4~6的总结性Blog
· 前言 本次的三个作业,由答题判题程序- 4.家居强电电路模拟程序- 1.家居强电电路模拟程序 -2组成. 答题判题程序-4是对前三次判题程序的最后升级,设计多个子类继承于基础题类来实现对每种题型的 ...
- python argparse变量到class变量的转换代码
github上的项目总喜欢使用argparse + bash来运行,这对于快速运行一个项目来说可能有好处,但在debug的时候是很难受的.因为我们需要在.sh文件中修改传入参数,并且不能使用jupyt ...
- JAVA 注解示例 详解
注解(Annotation) 为我们在代码中天界信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解析注解 来使用这些数据). 注解的语法比较简单,除了@符号的使用以外, ...
- JDK中的动态代理
江苏 无锡 缪小东 写到代理模式这章,不得不提到JDK中的动态代理,它是java语言自身对动态代理的支持,类似于JDK中在java.util包中提供Observable类和Observer接口提供对观 ...
- 一、STM32F103C8T6--GPIO
STM32f103c8t6 32位Cortex-M3内核 RISC处理器,最高主频72MHZ,Flash:64KB,SRAM:20KB 片上外设: I/O端口: 多达37个GPIO引脚(支持复用功能) ...
- elementUI中的级联选择器,默认赋值不起作用
今天遇到再使用element的级联选择器功能的时候,是多选,默认赋值不起作用. 后来查到是因为少了multiple属性,但是multiple属性要放在props绑定的对象中,而不是直接放在标签上 &l ...
- RocketMQ系列1:基础介绍
★消息队列16篇 1 认识RocketMQ RocketMQ是一款基于Java开发的分布式消息中间件,它以其高性能.高可靠性.高实时性以及分布式特性而广受好评. 它支持事务消息.顺序消息.批量消息.定 ...
- apisix~路由前缀的正则匹配
参考:https://apisix.apache.org/zh/docs/apisix/FAQ/ 在你提供的 Apache APISIX 路由配置中,vars 字段用于定义一些变量匹配规则.具体来说, ...
- 以下哪一项是对CSMA/CA和CSMA/CD LAN控制通用的CSMA方法的适当描述?
A. 检测载波信号并控制数据传输. B. 获得具有传输权的消息(令牌)的终端传输数据. C. 如果在数据传输过程中发生冲突,立即重新发送. D. 即使在使用传输线时也可以传输数据. = ...