本系列其余文章
代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素。
DAY02 数组+双指针
今天依旧是数组中双指针的使用。
977.有序数组的平方
题目链接:力扣977
因为对vector类不够了解,不知道其自带排序函数。于是本题最开始的解题思路是将每个数字平方后再进行冒泡排序,这种方法超出时间限制,时间复杂度高。后来根据力扣977解题思路找负数与非负数的分解线flag,使用两个指针分别指向位置left= flag和right=flag+1,每次比较两个指针对应的数,选择较小的那个放入答案并移动指针。当某一指针移至边界时,将另一指针还未遍历到的数依次放入答案。但是可能哪里还没有考虑到,在代码编写上总是会出错,左右指针总是会溢出,提交了好几次,要不就是执行错误,要不就是解答错误。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> ans(nums.size(),0);
int flag;
int numsLen = nums.size();
if (numsLen == 1)
{
ans[0] = nums[0] * nums[0];
}
else
{
for (int i = 0; i < numsLen; i++)
{
if (nums[i] < 0) {
flag = i; //找到正负的交界处
}
else {
break;
}
}
int right = flag + 1, left = flag;//left表示数组小于0的部分,即下标[0,flag];right表示数组大于或等于0的部分,即下标[flag+1,nums.size()-1]
for (int i = 0; i < numsLen; i++)
{
if (left == 0 && right != numsLen - 1)
{
ans[i] = nums[right] * nums[right];
right++;
}
else if (left != 0 && right == numsLen - 1)
{
ans[i] = nums[left] * nums[left];
left--;
}
else if (left == 0 && right == numsLen - 1)
{
if (i < numsLen - 1)
{
if (nums[right] * nums[right] <= nums[left] * nums[left])
{
ans[i++] = nums[right] * nums[right];
ans[i] = nums[left] * nums[left];
}
else
{
ans[i++] = nums[left] * nums[left];
ans[i] = nums[right] * nums[right];
}
}
}
else
{
if (nums[right] * nums[right] <= nums[left] * nums[left])
{
ans[i] = nums[right] * nums[right];
right++;
}
else
{
ans[i] = nums[left] * nums[left];
left--;
}
}
}
}
return ans;
}
};
【以上是找分界线后的双指针解法,但是有错,欢迎各位大佬指导一下我还有什么地方没有考虑到】
考虑了一下可以使用左右指针指向数组头和数组尾,比较数组头和数组尾的平方大小,最大的那个放入新的数组尾。若left所指的数平方后更大,则挪动left指针,left=left+1。否则挪动right指针,right=right-1。
#include <iostream>
#include <string>
#include <vector>
#include <stdlib.h>
#include <time.h>
#include <sstream>
#include<algorithm>
using namespace std;
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> ans(nums.size(), 0);
int left = 0, right = nums.size() - 1;
int n = ans.size() - 1;
while (left <= right)
{
if (nums[left] * nums[left] <= nums[right] * nums[right])
{
ans[n] = nums[right] * nums[right];
right--;
}
else
{
ans[n] = nums[left] * nums[left];
left++;
}
n--;
}
return ans;
}
};
void trimLeftTrailingSpaces(string &input) {
input.erase(input.begin(), find_if(input.begin(), input.end(), [](int ch) {
return !isspace(ch);
}));
}
void trimRightTrailingSpaces(string &input) {
input.erase(find_if(input.rbegin(), input.rend(), [](int ch) {
return !isspace(ch);
}).base(), input.end());
}
vector<int> stringToIntegerVector(string input) {
vector<int> output;
trimLeftTrailingSpaces(input);
trimRightTrailingSpaces(input);
input = input.substr(1, input.length() - 2);
stringstream ss;
ss.str(input);
string item;
char delim = ',';
while (getline(ss, item, delim)) {
output.push_back(stoi(item));
}
return output;
}
string integerVectorToString(vector<int> list, int length = -1) {
if (length == -1) {
length = list.size();
}
if (length == 0) {
return "[]";
}
string result;
for (int index = 0; index < length; index++) {
int number = list[index];
result += to_string(number) + ", ";
}
return "[" + result.substr(0, result.length() - 2) + "]";
}
int main() {
vector<int> nums = { -7,-3,2,3,11 };
vector<int> ret = Solution().sortedSquares(nums);
string out = integerVectorToString(ret);
cout << out << endl;
return 0;
}
【以上是换了种思路后运行的正确的代码】
209.长度最小的子数组
题目链接:力扣209
本题最开始想的是用暴力解法,使用两个for循环解决问题。但是这种方法超出时间限制了。在观看卡哥视频【拿下滑动窗口! | LeetCode 209 长度最小的子数组】后,学会了滑动窗口的算法。
#include <iostream>
#include <string>
#include <vector>
#include <stdlib.h>
#include <time.h>
#include <sstream>
#include <numeric>
using namespace std;
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int i = 0, j = 0, sum = 0, minSub = nums.size();//j表示滑动窗口的终止位置,i表示滑动窗口的起始位置,minSub表示最小的长度,初始值为整个数组的最大值。
int flag = 0;
for (j; j < nums.size(); j++)
{
sum = sum + nums[j];
while (sum >= target)//使用while而不使用if是因为防止target=100,nums=[1,1,1,100]这种情况出现
{
if (j - i + 1 < minSub)
{
minSub = j - i + 1;
}
flag = minSub;
sum = sum - nums[i];
i++;
}
}
return flag;
}
};
int main() {
int target = 7;
vector<int> nums = { 2,3,1,2,4,3 };
int ret = Solution().minSubArrayLen(target, nums);
cout << ret << endl;
return 0;
}
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。
首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。
如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?
此时难免再次陷入 暴力解法的怪圈。
所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。
那么问题来了, 滑动窗口的起始位置如何移动呢?
这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:
最后找到 4,3 是最短距离。
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
以上文字和图片来源于代码随想录。【该部分仅为方便自己看笔记,没有要侵权的意思。如涉及侵权,会删掉这部分内容。】
59.螺旋矩阵II
题目链接:力扣59
本题没有任何解题思路,所以直接打开了卡哥的视频,在看懂其讲解后,自己模仿写出代码:
一入循环深似海 | LeetCode:59.螺旋矩阵II
写出正确的二分法一定要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
填充上行从左到右
填充右列从上到下
填充下行从右到左
填充左列从下到上
考虑n为奇数还是偶数。如果n为偶数,则会转(n/2)圈;如果是奇数,则转完(n/2)圈后会留下最中间的中心,且该中心所填入的数值为(n*n)。
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
int startX = 0, startY = 0, offset = 1;//startX和startY是X轴和Y轴的起始点,offset表示每次循环后左闭右开的区间都会少一个。
int num = n / 2;//转圈数
int value = 1;//填入的数值
int j = startY;
int i = startX;
while (num--)
{
//四个for循环就是以左闭右开的方式转了一圈
//模拟填充上行从左到右(左闭右开)
for (j = startY; j < n - offset; j++)
{
res[startX][j] = value++;
}
//模拟填充右行从上到下(左闭右开)
for (i = startX; i < n - offset; i++)
{
res[i][j] = value++;
}
//模拟填充下行从右到左(左闭右开)
for (j; j > startY; j--)
{
res[i][j] = value++;
}
//模拟填充左行从下到上(左闭右开)
for (i; i > startX; i--)
{
res[i][j] = value++;
}
startX++;
startY++;
offset++;
}
if (n % 2 == 1)
{
res[n/2][n/2] = value;
}
return res;
}
};