看注释的时候,放声读出来,就更好理解了。
我写的注释比较土里土气,口语化2333333。
有好多没什么用的图片,还外部链接失败了,懒得弄了就删了,不影响。
数组简介
搜索插入位置
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
class Solution {
//涉及到数组的访问,千万千万记住nums.size()-1
//如果for循环中有了i+1,千万千万记住i<nums.size()-1,不仅仅是小于nums.size()
//二分查找防止溢出可以mid = left + (right - left) / 2;
//判断元素插入位置的时候应当考虑全面,是插入在中间还是插入在两端,都要考虑
public:
int searchInsert(vector<int>& nums, int target)
{
int isFound = binarySearch(nums, target);
if (isFound == -1)
{
//没考虑全面--
//比最小的小,比最大的大,没考虑到
if (nums[0] >= target)
{
return 0;
}
else if (nums[nums.size() - 1] <= target)
{
return nums.size();
}
//--
for (int i = 0; i < nums.size()-1; i++)
{
if (nums[i] < target && nums[i + 1] > target)
{
return i + 1;
}
}
return -1;
}
else
{
return isFound;
}
}
private://这个影响吗?
int binarySearch(vector<int>& nums, int target)
{
int left = 0;
int right = nums.size() - 1;
int mid = 0;
while (left <= right)
{
mid = left + (right - left) / 2;
if (nums[mid] == target)
{
return mid;
}
else if (nums[mid] < target)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return -1;
}
};
合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
class Solution {
//开始:不太会。
//然后:看下面的注释,学习加深理解然后再自己捋捋写写。
public:
vector<vector<int> > merge (vector<vector<int> >& intervals)
{
int x = 0, y = 0;
vector<vector<int> >vec;
//默认是按照intervals[i][0]的大小排序,[0]一样才排[1]
sort(intervals.begin(), intervals.end());
//如果整个intervals只有一个区间那么直接返回即可。
if (intervals.size() == 1)
{
vec.push_back(intervals[0]);
return vec;
}
//如果整个intervals有多个区间:
for (int i = 0; i < intervals.size() - 1; i++)
{
//这里for循环的边缘值???
//这里复习一下:
//c++的for循环,更新表达式在【迭代结束时】执行。
if (intervals[i][1] < intervals[i + 1][0])
{
vec.push_back(intervals[i]);
//如果装载完当前区间后面只剩下一个区间了那么
//[可以]直接把最后的那个区间也装入结果中
//【注】是【必须】,不是[可以]!!!
//因为当i==intervals.size()-2时
//当这个循环结束后,会执行i++
//此时下一个循环就不会执行
//所以必须在只剩下最后一个区间的时候及时把最后一个区间放到结果中
if (i == intervals.size() - 2)
{
vec.push_back(intervals[i + 1]);
//return vec;加不加无所谓,这一步执行完循环也就完了。
//但是尽量不要在循环里多路return,如果return的是一个东西的话
//可能会在不好的时候触发return
//既然循环是专门处理vec这个结果的
//那么循环结束之后直接return就好
}
}
else
{
///
/// 这里的思路是这样的:
/// 因为上面的逻辑是intervals[i][1] < intervals[i + 1][0]
/// 即通过判断intervals中的某个区间的上界是否小于下个区间的下界
/// 如果是,则将这个区间装入结果中
/// 我们需要比较的是相邻的两个区间
/// 那么如何实现区间的合并?就要在比较完相邻两个区间之后及时更新
/// 更新原intervals的内容。
/// 例如,[1,3]&[2,6],中间确实3>2,合并了
/// 那么最终区间要变成[1,6],此时就要取1、2的较小值来变成[2,6]的左端点
/// 类似的,假如是[1,8]&[2,6],整个[2,6]是被包含进去的
/// 那么就要min(两个左端点),max(两个右端点)来实现区间的包含复合。
/// 下面的四行就是干了个这事儿。
///
x = min(intervals[i][0], intervals[i + 1][0]);
y = max(intervals[i][1], intervals[i + 1][1]);
intervals[i + 1][0] = x;
intervals[i + 1][1] = y;
//依然是看是不是只剩下了一个,只剩下了一个,那么就直接装入结果。
//这里跟上面的相同语句又不太一样,这里上面四行更新的区间是
//把“靠后的那个区间”更新了
//所以这里在这个循环即将结束的时候
//把最后一个更新过的区间加入到结果中
if (i == intervals.size() - 2)
{
vec.push_back(intervals[i + 1]);
}
}
}
return vec;
}
};
该结果的代码:
//#1
class Solution {
//合并区间的本质,首先是判断某两个区间的头和尾能不能衔接上;
//然后再判断某两个区间的头和头哪个小,尾和尾哪个大。
//每判断完相邻的两个,都要把第二个更新一下,再把更新过后的继续与下一个判断。
//如果两个区间头尾衔接不上,那么就把衔接不上的那组的前一个区间放到结果里
public:
vector<vector<int> > merge (vector<vector<int> >& intervals)
{
//!只有一个的话直接return intervals是不是更快?
vector<vector<int> >result;
//!只有一个区间传入的情况没有考虑到
if (intervals.size() == 1)
{
return intervals;
}
int interval_start;
int interval_end;
int interval_size = intervals.size();
//!不要忘记排序
sort(intervals.begin(), intervals.end());
//!这里需要是小于等于size-2,
//因为这里的逻辑是虽然i在遍历数组的时候可以i==size-1
//但是这里循环涉及到i+1,所以i不能取到size-1,但是可以取到size-2.
//所以i<=interval_size-2。
for (int i = 0; i <= interval_size - 2; i++)
{
if (intervals[i][1] < intervals[i + 1][0])
{
result.push_back(intervals[i]);
if (i == interval_size - 2)
{
result.push_back(intervals[i + 1]);
}
}
else//intervals[i][1]>intervals[i+1][0]
{
interval_start = min(intervals[i][0], intervals[i + 1][0]);
interval_end = max(intervals[i][1], intervals[i + 1][1]);
intervals[i + 1][0] = interval_start;
intervals[i + 1][1] = interval_end;
if (i == interval_size - 2)
{
result.push_back(intervals[i + 1]);
}
}
}
return result;
}
};
区别在于储存结果的vector的定义位置。
接下来尝试将储存结果的vector定义到后面,如果intervals只有一个则直接return intervals
// #2
class Solution {
//合并区间的本质,首先是判断某两个区间的头和尾能不能衔接上;
//然后再判断某两个区间的头和头哪个小,尾和尾哪个大。
//每判断完相邻的两个,都要把第二个更新一下,再把更新过后的继续与下一个判断。
//如果两个区间头尾衔接不上,那么就把衔接不上的那组的前一个区间放到结果里
public:
vector<vector<int> > merge (vector<vector<int> >& intervals)
{
//!只有一个的话直接return intervals是不是更快?
//!只有一个区间传入的情况没有考虑到
if (intervals.size() == 1)
{
return intervals;
}
int interval_start;
int interval_end;
vector<vector<int> >result;
int interval_size = intervals.size();
//!不要忘记排序
sort(intervals.begin(), intervals.end());
//!这里需要是小于等于size-2,
//因为这里的逻辑是虽然i在遍历数组的时候可以i==size-1
//但是这里循环涉及到i+1,所以i不能取到size-1,但是可以取到size-2.
//所以i<=interval_size-2。
for (int i = 0; i <= interval_size - 2; i++)
{
if (intervals[i][1] < intervals[i + 1][0])
{
result.push_back(intervals[i]);
if (i == interval_size - 2)
{
result.push_back(intervals[i + 1]);
}
}
else//intervals[i][1]>intervals[i+1][0]
{
interval_start = min(intervals[i][0], intervals[i + 1][0]);
interval_end = max(intervals[i][1], intervals[i + 1][1]);
intervals[i + 1][0] = interval_start;
intervals[i + 1][1] = interval_end;
if (i == interval_size - 2)
{
result.push_back(intervals[i + 1]);
}
}
}
return result;
}
}solution;
//这个思路就是不断比较右端点,只要左侧的右端点大于右侧的左端点,就合到一起
//然后紧接着比下下个
//如果不能合并了,就把结果推到结果中去
//这个跟上面那个方法不同之处在于这个不需要在原数组上更改,只需要记录当前最左边区间的左端点和记录下的最大的右端点即可。
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end());
vector<vector<int>> ans;
for (int i = 0; i < intervals.size();) {
int t = intervals[i][1];
int j = i + 1;
while (j < intervals.size() && intervals[j][0] <= t) {
t = max(t, intervals[j][1]);
j++;
}
ans.push_back({ intervals[i][0], t });
i = j;
}
return ans;
}
二维数组简介
旋转矩阵
给你一幅由 N × N
矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
解法1
这里面设计一个数学推导的过程。
class Solution {
public:
void rotate(vector<vector<int> >& matrix)
{
int temp = 0;
int n = matrix.size();
for (int row = 0; row < (n + 1) / 2; row++)
{
for (int col = 0; col < n / 2; col++)
{
temp = matrix[row][col];
matrix[row][col] = matrix[n - col - 1][row];
matrix[n - col - 1][row] = matrix[n - row - 1][n - col - 1];
matrix[n - row - 1][n - col - 1] = matrix[col][n - row - 1];
matrix[col][n - row - 1] = temp;
}
}
}
};
我认为,本题有两个难点:
- 主要是要推出来仅用一个扩展变量来实现一次性转换四个数的赋值方程组
- 在两个循环的行、列边缘值取值方面,需要仔细推敲。(详情推敲过程见草稿最下面)。
解法2
写在下图和下草稿的前面:
这里还是需要注意一个事情,即行数(列数)的边缘值影响。
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int temp = 0;
int n = matrix.size();
for (int i = 0; i < n / 2; i++)
{
for (int j = 0; j < n; j++)
{
swap(matrix[i][j], matrix[n - i - 1][j]);
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < i; j++)
{
swap(matrix[i][j], matrix[j][i]);
}
}
}
};
这个比上一个要慢一些,因为这个相当于是取两个就要开一个temp交换一次,但是上面那个是取4个开一个temp交换。
零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
// #1 我的解法
//第一遍记录0对应的行和列,第二遍集中处理。
//写完后思考:是不是可以用set?内存空间更少!
//pair直接被存在了<iostream>头文件里
//正好比较一下pair和vector在面对
class Solution {
public:
void setZeroes(vector<vector<int> >& matrix)
{
vector<pair<int,int> >zeroRC;
for (int i = 0; i < matrix.size(); i++)
{
for (int j = 0; j < matrix[0].size(); j++)
{
if (matrix[i][j] == 0)
{
zeroRC.push_back(make_pair(i, j));
}
}
}
//注意行和列的元素数目分别对应于matrix.size()还是matrix[0].size()
for (int i = 0; i < zeroRC.size(); i++)
{
for (int j = 0; j < matrix[0].size(); j++)
{
matrix[zeroRC[i].first][j] = 0;
}
for (int j = 0; j < matrix.size(); j++)
{
matrix[j][zeroRC[i].second] = 0;
}
}
}
};
如果把pair换成vector,那么内存上会有一些损失:
但是为什么呢?
不太理解。我看了pair的源码,按理说跟vector储存的数据类型是一样的,内存空间也应该是一样的呀!
//#2 pair换成vector
class Solution {
public:
void setZeroes(vector<vector<int> >& matrix)
{
vector<vector<int> >zeroRC;
for (int i = 0; i < matrix.size(); i++)
{
for (int j = 0; j < matrix[0].size(); j++)
{
if (matrix[i][j] == 0)
{
zeroRC.push_back({i,j});
}
}
}
//注意行和列的元素数目分别对应于matrix.size()还是matrix[0].size()
for (int i = 0; i < zeroRC.size(); i++)
{
for (int j = 0; j < matrix[0].size(); j++)
{
matrix[zeroRC[i][0]][j] = 0;
}
for (int j = 0; j < matrix.size(); j++)
{
matrix[j][zeroRC[i][1]] = 0;
}
}
}
};
//#3 使用set分别存放含0的行和列
class Solution {
public:
void setZeroes(vector<vector<int> >& matrix)
{
set<int>zeroR;
set<int>zeroC;
for (int i = 0; i < matrix.size(); i++)
{
for (int j = 0; j < matrix[0].size(); j++)
{
if (matrix[i][j] == 0)
{
zeroR.insert(i);
zeroC.insert(j);
}
}
}
//如果是set,那么这里不能直接这样子,应该用set的迭代器来跑
//因为要遍历的是set里的每一个元素,而且set的元素还不能用下标遍历
for (set<int>::iterator i = zeroR.begin(); i !=zeroR.end(); i++)
{
for (int j = 0; j < matrix[0].size(); j++)
{
matrix[*i][j] = 0;
}
}
for (set<int>::iterator j = zeroC.begin(); j != zeroC.end(); j++)
{
for (int i = 0; i < matrix.size(); i++)
{
matrix[i][*j] = 0;
}
}
}
};
下一个解法:
//#4 据说是双百的解法,思路比较奇特
//实测不是双百,但是挺妙的
//这个太妙了,我都不好说他妙在哪儿,就是挺妙的。。。
//这个解法实际上是先检测了第一行和第一列是否含0,
//因为接下来从第二行和第二列开始的遍历都是要把第一行和第一列当作标志位的。
//如果第一行和第一列不含零,那么无所谓了,之后的操作也不用管了
//如果第一行和第一列含0,那么就得记住,如果说之后某行或某列没有0的话,
//最终还得记得把第一行或第一列补0.
//也就是说,后面从第二行第二列开始的遍历,是可以保证用标志位表征标志位所对应行列的含0情况的。
//而标志位的含0情况,则需要通过两个isFirst来判断。
//如果标志位的行列本身不含0,那OK,其他行列有0的,把标志位弄成0,没有0的,标志位就非0
//如果标志位的行列本身含0,那也OK,标志位当完标志位后,再按照两个isFirst来把标志位行列也走流程置0
//因为标志位行列是否含0已经通过isFirstxxxx表征了
//就当个拓展思路吧,现在知道了,但是之前无论如何想不到这么做的,太妙了……
class Solution {
public void setZeroes(vector<vector<int>>& matrix) {
bool isFirstRowHaveZero = false;
bool isFirstColHaveZero = false;
for(int i = 0; i < matrix.length; i++) {
if (matrix[i][0] == 0) {
isFirstColHaveZero = true;
}
}
for(int j = 0; j < matrix[0].length; j++) {
if (matrix[0][j] == 0) {
isFirstRowHaveZero = true;
}
}
for(int i = 1; i < matrix.length; i++) {
for(int j = 1; j < matrix[i].length; j++) {
if (matrix[i][j] == 0) {
matrix[0][j] = 0;
matrix[i][0] = 0;
}
}
}
for(int i = 1; i < matrix.length; i++) {
for(int j = 1; j < matrix[i].length; j++) {
if (matrix[0][j] == 0 || matrix[i][0] == 0) {
matrix[i][j] = 0;
}
}
}
for(int i = 0; i < matrix.length; i++) {
if (isFirstColHaveZero) {
matrix[i][0] = 0;
}
}
for(int j = 0; j < matrix[0].length; j++) {
if (isFirstRowHaveZero) {
matrix[0][j] = 0;
}
}
}
}
对角线遍历
//#1 修改的大佬代码加入了自己的理解!
class Solution {
public:
vector<int> findDiagonalOrder(vector<vector<int>>& matrix)
{
vector<int>nums;
int m = matrix.size();
//这里记得要判空,如果是空要及时return
if (m == 0)
{
return nums;
}
int n = matrix[0].size();
int sum_x_y = 0;
/// 下方的其他过程其实都挺好理解的,就是这里
/// 为什么是sum_x_y < m + n,为什么是m + n?
/// 经过我的推敲,其实这里m+n是有冗余的。
/// 同一个对角线(如果不是nxn的矩阵说斜线可能更合适)上面的横纵坐标之和
/// 最多与m+n-2相等
/// 为什么呢?因为不管是横还是纵,下标与数值相比都是少1的。两个加起来就是少了2.
/// 所以,当横纵坐标之和超过m+n-2,在给定的矩阵中肯定是找不到点了。
/// 把m+n-2理解之后,剩下的遍历层面就没问题了。
///
while (sum_x_y <= m + n-2)
{
//第1、3、5...趟:
/// 这个地方和下面的y2有几分类似,为什么这里要这样子呢?
/// 先拿这里的x1说事儿:
/// 假如给了一个向右和向下无限延伸的矩阵
/// 然后在这个矩阵上以原点为左上角画了一个区域
/// 我们在这个区域上进行遍历,
/// 这里很重要的是我画的在下面的那个图
/// 下面【图:解析对角线遍历辅助图】(用手机拍下来边读这里边看更有效率)
/// 本来我们是可以直接让x1=sum_x_y的
/// 但是因为我们所要的区域在一些地方不包含x1=sum_x_y了
/// 就需要采取这种措施,将我们的横下标限制在一定的区域内。
/// 其实这些代码里面所有的<m啦,<n啦,其实都应该改成<=m-1、<=n-1这样子
/// 这样才能看出下标和实际矩阵之间的关系。不过这里没关系,我们继续看
/// 以3x3的矩阵为例,当x的起点为(2,0)的时候,那么OK,因为2+0<m,
/// 这个(2,0)依然在所画区域的范围里。
/// 但是情况往下的时候就不太一样了
/// 比如第5次遍历的时候,在我们的无限延伸的矩阵里,遍历应当从(4,0)开始
/// 但是不行啊!(4+0)>=m,即>=3了。(4,0)根本不在我们画的这个区域里。
/// 所以这个时候,我们需要强行找到在(4,0)这根线上
/// 但是依然处在我们所画区域里的那个点。
/// 所以我们要先把x1缩到m-1,因为想要在这个区域里,x1的最大值就是m-1.
/// 然后通过sum_x_y - x1计算得到y1,
/// 然后先通过x1 >= 0 && y1 < n来判断这个计算出来的x1已经取到了是最大值的那个点
/// y1符不符合要求
/// 不符合要求了,那下面那个while循环就进不去,也就是说不会往nums这个向量里塞东西
/// 如果这个还是符合要求的,while循环进去了,把符合要求的当前这个点塞到nums里了,
/// 那既然这里进去了,那这条路径上还有没有其他符合要求的点呢?
/// 那么就要x1--,y1++,用这条路径的规则推算下一个点,然后继续x1>=0&&y1<n
/// 来判断下一个点是不是符合要求。
/// 这样就完事了。
/// 下面那个y2的也是同理的。
///
int x1 = (sum_x_y < m) ? sum_x_y : m - 1;
int y1 = sum_x_y - x1;
while (x1 >= 0 && y1 < n)
{
nums.push_back(matrix[x1][y1]);
x1--;
y1++;
}
sum_x_y++;
if (sum_x_y > m + n-2)
{
break;
}
//第2、4、6...趟:
//
int y2 = (sum_x_y < n) ? sum_x_y : n - 1;
int x2 = sum_x_y - y2;
while (y2 >= 0 && x2 < m)
{
nums.push_back(matrix[x2][y2]);
x2++;
y2--;
}
sum_x_y++;
}
return nums;
}
}solution;
【图:解析对角线遍历辅助图】