代码随想录DAY02 | LeetCode977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵
)
前言
今天完成的是对数组内容部分的首尾。前两道题目吗仍然涉及到双指针算法,第三道题目则是运用到二维前缀和部分的相关知识。
一、LeetCode977.有序数组的平方
题目解析:给大家一个有序数组,然后返回一个所有元素平方后返回的一个数组,要求该数组依然有序。(原数组包含复数)暴力思路:可以先所有平方,然后在进行排序(可以快速排序,归并排序)。双指针思路:最大的元素一定从原数组的两边开始取到。则设计两个指针分别从首尾两端向中间靠拢。靠拢时比较指向的数的平方的大小。根据题目的要求。我们选择那个较大的或者较小的,并且将指针代表的下标进行移动。指针不断移动的过程就是循环的过程。循环要求的条件check().则是例如的i < j 等到条件(相遇的时候就会终止)。双指针思路的重要一环:寻找i++和j++的条件。
JAVA代码如下(已经添加注释)
class Solution {
public int[] sortedSquares(int[] nums) {
int n=nums.length;
int[] ans=new int[n];//最后返回的新数组
int start=0;//开头的变量,可以看成i
int last=n-1;//结尾的变量,可以看成j
while (start<=last){//相遇的时候终止循环
n--;//代表新数组的元素的下标,我们要求从下到大,那么就到着写
//接下来的是具体的移动过程,先比较大小,看条件选择一个进行移动
if (nums[start]*nums[start]>nums[last]*nums[last]){
ans[n]=nums[start]*nums[start];
start++;
}else {
ans[n]=nums[last]*nums[last];
last--;
}
}
return ans;
}
}
C++代码如下:(含注释)
class Solution {
public:
vector<int> sortedSquares(vector<int>& A) {
int k = A.size() - 1;
vector<int> result(A.size(), 0);
for (int i = 0, j = A.size() - 1; i <= j;) { // 定义变量和终止条件
//进行移动
if (A[i] * A[i] < A[j] * A[j]) {
result[k--] = A[j] * A[j];
j--;
}
else {
result[k--] = A[i] * A[i];
i++;
}
}
return result;
}
};
简单提一下:个人还是比较习惯实用while循环来写,因为终止条件可以看的更加明显。
二、LeetCode 209.长度最小的子数组
题目解析:看到题目的第一印象,遍历出数组的所有的情况,让后进行比大小。比大小还好说,就是可以每一次一一的进行比较,但是遍历数组的情况时,我们的反应就是两层for循环,一层的循环变量用来记录数组的起始位置,另一层循环遍历用来记录数组的结束为止。诶!!这里就注意了,记录位置,那我们不就可以想到指针嘛,而它恰好又是双层循环,那么我们不是正好就可以尝试一下双指针嘛。一个for循环来做两个for循环来做的事情。那么循环里的索引下标j表示的是起始位置还是初始位置呢?我们假设它是起始位置,那么另一个坐标需要遍历数组中在它之后的所有元素。会比较麻烦(和暴力就没有什么区别了)。所以for循环里的 j 表示的是截止位置。所以我们来探索起始位置就好了。随着终止位置不断移动,如果这次形成的这个集合符合条件的话,那么就记录一下此时的长度并且继续尝试下一次的移动。
所以核心且重点的代码!!
while(sum >= s){
sublength = j-i+1;//取得子序列的长度
result = result <sublength ? result : sublength;//更新此时的长度
sum -= nums[i++];//这里是滑动窗口的精妙支出,不断表更i(子序列的起始位置)
}
本题目JAVA代码如下
class Solution {
// 滑动窗口
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {//末位置移动。
sum += nums[right];//题目要求的区间和
//核心代码块
while (sum >= s) {
result = Math.min(result, right - left + 1);//取得子序列的长度并且更新此时的长度
sum -= nums[left++];//这里是滑动窗口的精妙支出,不断表更i(子序列的起始位置)
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
补充题目: LeetCode 799.最长的包含不重复元素的子数列
暴力算法:
for(int i = 0.;j < n ;j++){
for(int j = 0; j <= i;j++){
if check(i,j){
res = min(res,i - j +1)
}
}
}
双指针做法:
i每一次移动一个单位,在求出每个i会对应一个j,只枚举遍历 i 和 j 的话只需要判断需不需要再次移动。
for(int i = 0,j = 0;i <n;i++)
{
while(j <= i && check(i,j)) j++
res = max(res,j - i + 1);
}
C++代码答案
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int q[N], s[N];
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
int res = 0;
for (int i = 0, j = 0; i < n; i ++ )
{
s[q[i]] ++ ;
while (j < i && s[q[i]] > 1) s[q[j ++ ]] -- ;
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
三、LeetCode 59.螺旋矩阵
题目解析:本题是一个模拟转圈的过程,某种角度来看,它和二维的前缀和是及其相似的。前缀和的概念大家可以类比高等数学中的级数和的概念。
在讨论本题目之前,我先去介绍一下一道求子矩阵的和的LeetCode题目,题号为796.
大矩阵的前缀和等于红色矩阵的前缀和加上绿色矩阵的前缀和(有点像容斥原理),并且继续递归下去。
C++代码如下
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &s[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
while (q -- )
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
}
return 0;
}
那么本题目可以类比
JAVA版本
class Solution {
public int[][] generateMatrix(int n) {
int loop = 0; // 控制循环次数
int[][] res = new int[n][n];
int start = 0; // 每次循环的开始点(start, start)
int count = 1; // 定义填充数字
int i, j;
while (loop++ < n / 2) { // 判断边界后,loop从1开始
// 模拟上侧从左到右
for (j = start; j < n - loop; j++) {
res[start][j] = count++;
}
// 模拟右侧从上到下
for (i = start; i < n - loop; i++) {
res[i][j] = count++;
}
// 模拟下侧从右到左
for (; j >= loop; j--) {
res[i][j] = count++;
}
// 模拟左侧从下到上
for (; i >= loop; i--) {
res[i][j] = count++;
}
start++;
}
if (n % 2 == 1) {
res[start][start] = count;
}
return res;
}
}
C++版本
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 1; // 用来给矩阵中每一个空格赋值
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < n - offset; j++) {
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};
四、总结
今天刷的题目总结了很多,不过很多还没有完全理解下来,还需要再巩固一下。还是有些力不从心,不过状态也好多了。