您现在的位置是:首页 >技术教程 >代码随想录算法训练营第二天|leetcode 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II网站首页技术教程

代码随想录算法训练营第二天|leetcode 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

迷路的夏风 2024-06-17 10:19:56
简介代码随想录算法训练营第二天|leetcode 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

leetcode 977.有序数组的平方

请添加图片描述
想到昨天写的双指针,十分刻意用了一下,感觉还是比较生疏,还得加强练习和思考,然后发现还需要排序,想到了vector的排序sort(),但是觉得直接用不好,也忘记了sort()是类似于快速排序的方法(C++ sort()排序详解),就傻傻地写了一个冒泡排序(比较熟悉),结果果然超时了。

class Solution {
public:

    vector<int> sortedSquares(vector<int>& nums) {
		int fast = 0;
		int slow = 0;
		while (fast < nums.size())
		{
			nums[slow++] = nums[fast] * nums[fast];
			fast++;
		}
		for (int i = 0; i < nums.size()-1; i++)
		{
			for (int j = 0; j < nums.size() - i - 1; j++)
			{
				if (nums[j] > nums[j + 1])
				{
					int temp = nums[j + 1];
					nums[j + 1] = nums[j];
					nums[j] = temp;
				}
			}
		}
        return nums;
    } 
};

双指针法经典题目 | LeetCode:977.有序数组的平方
学习之后,才发现题目给出首先非递减顺序排序的整数数组 nums,说明初始数组的顺序已经排好了,数列递增,但不是单调递增,中间可以有重复。数组其实是有序的, 只不过负数平方之后可能成为最大数了。那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。

leecode官方在这里有讲解并给出了另一种解法,如果数组 nums 中的所有数都是非负数,那么将每个数平方后,数组仍然保持升序;如果数组 nums 中的所有数都是负数,那么将每个数平方后,数组会保持降序,这样一来,如果我们能够找到数组 nums 中负数与非负数的分界线,那么就可以用类似「归并排序」的方法,这里就不写了(后面再看)。

这里给出代码随想录里的两种解法。

1、暴力排序

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for (int i = 0; i < nums.size(); i++) {
            nums[i] *= nums[i];
        }
        sort(nums.begin(), nums.end()); // 直接使用vector自己的排序算法,快速排序
        return nums;
    }
};

2、双指针法

class Solution {
public:

    vector<int> sortedSquares(vector<int>& nums) {
		vector<int> r(nums.size(), 0);//新建一个容器,初始化为0
		int k = nums.size() - 1;//r容器的指针指向容器末尾
		
		for (int i = 0, j = nums.size() - 1; i <= j;)
		{
			if (nums[i] * nums[i] < nums[j] * nums[j])
			{
				r[k--] = nums[j] * nums[j];
				j--;
			}
			else
			{
				r[k--] = nums[i] * nums[i];
				i++;
			}
		}
		return r;
    } 
};

这里双指针和昨天不同的是一个指针指向原始容器的首部,一个指针指向原始容器的尾部,然后首尾比较后将大的从尾部存入新的容器,这里还需要定义一个指向新容器的指针指向尾部。

leetcode 209. 长度最小的子数组

请添加图片描述
这道题第一眼看上去就是想用暴力解法,两个for循环,但又在想怎么使用滑动窗口,没有什么头绪,索性还是得学习一下,感觉十分巧妙。
拿下滑动窗口! | LeetCode 209 长度最小的子数组接描述

滑动窗口

#include<iostream>
#include<vector>
using namespace std;

int minSubArrayLen(int target, vector<int>& nums) {
	int result = INT_MAX; 
	int sum = 0;
	int L = 0;
	int i = 0;
	for (int j = 0; j < nums.size(); j++)
	{
		sum += nums[j];
		while (sum >= target)
		{
			L = j - i + 1;
			result = result < L ? result : L;
			sum -= nums[i++];//这里十分巧妙地遍历了子数组中的子数组,不断更改i(子序列的起始位置)
		}
	}
	return result == INT_MAX ? 0 : result;
}


int main()
{
	//target = 7, nums = [2,3,1,2,4,3]
	vector<int> v;
	v.push_back(2);
	v.push_back(3);
	v.push_back(1);
	v.push_back(2);
	v.push_back(4);
	v.push_back(3);
	
	int r = minSubArrayLen(7, v);

	if (r == 0)
	{
		cout << "未找到该条件下的长度最小的子数组" << endl;
	}
	else
	{
		cout << "该条件下的长度最小的子数组长度为:" <<r<< endl;
	}

	return 0;
}

注:引用群里大佬的讲解,INT_MAX 和 INT32_MAX都是C++中常量,分别表示最大整数,定义在头文件limits.h中。因为int占4字节32位,根据二进制编码的规则,INT_MAX = 2^31-1,INT_MIN= -2^31。所以INT_MAX和INT32_MAX的值是相同的。

滑动窗口也可以理解为双指针法的一种,只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
请添加图片描述
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动: 如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了,遍历子集的子集)。
窗口的结束位置如何移动: 窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。
从而将O(n^2)暴力解法降为O(n)。

leetcode 59. 螺旋矩阵 II

请添加图片描述
一入循环深似海 | LeetCode:59.螺旋矩阵II
还是比较复杂,自己没什么头绪,总想找规律但是也没找到(哭笑),还是学习之后跟着敲了一遍。

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        int startx = 0;
        int starty = 0;
        int loop = n / 2;//每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int count = 1;
        int offset = 1;// 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i, j;
        vector<vector<int>> v(n,vector<int>(n,0));
        while (loop--)
        {
            i = startx;
            j = starty;
            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = starty; j < n - offset; j++)
            {
                v[startx][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = startx; i < n - offset; i++)
            {
                v[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--)
            {
                v[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--)
            {
                v[i][j] = count++;
            }
            startx++;
            starty++;
            offset += 1;

        }
        int mid = n / 2;
        if (n % 2) {
            v[mid][mid] = count;
        }

        return v;

    }
};

此题总结:

  • 一定要坚持循环不变量原则。
  • 每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。