1.3 LeetCode总结(线性表)_滑动窗口

编程总结

每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧
双指针一定要想好思路先,切记先陷入到题里面去了,难以拔出来。
多练习吧
双指针尤其要注意用例出现超时;
切记直接暴力两层循环解题,能用左右指针使用双指针解题。

手法是:

  1. 初始化 某个[i, j)区间
  2. 保持 某个[i,j)区间
  3. 终止 找终止条件
    双指针的三大步骤:初始化,保持,终止牢记 牢记

在这里插入图片描述

最长的指定瑕疵度的元音子串

定义:开头和结尾都是元音字母(aeiouAEIOU)的字符串为 元音字符串 ,其中混杂的非元音字母数量为其 瑕疵度 。比如:

“a” 、 “aa”是元音字符串,其瑕疵度都为0
“aiur”不是元音字符串(结尾不是元音字符)
“abira”是元音字符串,其瑕疵度为2
给定一个字符串,请找出指定瑕疵度的最长元音字符子串,并输出其长度,如果找不到满足条件的元音字符子串,输出0。
子串:字符串中任意个连续的字符组成的子序列称为该字符串的子串

思路:非常经典的双指针玩法, 稍有不慎容易陷入思维黑洞无法自拔…理清思路

  1. 右移窗口的过程中,遇到非元音字母,瑕疵度增加
  2. 右移导致瑕疵度过大,此时左侧窗口收缩直到重新满足瑕疵度
  3. 当前窗口满足头尾元音,瑕疵度要求的条件

输入样例 3 复制
1
aabeebuu
输出样例 3
5
提示样例 3
满足条件的最长元音字符子串有两个,分别为aabee和eebuu,长度为5

int GetLongestFlawedVowelSubstrLen(int flaw, char *str)
{
    int i;
    char vowel[NUM_10] = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'};
    int  vowelNum[128] = {0};
    int l = 0;
    int r = 0;
    int len = strlen(str);
    int flawValue = 0;
    int maxLen = 0;

    // 计算元音字母
    for (i = 0; i < NUM_10; i++) {
        vowelNum[(vowel[i] - 'A')] = 1;
    }

    while (r < len) {
        if (vowelNum[str[r] - 'A'] == 0) {
            flawValue++; // 右移窗口的过程中,遇到非元音字母,瑕疵度增加
        }
        while (flawValue > flaw) { // 右移导致瑕疵度过大,此时左侧窗口收缩直到重新满足瑕疵度
            if (vowelNum[str[l] - 'A'] == 0) {
                flawValue--;
            }
            l++;
        }
        // 当前窗口满足头尾元音,瑕疵度要求的条件
        if ((vowelNum[str[r] - 'A'] == 1) && (vowelNum[str[l] - 'A'] == 1) && (flawValue == flaw)) {
            maxLen = fmax(r - l + 1, maxLen);
        }
        r++;
    }

    return maxLen;
}

26.删除有序数组中的重复项

在这里插入图片描述
在这里插入图片描述

思路,这题在空间不做要求的情况下可以使用桶排序,下面介绍 O(1) 空间复杂度的解法(已知是有序数组):

  1. 初始化:为了保证区间 [0…j) (注意这里是左闭右开区间)里没有重复元素。初始的时候,i = 0 ,下标为 0 的位置只有一个数,区间 [0…j) 一定不会出现重复,这件事情表示为区间 [0…0]
    没有重复数字,即区间 [0…1) 没有重复数字,因此 j 初始化的时候需要等于 1。
  2. 保持:遇到和 pre 指向的数字相等元素,i++ 直接看到下一个元素,如果 nums[i] != pre ,表示程序看到了第 1 个不重复的数字,此时需要赋值 nums[j] = nums[i] 和 pre = nums[j] ,然后让 j++ 指向下一个需要赋值的下标;
  3. 终止:循环结束以后 i = len ,程序看完了输入数组的所有元素,此时区间 [0…j) 里没有重复元素,它的长度为 j ,返回。
// Return :j 返回删除后数组的新长度
int removeDuplicates(int *nums, int numsSize)
{
	int len = numsSize;
	if (len < 2) {
		return len;
	}
	int j = 1;
	int pre = nums[0];
	for (int i = 1; i < len; i++) {
		if (nums[i] != pre) {
			nums[j] = nums[i];
			pre = nums[j];
			j++;
		}
	}

	return j;
}

674. 最长连续递增序列

在这里插入图片描述

思路:像这种带有最长***(最长元音,最长递增序列等),很有可能就可以使用双指针来解题,套用双指针三大手法即可。

int findLengthOfLCIS(int *nums, int numsSize)
{
	int i = 0;
	int j = 0;
	int res = 1;
	int result = 0;

	if (numsSize < 2) {
		return numsSize;
	}
	// 1.初始化[0, 1)严格单调递增
	// 2.保持 [i,j) 循环不变量 [i...j) 严格单调递增
	// 3.判断结束条件 j < numsSize.
	for (j = 1; j < numsSize; j++) {
		if (nums[j] <= nums[i]) { // 非单调递增序列
			res = j - i;          // res表示递增的元素个数
			i = j;                // 更新i
			result = fmax(res, result);
			res = 1;
		}
		else {                    // 单调递增序列
			nums[i] = nums[j];
			res++;
			result = fmax(res, result);
		}
	}

	return result;
}

27. 移除元素

在这里插入图片描述
在这里插入图片描述

int removeElement(int *nums, int numsSize, int val)
{
	int j = 0;
	int i = 0;
	
	// 循环不变量: [i, j)不存在val的值. 
	for (j = 0; j < numsSize; j++) {
		if (nums[j] != val) {
			nums[i] = nums[j];
			i++;
		}
	}

	return i;
}

求差值的组合

给定一个数组,每个元素的值是唯一的,找出其中两个元素相减等于给定差值 diff 的所有不同组合的个数。
组合是无序的:如:(1, 4)和(4, 1)表示的是同一个组合。

样例
输入样例 1 复制
3
5
1 3 2 5 4
输出样例 1
2
提示样例 1
数组为[1 3 2 5 4], 差值 diff 为 3,其中 4 - 1 = 3,5 - 2 = 3,共 2 个组合满足条件,因此输出 2.

思路1:暴力遍历两两的差值

int Proc1(int *arr, int arrLen, int diff) // 暴力求解,两层循环,用例出现超时
{
    int i, j;
    int res = 0;
    int resTmp;
    qsort(arr, arrLen, sizeof(int), cmp);
    diff = fabs(diff);

    for (i = 0; i < arrLen; i++) {
        for (j = i + 1; j < arrLen; j++) {
            resTmp = fabs(arr[j] - arr[i]);
            if (resTmp == diff) {
                res++;
                if (arr[j + 1] > arr[j]) { // 剪枝, 并没有用
                    break;
                }
            }
        }
    }

    return res;
}

思路2:先做排序,一次O(n)遍历完就能找到满足条件的cnt. 有点点像二分查找那个套路,其实也可以使用二分查找,更快找到。

int Proc(int *arr, int arrLen, int diff) // 利用双指针求解,通过!
{
    int i, j;
    int resTmp;
    qsort(arr, arrLen, sizeof(int), cmp);
    diff = fabs(diff);
    int r = 0;
    int l = 0;
    int cnt = 0;

    while (r < arrLen) {
            if (r == l) {
                r++;
                continue;
            }
            if (arr[r] - arr[l] < diff) {
                r++;
            } else if (arr[r] - arr[l] == diff) {
                cnt++;
                r++;
            } else {
                while (arr[r] - arr[l] > diff && l < r) {
                    l++;
                }
            }
    }

    return cnt;
}

3. 无重复字符的最长子串

在这里插入图片描述
思路:又看到了“最长”的字样,还是借鉴三步走,想清楚三步走后,基本就没有什么阻塞了。

  1. 初始化某个[i, j)区间
  2. 保持某个[i, j)区间
  3. 终止找终止条件
int JudgeStr(int j, int *str, char *s)
{
	int res = 1;

	if (str[s[j]] == 1) {
		res = 0;
		return res;
	}

	return res;
}
// 1. 初始化某个[i, j)区间
// 2. 保持某个[i, j)区间
// 3. 终止找终止条件
int lengthOfLongestSubstring(char *s)
{
	int  i = 0;
	int  j = 0;
	int  len = strlen(s);
	int  str[512] = { 0 };
	int  res  = 0;
	int  step = 0;

	while (i < len && j < len) {
		if (JudgeStr(j, str, s) == 1) {
			str[s[j]] = 1;
			step++;
			j++;
			res = fmax(res, step);
		}
		else {
			res = fmax(res, step);
			i++;
			j = i;
			memset(str, 0, 512 *sizeof(int)); // 优化:耗时太严重,且又需要重新都计算一次
			step = 0;
		}
	}
	
	return res;
}

提交完效率有些低:
在这里插入图片描述

优化:
思路与上面类似,但区别是不需要清除掉 str 数组,出现重复字符,就左移窗口剔除左边的元素,直到满足 [i, j]区间都是不重复字符,此时窗口大小为 (j - i + 1).
也是三步走的思路,维持的区间为 [i, j]

int lengthOfLongestSubstring(char *s)
{
	int len = strlen(s);
	if (len == 0) {
		return 0;
	}
	int str[128] = { 0 };
	int i, j;
	int res = 0;

	for (i = 0, j = 0; j < len; j++) {
		str[s[j]]++;
		while (str[s[j]] > 1) {
			str[s[i]]--;            // 出现重复字符串,左移窗口,好手法!它这里是一直维持着前面计算的结果
			i++;
		}
		res = fmax(res, j - i + 1); // (j - i + 1)为维持的窗口大小.
	}

	return res;
}

在这里插入图片描述

567. 字符串的排列

在这里插入图片描述

排列的含义:
首先,介绍一下排列!数学中的排列是:从n个不同元素中,任取m(m≤n)个元素(被取出的元素各不相同),按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列。
大家是不是觉得挺难懂的?好吧!咱用一个简单的例子说明:有1、2、3、4这四个数字,现在请列举出由这四个数字组成的四位数有几种排列?(注意数字不能重复)
在这里插入图片描述
直接求排列不好求,这里有个手法:
在这里插入图片描述当 pFrep 和 winFreq 频数和种类都相等时,排列就满足.

// 判断排列是否满足,前提已经 cnt1 和 cnt2 字符长度都是n,只需要比较频次是否相等
bool equals(int *cnt1, int *cnt2) {
	for (int i = 0; i < 26; i++) {
		if (cnt1[i] != cnt2[i]) {
			return false;
		}
	}
	return true;
}

bool checkInclusion(char *s1, char *s2) {
	int n = strlen(s1), m = strlen(s2);
	if (n > m) {
		return false;
	}
	int cnt1[26], cnt2[26];
	memset(cnt1, 0, sizeof(cnt1));
	memset(cnt2, 0, sizeof(cnt2));
	// 填充好 n 区间大小的起始值
	for (int i = 0; i < n; ++i) {
		++cnt1[s1[i] - 'a'];
		++cnt2[s2[i] - 'a'];
	}
	if (equals(cnt1, cnt2)) {
		return true;
	}
	for (int i = n; i < m; ++i) { // 使用一个固定长度为n的滑动窗口来维护cnt2
		// 滑动窗口每向右滑动一次(i++),就多统计一次进入窗口的字符,减少一次离开窗口的字符
		++cnt2[s2[i] - 'a'];      
		--cnt2[s2[i - n] - 'a']; 
		if (equals(cnt1, cnt2)) { // 判断排列是否相等
			return true;
		}
	}
	
	return false;
}

443. 压缩字符串

在这里插入图片描述

手法1:不要使用 itoa 函数了,可以使用 sprintf 函数做int转字符.
在这里插入图片描述

我的代码: 可以通过,但内存消耗有点大:
在这里插入图片描述

void combine(char *str, int cnt, int *pos)
{
	char *tmpStr = NULL;
	int  len = 0;
	int  cntTmp = cnt;
	int  i = 0;
	while (cntTmp > 0) {
		cntTmp = cntTmp / 10;
		len++;
	}
	tmpStr = (char *)malloc(sizeof(char) * (len + 1));
	sprintf_s(tmpStr, len + 1, "%d", cnt);

	if (cnt == 1) {
		return;
	}
	while (i < len) {
		str[*pos] = tmpStr[i];
		*pos = *pos + 1;
		i++;
	}
	free(tmpStr);
	return;
}

int compress(char *chars, int charsSize)
{
	char strR;
	char strL;
	char *res = (char *)malloc(sizeof(char) * (charsSize + 1)); // 字符串的处理一定要多拷贝一个
	int  r = 0;   // 统一都从0开始处理,方便容易推导
	int  l = 0;
	int  cnt = 0;
	int  pos = 0;

	if (charsSize == 1) {
		return 1;
	}
	while (r < charsSize) {
		strL = chars[l];
		strR = chars[r];
		if (strR == strL) {
			cnt++;
			r++;
		} else {
			l = r;
			res[pos] = strL;
			pos++;
			combine(res, cnt, &pos);
			cnt = 0;
		}
		// 处理最后达到的情况
		if (r == charsSize) {
			res[pos] = strL;
			pos++;
			combine(res, cnt, &pos);
			cnt = 0;
		}
	}
	memcpy(chars, res, pos); // 这里没有拷贝 strlen(res) + 1的原因是,chars并没有这多么空间,用例 'a','a' -》 'a2'
	free(res);
	return pos;
}

int main() {
	char chars[2] = {'a', 'a'};
	int  size = 2;
	int  res  = compress((char *)chars, size);

	return 0;
}

209. 长度最小的子数组

在这里插入图片描述

对于滑动窗口一定注意,算法复杂度,一般不会使用 O(n^2),要做些优化
在这里插入图片描述

// 朴素的滑动窗口 O(n^2)
int arraySum(int *nums, int left, int right)
{
    int res = 0;
    for (int i = left; i <= right; i++) {
        res = res + nums[i];

    }
    return res;
}

int minSubArrayLen(int target, int *nums, int numsSize)
{
    int left = 0;
    int right = 0;
    int res = 0;
    int step = INT_MAX;

    while (left <= right && right < numsSize) {
        res = arraySum(nums, left, right);
        if (res >= target) {
            step = fmin(step, right - left + 1);
            left++;
        } else {
            right++;
        }
    }
    if (step == INT_MAX) {
        step = 0;
    }

    return step;
}
// 法2:优化的滑动
int minSubArrayLen(int target, int* nums, int numsSize){
    // 初始化最小长度为INT_MAX
    int step = INT_MAX;
    int res    = 0;
    int length = 0;
    int left   = 0, right;

    // 右边界向右扩展
    for(right = 0; right < numsSize; ++right) {
        res += nums[right];
        // 当sum的值大于等于target时,保存长度,并且收缩左边界
        while(res >= target) {
            length = right - left + 1;
            step = fmin(step, length);
            res -= nums[left++];
        }
    }
    // 若minLength不为INT_MAX,则返回minLnegth
    return step == INT_MAX ? 0 : step;
}

1004. 最大连续1的个数 III

在这里插入图片描述

int longestOnes(int *nums, int numsSize, int k)
{
	int left  = 0;
	int right = 0; 
	int lsum  = 0, rsum = 0;
	int ans   = 0;
	int i = 0;
	for (i = 0; i < numsSize; i++) {
		nums[i] = fabs(nums[i] - 1);
	}
	// 1. 初始化区间
	// 2. 保持区间 [left, right]
	for (right = 0; right < numsSize; ++right)
	{
		rsum += nums[right]; // Right一直加,加多了再考虑移动Left做减法
		// 3. 终止条件 
		while (rsum - lsum > k && right < numsSize) {
			lsum += nums[left]; //符合终止条件时,Left开始移动
			++left;
		}
		ans = fmax(ans, right - (left - 1));
	}

	return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值