之前已经刷了一部分但是因为备考耽搁了,再加上自己本身算法功底不牢固,决定直接从零开始刷代码随想录并开始写博客,挑选其中个人觉得对自己比较有启发的题目进行记录。
704.二分查找
题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
思路:
观察到题目中所给的是有序数组,且需要查找一个目标值,可以想到通过二分查找来进行搜索。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}
else if(nums[mid] > target){
right = mid - 1;
}
else{
left = mid + 1;
}
}
return -1;
}
};
启发:
1.本题中容易提交失败的一个细节便是是否取等的问题:while循环条件里left与right比较大小是否要取等?right是等于mid-1还是等于mid?
这就涉及到循环不变量规则。为了防止一如循环深似海,边界问题一定要率先考虑清楚。在本题中我们要考虑的就是区间left到right到底是左闭右闭还是左闭右开?本题我的代码中采用的是左闭右闭思路。
确定好区间选择后再来看while的循环条件,既然是左闭右闭区间,那么left可以等于right,所以用≤。当中间值大于目标值时,此时已经确定中间值mid比目标值大了,还将mid纳入区间范围内显然没有意义,所以right = mid -1。
当然,如果采用左闭右开区间,那么while循环条件处肯定left不能等于right,所以用<。当中间值大于目标值时,因为要去左区间寻找,所以让right = mid - 1。
到底right如何赋值,核心还是取决于让搜索的下一区间不包括这个中间值。
27.移除元素
题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。
思路:
1.因为数组元素地址空间连续,不能单独删除一个元素,只能进行覆盖
2.注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。
3.很容易想到双重for循环暴力解法,将其拆分后其核心当然是先找到元素等于val的元素,然后再将后面所有的元素向前移动一个位置。在此基础之上我们可以采用双指针。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast = 0;fast < nums.size();fast++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
};
启发:
1.这是第一次接触双指针方法。双指针方法核心一定要理解快慢指针各自的含义!本题中:
快指针:寻找新数组的元素。
慢指针:指向新数组元素下标。
977.有序数组的平方
题目:给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1: 输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]
思路:
1.本题很容易想到直接将所有数组元素平方和后再排序的暴力做法,但实质上是依赖了官方库函数。
2.因为是非递减顺序的整数数组,可以很清楚的想到平方最大的数一定是最左边的数或最右边的数。既然本题较难让结果数组从最小的平方开始存储,不妨换个角度从最大的平方开始存储。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size(),0);
int left = 0, right = nums.size() - 1;
int k = res.size() - 1;
while(left <= right){
if(nums[left] * nums[left] < nums[right] * nums[right]){
res[k] = nums[right] * nums[right];
right--;
k--;
}
else{
res[k] = nums[left] * nums[left];
left++;
k--;
}
}
return res;
}
};
启发:
1.本题再一次使用双指针法,一个指向最左一个指向最右,通过比较二者平方的大小将更大的一方存储进新数组中,且新数组从最大下标开始存储。
904.水果成篮
题目:
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
示例 :
输入:fruits = [3,3,3,1,2,1,1,2,3,3,4] 输出:5 解释:可以采摘 [1,2,1,1,2] 这五棵树。
思路:
1.本题是在209.求长度最小数组的基础上进行了强化的题目,所以直接将这道题拿来记录。
2.初步思路为滑动窗口,一个快指针一直遍历整个数组,直到发现第三种类型的水果,此时要将原本的第一种水果进行删除(即慢指针右移),直到记录的只有两种水果。每一次遍历的最后都需要记录两个指针的差+1(即长度)进行存储。
3.本题在遍历的同时还需要记录水果的种类,因此需要借助哈希表。但是自己在做这道题时对C++的哈希表仍掌握不熟练,因此采用的C#的字典进行解答。
public class Solution {
public int TotalFruit(int[] fruits) {
Dictionary<int,int> dict = new Dictionary<int,int>();
int left = 0;
int ans = 0;
for(int right = 0;right < fruits.Length;right++){
if(!dict.ContainsKey(fruits[right])){
dict.Add(fruits[right],1);
while(dict.Count > 2){
dict[fruits[left]]--;
if(dict[fruits[left]] == 0){
dict.Remove(fruits[left]);
}
left++;
}
}
else{
dict[fruits[right]]++;
}
ans = Math.Max(ans,right - left + 1);
}
return ans;
}
}
启发:
1.滑动窗口的核心是三点:(1)窗口内装的什么?(2)如何移动门窗口起始位置》(3)如何移动窗口结束位置?
本题中,窗口内装的是采摘的水果(只能有两种,即两种不同的数字);每次遍历窗口结束位置都移动一位直到数组末尾;当窗口内出现三种水果时,需要移动窗口起始位置直至窗口内只有两种水果
2.通过字典键值对的方式来存储水果,其中键为数组的元素值(即水果种类),必须保证字典内最多只有两种键;值为该类水果的个数。
3.当发现字典中有三种键时,必须确保字典中只有两种键,此时必须使用while循环来将第一种水果的值减为0并且剔除出字典。此处需要注意的点:必须使用while循环而非if,因为必须保证剔除所有该种类水果,if只能剔除一个而非一种;另外也得让窗口起始位置一直移动到指向另一种水果。
59.螺旋矩阵
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
思路:
1.本题实质上是模拟顺时针遍历矩阵的一个过程,一定要考虑区间选择问题。
2.在遍历之前可以先想一想我们需要哪些信息,每一次遍历时我们需要初始的X,Y位置,需要知道矩阵的中心位置,每一次遍历后相当于整体往内部缩了一圈,因此每一整圈遍历的长度会变,需要有一个变量来记录偏移量。
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int count = 1;
vector<vector<int>> res(n,vector<int>(n,0));
int loop = n / 2;
int mid = n / 2;
int startX = 0,startY = 0;
int offset = 1;
int i,j = 0;
while(loop--){
for(j = startX;j<n-offset;j++){
res[startX][j] = count++;
}
for(i = startY;i<n-offset;i++){
res[i][j] = count++;
}
for(;j>startX;j--){
res[i][j] = count++;
}
for(;i>startY;i--){
res[i][j] = count++;
}
startX++;
startY++;
offset++;
}
if(n % 2 == 1){
res[mid][mid] = n * n;
}
return res;
}
};
启发:
1.本题因为涉及区间选择问题,依旧要保证循环不变量原则,而在本题中最好选择左闭右开区间,因为如果选择左闭右闭区间,很明显会出现重复取值。
2.本题在模拟整个遍历过程中实际上只是遍历了所有的圈,最后的中心位置元素没有在遍历中处理,因此记得需要在遍历完后处理中心元素。涉及单独处理中心元素的情况只有当矩阵行列数为奇数时。
54.螺旋矩阵
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 : 输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出:[1,2,3,4,8,12,11,10,9,5,6,7]
思路:
1.将本题与上一题进行比较,最明显的差别便是矩阵行列数可能不同。
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
if(matrix.size() == 0) return res;
int top = 0;
int down = matrix.size() - 1;
int left = 0;
int right = matrix[0].size() - 1;
while(true){
for(int j = left;j<=right;j++){
res.push_back(matrix[top][j]);
}
if(++top > down) break;
for(int i = top ; i <= down ; i++){
res.push_back(matrix[i][right]);
}
if(--right < left) break;
for(int j = right; j >= left; j--){
res.push_back(matrix[down][j]);
}
if(--down < top) break;
for(int i = down; i >= top ; i--){
res.push_back(matrix[i][left]);
}
if(++left > right) break;
}
return res;
}
};
启发:
1.在本题中笔者最初采取的和上一题同样的做法,但很快发现其中有一个很大的问题在于上一题中的循环是一圈一圈的遍历,而在本题中因为行列数不同,在一圈遍历结束后中心部分很可能已经构不成圈了,而要单独处理中心部分的元素的话要考虑的情况较多且复杂。因此在尝试了多次依旧失败后换了一种通过边界的方法进行判断
2.思路依旧是模拟整个顺时针遍历的过程,每遍历完一行或者一列就将其对应的边界进行移动,如果左右边界或上下边界交错,那么就跳出循环。
3.本题中容易出错的依旧是涉及边界的问题。首先是如何考虑边界交错?如果两个边界相等的时候要不要跳出循环?考虑到遍历到最后只剩下中心部分的时候,此时一定有两个边界是相等的(如示例中三行四列的矩阵,遍历到最后只剩下中间的6、7,此时上下边界已经相等)。而我们采用这种方法的目的就是为了避免再单独处理中间的元素而是遍历所有元素,所以在考虑边界交错时不能取等!
其次是每次遍历一行或者一列时,需要注意除了第一行的遍历之外,之后的每一次遍历之前边界都进行了移动(例如示例中第一行的1、2、3、4遍历完后,因为进行了边界交错判断所以此时上边界等于1),因此最后一列遍历时将上边界的值赋给i后是从第二行开始的,以此类推。所以在本题中区间选择更推荐左闭右闭区间。