专题4:矩阵
题目463:岛屿的周长(NO)
- 解题思路:每个方块必定有四个边,直接判断每个方块不合法的边数然后减去就行了。
给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
//直接从侧面入手,查看哪些不能作为边长,靠着方块的就不能算边长
int ans=0;
for(int i=0;i<grid.size();i++)
{
for(int j=0;j<grid[i].size();j++)
{
if(grid[i][j]==1)
{
//是岛屿,开始判断不合法的边
int count=4;//原来合法的边数
if(j>=1&&grid[i][j-1]==1)count--;//左边不合法
if(j<grid[i].size()-1&&grid[i][j+1]==1)count--;//右边不合法
if(i>=1&&grid[i-1][j]==1)count--;//上边不合法
if(i<grid.size()-1&&grid[i+1][j]==1)count--;//下边不合法
ans+=count;
}
}
}
return ans;
}
};
题目566:重塑矩阵(NO)
- 解题思路:先判断元素的个数是否不变,再使用vector构建二维数组的方法重新构建一个容器,后面通过将原来数组展开成一位数组进行for遍历,进行数据的填充。
在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。
给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
class Solution {
public:
vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
int previous_row=mat.size();
int previous_col=mat[0].size();
if(previous_row*previous_col!=r*c)
{
//元素个数不一样了,表示不能重塑,返回元素组
return mat;
}
//重新构建一个r行c列的容器
vector<vector<int>> ans(r,vector<int>(c));//记住这种写法
for(int i=0;i<previous_row*previous_col;i++)
{
//这里主要是以列为主,填完一列到下一个
ans[i/c][i%c]=mat[i/previous_col][i%previous_col];
}
return ans;
}
};
题目661:图片平滑器(YES)
- 这题我自己给出的不是很好,是强行判断周边的所有元素的状态。解析给出的也是用时四层for强行分析,值得借鉴
图像平滑器 是大小为 3 x 3 的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度。
每个单元格的 平均灰度 定义为:该单元格自身及其周围的 8 个单元格的平均值,结果需向下取整。(即,需要计算蓝色平滑器中 9 个单元格的平均值)。
如果一个单元格周围存在单元格缺失的情况,则计算平均灰度时不考虑缺失的单元格(即,需要计算红色平滑器中 4 个单元格的平均值)。
- myself
class Solution {
public:
vector<vector<int>> imageSmoother(vector<vector<int>>& img) {
//我现在没想到什么好的办法直接遍历然后逐个判断周边情况
int row=img.size();
int col=img[0].size();
vector<vector<int>>ans(row,vector<int>(col));
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
//逐个判断周边情况
int sum=0;
int count=1;//记录格子数量
//自己
sum+=img[i][j];
//判断左边是否有值
if(j>=1)
{
sum+=img[i][j-1];
count++;
}
//判断是否有右值
if(j<col-1)
{
sum+=img[i][j+1];
count++;
}
//判断是否有下值
if(i<row-1)
{
sum+=img[i+1][j];
count++;
}
//判断是否有上值
if(i>=1)
{
sum+=img[i-1][j];
count++;
}
//判断左下值
if(j>=1&&i<row-1)
{
sum+=img[i+1][j-1];
count++;
}
//判断右下值
if(j<col-1&&i<row-1)
{
sum+=img[i+1][j+1];
count++;
}
//判断左上值
if(j>=1&&i>=1)
{
sum+=img[i-1][j-1];
count++;
}
//判断右上值
if(j<col-1&&i>=1)
{
sum+=img[i-1][j+1];
count++;
}
ans[i][j]=sum/count;
}
}
return ans;
}
};
- 解析
class Solution {
public:
vector<vector<int>> imageSmoother(vector<vector<int>>& img) {
int m = img.size(), n = img[0].size();
vector<vector<int>> ret(m, vector<int>(n));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int num = 0, sum = 0;
for (int x = i - 1; x <= i + 1; x++) {
for (int y = j - 1; y <= j + 1; y++) {
if (x >= 0 && x < m && y >= 0 && y < n) {
num++;
sum += img[x][y];
}
}
}
ret[i][j] = sum / num;
}
}
return ret;
}
};
题目733:图像渲染(NO)
- 广度优先遍历,每当检查一个队列里的节点时,都会检查其周边节点是否满足条件,满足就进入。
有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。
你也被给予三个整数 sr , sc 和 newColor 。你应该从像素 image[sr][sc] 开始对图像进行 上色填充 。
为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor 。
最后返回 经过上色渲染后的图像 。
class Solution {
public:// 上 下 左 右
const int dr[4]={-1, 1, 0, 0 };
const int dc[4]={ 0, 0, -1, 1};
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
int max_row=image.size();
int max_col=image[0].size();
//使用广度优先遍历,用队列
int current_color=image[sr][sc];
if(color==current_color)
{
//如果第一个要修改的值就不满足修改条件,返回
return image;
}
queue<pair<int,int>>que;
//先将自身节点入队
//pair<int,int>p={sr,sc};
que.push(pair<int,int>(sr,sc));
//不为空则循环执行
while(!que.empty())
{
auto front=que.front();
int row=front.first;
int col=front.second;
//修改自身的值
image[row][col]=color;
//出队
que.pop();
//每个元素四个方向都要检查
for(int i=0;i<4;i++)
{
//dx和dy可以显示上下左右的值
int index_row=row+dr[i];
int index_col=col+dc[i];
//判断是否符合条件
if(index_row>=0&&index_row<max_row&&index_col>=0&&index_col<max_col
&&image[index_row][index_col]==current_color)
{
//满足条件,入队
que.push(pair<int,int>(index_row,index_col));
}
}
}
return image;
}
};
题目766:托普利兹矩阵(YES)
- 解题思路:直接两层for先遍历一遍所有元素,并再遍历的过程中检查每个元素的右下角的值是否等于当前值,如果不等,则不满足条件,直接返回即可。
给你一个 m x n 的矩阵 matrix 。如果这个矩阵是托普利茨矩阵,返回 true ;否则,返回 false 。
如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。
- myself
class Solution {
public:
//这里增量的设置其实是多余的,我只是想巩固一下这一专题的解题套路
const int dr=1;
const int dc=1;//定义右下角的增量
bool isToeplitzMatrix(vector<vector<int>>& matrix) {
int row=matrix.size();
int col=matrix[0].size();
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
//遍历每一个元素,并判断每个一的右下角的值是相同
//如果不同则直接返回false,如果没有,则继续判断
int current_row=i+dr;
int current_col=j+dc;
if(current_row<row&¤t_col<col&&
matrix[i][j]!=matrix[current_row][current_col])
{
//右下角不相等,返回false
return false;
}
}
}
return true;
}
};
题目832:翻转图像(YES)
- 解题思路:一次for遍历每一个容器,然后对每一个容器使用双指针进行翻转操作,翻转的同时使用^(异或)运算对值进行改变(1-0,0-1)
给定一个 n x n 的二进制矩阵 image ,先 水平 翻转图像,然后 反转 图像并返回 结果 。
水平翻转图片就是将图片的每一行都进行翻转,即逆序。
例如,水平翻转 [1,1,0] 的结果是 [0,1,1]。
反转图片的意思是图片中的 0 全部被 1 替换, 1 全部被 0 替换。
例如,反转 [0,1,1] 的结果是 [1,0,0]。
- myself
class Solution {
public:
vector<vector<int>> flipAndInvertImage(vector<vector<int>>& image) {
//这里反转的思路我打算使用双指针来操作,并在反转的过程中对值进行修改
int n=image.size();
for(int i=0;i<n;i++)
{
//对每一个容器进行双指针操作
int left=0;
int right=n-1;
while(left<=right)
{
//先修改值再交换元素,使用异或运算
if(left!=right)
{
image[i][left] ^=1;
image[i][right]^=1;
}else
{
image[i][left]^=1;
}
//交换两个元素
swap(image[i][left],image[i][right]);
left++;
right--;
}
}
return image;
}
};
专题5:枚举
题目1534:统计好三元组(NO)
- 解题思路:这题直接三层for就可以直接暴力解决了,当时让我感到疑惑的一个点是三层for为什么就可以遍历完所有的三元组了。
给你一个整数数组 arr ,以及 a、b 、c 三个整数。请你统计其中好三元组的数量。
如果三元组 (arr[i], arr[j], arr[k]) 满足下列全部条件,则认为它是一个 好三元组 。
0 <= i < j < k < arr.length
|arr[i] - arr[j]| <= a
|arr[j] - arr[k]| <= b
|arr[i] - arr[k]| <= c
其中 |x| 表示 x 的绝对值。
返回 好三元组的数量 。
class Solution {
public:
int countGoodTriplets(vector<int>& arr, int a, int b, int c) {
//直接暴力三层for
int ans=0;
for(int i=0;i<arr.size();i++)
{
for(int j=i+1;j<arr.size();j++)
{
for(int k=j+1;k<arr.size();k++)
{
//判断是否满是条件
if(abs(arr[i]-arr[j])<=a&&abs(arr[j]-arr[k])<=b
&&abs(arr[i]-arr[k])<=c)
{
ans++;
}
}
}
}
return ans;
}
};
题目1566:重复至少k次且长度为M的模式(NO)
- 解题思路:直接暴力的思路,遍历所有的模式,并且存储当前的模式,与后面连续的模式进行比较,看是否满足条件
给你一个正整数数组 arr,请你找出一个长度为 m 且在数组中至少重复 k 次的模式。
模式 是由一个或多个值组成的子数组(连续的子序列),连续 重复多次但 不重叠 。 模式由其长度和重复次数定义。
如果数组中存在至少重复 k 次且长度为 m 的模式,则返回 true ,否则返回 false 。
class Solution {
public:
bool containsPattern(vector<int>& arr, int m, int k) {
int len = arr.size();
//如果输入长度连一次模式匹配的长度都没有,必然是错误的
if (len < m)
{
return false;
}
//遍历每一个模式
for (int i = 0; i <= len - m; i++)
{
vector<int> pattern(m);//存储当前的模式
for (int j = 0; j < m; j++)
{
pattern[j] = arr[i + j];
}
int count = 1;//自己的模式算一个
for (int p = i + m; p <= len - m; p += m)
{//从下一个模式开始匹配
bool matched = true;
for (int q = 0; q < m; q++)
{
//逐一比较里面的元素
if (arr[p + q] != pattern[q])
{
matched = false;
break;
}
}
if (matched)
{
count++;
if (count == k)
{
return true;
}
} else
{
//这里要求要连续,不连续则匹配下一个
break;
}
}
}
return false;
}
};
题目51:烹饪料理(NO)
- 解题思路:现查找满足要求的料理,再查看它们的组合哪个最大
欢迎各位勇者来到力扣城,城内设有烹饪锅供勇者制作料理,为自己恢复状态。
勇者背包内共有编号为 0 ~ 4 的五种食材,其中 materials[j] 表示第 j 种食材的数量。通过这些食材可以制作若干料理,cookbooks[i][j] 表示制作第 i 种料理需要第 j 种食材的数量,而 attribute[i] = [x,y] 表示第 i 道料理的美味度 x 和饱腹感 y。
在饱腹感不小于 limit 的情况下,请返回勇者可获得的最大美味度。如果无法满足饱腹感要求,则返回 -1。
class Solution {
public:
int perfectMenu(vector<int>& materials, vector<vector<int>>& cookbooks, vector<vector<int>>& attribute, int limit) {
//先将所有满足limit的都先存下来,存在vector中最为合适,
//可以循环遍历那种方案美味度最高
int row_att=attribute.size();
//初始化容器
vector<int>ans;
for(int i=0;i<row_att;i++)
{
if(attribute[i][1]>=limit)
{
//将满足条件的料理都存入到容器中
ans.push_back(i);
}
}
cout<<"ans.size="<<ans.size()<<std::endl;
//看是循环遍历每一种组合
int len=ans.size();
int max_ans=-1;//最高美味度
for(int i=0;i<len;i++)
{
//自己这开始产生组合
vector<int>temp_mate=materials;//产生一个备份
int temp_ans=0;//记录每个组合的美味度数量
for(int j=i;j<len;j++)
{ bool sign=true;
//查看第j是否可以制作出
int current=ans[j];
for(int k=0;k<temp_mate.size();k++)
{
//需要制作的材料背包是否能满足
if(cookbooks[current][k]>temp_mate[k])
{
sign=false;
break;
}
}
//如果可以制作处理
if(sign)
{
//减少材料
for(int k=0;k<materials.size();k++)
{
temp_mate[k]-=cookbooks[current][k];
}
if(attribute[current][0]>max_ans)
{
temp_ans+=attribute[current][0];
}
}
}
if(temp_ans!=0 && temp_ans>max_ans)
{
max_ans=temp_ans;
}
}
return max_ans;
}
};
- 回溯算法(NO)
class Solution {
public:
int res = INT_MIN;
//查看料理是否可以被制作出
bool check(vector<int>& materials, vector<int>& cook)
{
for (int j = 0; j < materials.size(); j++)
{
if (materials[j] < cook[j])
return false;
}
return true;
}
int perfectMenu(vector<int>& materials, vector<vector<int>>& cookbooks, vector<vector<int>>& attribute, int limit) {
int maxX = 0;
int maxY = 0;
int n = cookbooks.size(); // 遍历所有料理
function<void(int)> dfs = [&](int i) {
if (i == n)
{
if (maxY >= limit) res = max(res, maxX);
return;
}
// 不选择第i种料理
dfs(i + 1);
// 选择第i种料理
if (check(materials, cookbooks[i])) // 预处理:如果背包里有足够食材数量
{
// 更新食材数量
for (int j = 0; j < materials.size(); j++)
materials[j] -= cookbooks[i][j];
maxX += attribute[i][0];
maxY += attribute[i][1];
dfs(i + 1);
// 回溯
for (int j = 0; j < materials.size(); j++)
materials[j] += cookbooks[i][j];
maxX -= attribute[i][0];
maxY -= attribute[i][1];
}
};
dfs(0);
return res == INT_MIN ? -1 : res; // 检查res是否被更新过
}
};
题目1863:找出所有子集的异或总和再求和(NO)
一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为 空 ,则异或总和为 0 。
例如,数组 [2,5,6] 的 异或总和 为 2 XOR 5 XOR 6 = 1 。
给你一个数组 nums ,请你求出 nums 中每个 子集 的 异或总和 ,计算并返回这些值相加之 和 。
注意:在本题中,元素 相同 的不同子集应 多次 计数。
数组 a 是数组 b 的一个 子集 的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a 。
- 官方题解
class Solution {
public:
int subsetXORSum(vector<int>& nums) {
int res = 0;
int n = nums.size();
for (int i = 0; i < (1 << n); ++i){ // 遍历所有子集
int tmp = 0;
for (int j = 0; j < n; ++j){ // 遍历每个元素
if (i & (1 << j)){
tmp ^= nums[j];
}
}
res += tmp;
}
return res;
}
};
题目1668:最大重复子字符串(YES)
- 使用string 的find查找字符函数
给你一个字符串 sequence ,如果字符串 word 连续重复 k 次形成的字符串是 sequence 的一个子字符串,那么单词 word 的 重复值为 k 。单词 word 的 最大重复值 是单词 word 在 sequence 中最大的重复值。如果 word 不是 sequence 的子串,那么重复值 k 为 0 。
给你一个字符串 sequence 和 word ,请你返回 最大重复值 k 。
class Solution {
public:
int maxRepeating(string sequence, string word) {
/*
string 的 find() 函数用于在字符串中查找子字符串,并返回子字符串第一次出现的位置。
如果找到子字符串,则返回子字符串的起始索引,如果没有找到则返回 -1。
*/
int count=0;
string temp=word;
while(temp.size()<=sequence.size())
{
if(sequence.find(temp)==-1)
{
//没找到子串
break;
}
//查找到子串
count++;
temp+=word;
}
return count;
}
};
专题6:模拟
题目146:螺旋遍历二维数组(NO)
- 解题思路:这种控制方向的题目不是第一次遇见了,现例举了四个指定的方向,再遍历数组的过程中,通过下一个位置坐标来判断是否要改变方向,每次更新坐标的位置都是通过加上directions来实现的。
给定一个二维数组 array,请返回「螺旋遍历」该数组的结果。
螺旋遍历:从左上角开始,按照 向右、向下、向左、向上 的顺序 依次 提取元素,然后再进入内部一层重复相同的步骤,直到提取完所有元素。
class Solution {
public:
// 右 下 左 上
const int directions[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//控制遍历的方向
vector<int> spiralArray(vector<vector<int>>& array) {
//合法性判断
if(array.size()==0||array[0].size()==0)
{
return{};
}
int row=array.size();
int col=array[0].size();
//初始化一个数组用来记录是否被访问过,这里初始默认都是0
vector<vector<bool>>visited(row,vector<bool>(col));
int total=row*col;//左右要扫描元素的总和
int dire=0;//初始方向
int current_row=0;//当前行
int current_col=0;//当前列
vector<int>ans(total);//用来记录最后的输出结果
for(int i=0;i<total;i++)
{
//记录当前扫描的值
ans[i]=array[current_row][current_col];
//设置标记,表示已经访问过了
visited[current_row][current_col]=true;
//看看下一个位置是否需要改变方向
int next_row=current_row+directions[dire][0];
int next_col=current_col+directions[dire][1];
//判断是否需要改变
if(next_row<0||next_row>=row||next_col<0||next_col>=col
||visited[next_row][next_col])
{
//需要改变方向
dire=(dire+1)%4;
}
//更新当前的坐标值
current_row+=directions[dire][0];
current_col+=directions[dire][1];
}
return ans;
}
};
题目258:各位相加(YES)
- 解题思路:while循环操作就行,通过num/10就可以判断是否是一位数
给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。返回这个结果。
- myself
class Solution {
public:
int addDigits(int num) {
while(num/10)//如果num是一位数的话,num/10就是0
{
int temp=num;
int ans=0;
while(temp)
{
//提前出每一位数字
ans+=temp%10;
temp=temp/10;
}
num=ans;
}
return num;
}
};
题目412:Fizz Buzz(YES)
- 解题思路:for遍历每个数,对每个数进行单独处理,需要注意的地方就是to_string(1)可以转换字符串。
给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:
answer[i] == “FizzBuzz” 如果 i 同时是 3 和 5 的倍数。
answer[i] == “Fizz” 如果 i 是 3 的倍数。
answer[i] == “Buzz” 如果 i 是 5 的倍数。
answer[i] == i (以字符串形式)如果上述条件全不满足。
- myself
class Solution {
public:
vector<string> fizzBuzz(int n) {
vector<string>ans;
for(int i=1;i<=n;i++)
{
if(i%3==0&&i%5==0)
{
//同时是3和5的倍数
ans.push_back("FizzBuzz");
}else if(i%3==0)
{
ans.push_back("Fizz");
}else if(i%5==0)
{
ans.push_back("Buzz");
}else
{
//都不满足
ans.push_back(to_string(i));
}
}
return ans;
}
};
题目415:字符串相加(NO)
- 解题思路:这题模拟的是我们平时计算两个数的思路,用两个指针从末尾开始遍历,并保留一个进位位。
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
class Solution {
public:
string addStrings(string num1, string num2) {
int i = num1.length() - 1, j = num2.length() - 1, add = 0;
string ans = "";
while (i >= 0 || j >= 0 || add != 0) {
int x = i >= 0 ? num1[i] - '0' : 0;
int y = j >= 0 ? num2[j] - '0' : 0;
int result = x + y + add;
ans.push_back('0' + result % 10);
add = result / 10;
i -= 1;
j -= 1;
}
// 计算完以后的答案需要翻转过来
reverse(ans.begin(), ans.end());
return ans;
}
};
- 值得注意的知识点:
用字符char-'0’就可以转化成数字
用数字int+'0’就可以转成字符
题目657:机器人能否返回原点(YES)
- 解题思路:for逐一判断每个命令。
在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。
移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。机器人的有效动作有 R(右),L(左),U(上)和 D(下)。
如果机器人在完成所有动作后返回原点,则返回 true。否则,返回 false。
注意:机器人“面朝”的方向无关紧要。 “R” 将始终使机器人向右移动一次,“L” 将始终向左移动等。此外,假设每次移动机器人的移动幅度相同。
- myself
class Solution {
public:
bool judgeCircle(string moves) {
//标记当前的坐标
int current_row=0;
int current_col=0;
//开始解析命令
for(int i=0;i<moves.size();i++)
{
switch(moves[i])
{
case 'R':
//右
current_col+=1;
break;
case 'L':
//左
current_col-=1;
break;
case 'U':
//上
current_row-=1;
break;
case 'D':
//下
current_row+=1;
break;
}
}
return current_col==0&¤t_row==0;
}
};