代码随想录刷题笔记
代码随想录 (programmercarl.com)
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标下对应的数据。
注意:
数组下标都是从0开始的。
数组内存空间的地址是连续的
C++中:vector和array区别:
数组的元素是不能删的,只能覆盖。
二维数组在内存的空间地址是连续的么?
不同编程语言的内存管理是不一样的
C++中二维数组是连续分布的。
Java中,没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。二维数组的每一行头结点的地址是没有规则的,更谈不上连续。
Java的二维数组在内存中不是 3\*4
的连续地址空间,而是四条连续的地址空间组成!
vector类似于一个动态数组,当不确定所要使用的数组的大小的时候,可以使用vector。
vector可以动态的改变大小。
(1)头文件
include
(2)创建vector对象并初始
vector
(3)在尾部插入元素
nums.push_back(1); //在尾部插入值为1的元素
(4)指定位置插入元素
nums.insert(nums.begin()+1,9); //在nums的nums[1]位置插入元素9 如1,2,3,4 插入为 1,9,2,3,4
nums.insert(nums.begin()+1,3,9); //在nums的nums[1]位置插入3个数,值都为9
(5)删除尾部最后一个元素
nums.pop_back(); //删除尾部元素
(6) 删除第i个元素(删除后的数组重新排列 如删除之前nums[2]=3,删除nums[1]后,删除后的nums中nums[1]=3.)
nums.erase(nums.begin()+2) //删除第3个元素(删除nums[2])
//删除注意
#include
#include
using namespace std;
int main()
{
vectornums(4,0);
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
nums[3] = 4;
nums.erase(nums.begin()+1); //删除nums[1]
cout << nums[1]; //nums[2]前移 这时nums[1]为nums[2]的值
return 0;
}
// 输出nums[1]为 3
(7)删除区间元素
nums.erase(nums.beigin()+1,nums.begin()+3); //删除nums中nums[1]元素到nums[2]元素
(8)大小
nums.size();
(9)清空
nums.clear();
(10)判断是否为空
nums.empty();
#include
using namespace std;
#include
int main()
{
int a, b;
cin >> a >> b;
vector>nums(a, vector(b);//创建一个二维动态数组nums[a][b],并初始化为0;
//若初始化为1,将(b)改为(b,1)
int n=nums.size() ; //行数
int m=nums[0].size(); //列数
}
**前提:**1. 有序数组
2. 数组中无重复元素(如果有返回的元素下标可能不唯一)
解题思路:
设定左右指针
找出中间位置,并判断该位置值是否等于 target
nums[mid] == target 则返回该位置下标
nums[mid] > target 则右侧指针移到中间
nums[mid] < target 则左侧指针移到中间
时间复杂度:O(logN)
解题框架
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2; //计算 mid 时需要防止溢出,代码中 left + (right - left) / 2 就和 (left + right) / 2 的结果相同,但是有效防止了 left 和 right 太大直接相加导致溢出。
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
混淆点:
边界条件的选择。例如到底是 while(left < right)
还是 while(left <= right)
,到底是right = middle
呢,还是要right = middle - 1
呢?
解决方法:
确定区间(循环不变量)
左闭右闭?左闭右开?
根据区间,从而选择边界条件
(1)左闭右开:
left = 0;
right = numsize - 1;
while (left <= right)
if (nums[middle] > target) right 要赋值为 middle - 1
if (nums[middle] < target) left 要赋值为 middle + 1
思考:当[left, right]取left=right时,合不合法?例如当left=right=1,[1,1]区间包含1,是合法的,所以可以取等号,即:while(left <= right)
思考:区间:左闭右闭;条件nums[middle]>target。左闭右闭说明一定包含right;条件则说明nums[middle]一定大于target,所以nums[middle]一定不是寻找的值target,所以下一步区间选择right = middle - 1
思路同上,所以下一步区间选择left = middle + 1
class Solution {
public:
int search(vector& nums, int target) {
//左闭右闭区间解法
int left = 0;
int right = nums.size() - 1;
while(left <= right){
int middle = left + ((right - left) / 2); //防止溢出,等同于(left + right)/2
if(nums[middle] > target) {
right = middle - 1;
} else if(nums[middle] < target) {
left = middle + 1;
} else {
return middle; //找到目标值,返回下标
}
}
return -1; //未找到目标值,返回-1
}
};
(2)左闭右开:
left = 0;
right = numsize;
while (left < right)
if (nums[middle] > target) right 要赋值为 middle
if (nums[middle] < target) left 要赋值为 middle + 1
思考:当[left, right)取left=right时,合不合法?例如当left=right=1时,[1,1)区间代表又包含有不包含1,是不合法的,所以不可以取等号,即:while(left < right)
思考:区间:左闭右开;条件:nums[middle]>target。则因为接下来的搜索区间本来就不包含right,并且条件中nums[middle]>target说明下一次搜索的左区间也不包含nums[middle],(即下一个查询区间不会去比较nums[middle]),所以下一步区间选择right = middle
思考:区间:左闭右开;条件:nums[middle]>target。左闭所以包含了left,但是条件中nums[middle] 总结: while(left right)选择小于或小于等于时,将等于(即left= right = 1)条件带入,判断是否合法([1,1]合法,[1,1)不合法) 选择right = middle?还是right = middle - 1? 选择left = middle?还是left = middle + 1? 遇到闭区间一侧,则需要+或-1 遇到开区间一侧,则不需要+或-1 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例 1: 示例 2: 提示: **解题思路:**使用以上模板进行二分查找即可。 相关练习题: 二分查找法变式1:找出第一个大于等于目标元素的索引 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。—>二分查找 解题思路: 本题不同于原型之处在于,如果目标值**不存在于数组中,返回它将会被按顺序插入的位置。**另一种解读也就是:返回第一个大于或等于target元素的下标。 也就分为两种情况, 当数组中存在target时,与原型相同。 当数组中不存在target时,如何处理?其实也就是改变一下while后return值即可。将原型中的return -1,改变为: 第一种写法为什么最后返回right+1?因为判断条件是left<=right,所以结束的时候有left = right + 1,然后又因为target插入的位置是数组中第一个大于target元素的位置,所以应该返回的right+1和left其实是等价的。 二分法参考代码: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XXwZJioT-1680080333635)(…/…/…/lenovo/AppData/Roaming/Typora/typora-user-images/image-20220725174516368.png)] 其实,这道题如果不限制时间复杂度为 O(log n) 的算法,使用暴力解法更快。 暴力解题 不一定时间消耗就非常高,关键看实现的方式,就像是二分查找时间消耗不一定就很低,是一样的。 暴力解法代码: 效率如下: 二分查找法变式2:找出元素的第一个位置和最后一个位置(数组中有重复元素) https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solution/yi-wen-dai-ni-gao-ding-er-fen-cha-zhao-j-ymwl/(优秀题解) 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 在原型中的前提条件是数组中没有重复元素,以上变式是数组中存在重复元素,则需要返回对应区间,也就是要找到目标元素的第一个(下边界)和最后一个位置(上边界) 解题思路: 考虑到这种情况时(target = 5),mid所指位置并不是第一个5,所以需要继续寻找,而不是返回当前位置,如何做呢?需要改变的是判断条件,也就是当target = nums[mid]也让 right = mid - 1。即可以合并为target <= nums[mid] 时,让 right = mid - 1即可。还需要注意的就是,我们计算下边界时最后的返回值为 left 计算下边界代码 与寻找下边界相反。 当target = nums[mid]改为让 left = mid + 1。即可以合并为target >= nums[mid] 时,让 left = mid + 1即可。 计算上边界代码 总结一下: 计算下边界时,当 target <= nums[mid] 时,right = mid -1;target > nums[mid] 时,left = mid + 1; 计算上边界时,当 target < nums[mid] 时,right = mid -1; target >= nums[mid] 时 left = mid + 1;刚好和计算下边界时条件相反,返回right。 完整参考代码: 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。 解题思路: 题解1:二分查找法 不能使用内置函数,所以我们需要寻找一个整数,使得: 如果这个整数的平方 恰好等于 输入整数,那么我们就找到了这个整数; 如果这个整数的平方 严格大于 输入整数,那么这个整数肯定不是我们要找的那个数; 如果这个整数的平方 严格小于 输入整数,那么这个整数 可能 是我们要找的那个数(重点理解这句话)(也就是题解1中为什么要ans = mid的原因)。 应此,由以上思路,即可分析出,这道题可以使用二分查找法来查找这个整数,不断缩小范围去猜。 初始范围:[0, x / 2]; 还需要注意的是, mid * mid 防止溢出解决方法: (1)mid <= x / mid (2)(long long)mid * mid <= x if else条件判断 当mid * mid <= x时,说明有可能答案在[mid, r]之中,有可能是mid,所以可以巧用ans,将mid暂存,接下来将l = mid + 1;也就是[mid + 1, r]中搜索是否有符合条件的结果,如果有,则更新ans,如果没有,则答案就是上一步暂存的ans。 然而当mid * mid > x时,答案一定不在该范围内[mid, r] ,所以直接r = mid - 1; 二分查找法参考代码: 题解2:牛顿迭代法 推导公式: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pxydmA9S-1680080333636)(…/…/…/lenovo/AppData/Roaming/Typora/typora-user-images/image-20220726191032275.png)] 牛顿迭代法参考代码: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 解题思路: 因为数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。 所以这道题,其实也就是在**模拟vector库函数中,erase()**封装好的操作。 有两种解法: 题解1:暴力解法 使用两层for循环,第一层for循环负责遍历元素,找出目标值对应位置。第二层for循环负责更新数组,将找到对应位置之后的所有元素都向前移动一个位置,也就是进行覆盖操作。 时间复杂度:O(n^2) 空间复杂度:O(1) 暴力解法参考代码: 题解二:快慢指针解法 快慢指针(双指针):**通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**常见于数组、链表、字符串等操作中。 关键:定义快慢指针所代表的意义 快指针:寻找新数组的元素(不含有val的所有元素) 慢指针:指向需要更新新数组下标的位置(将快指针所指的需要的元素,放到慢指针所指位置中) 时间复杂度:O(n)、空间复杂度:O(1) 快慢指针参考代码: 其实这道题有个更简单的思路, 因为题目的要求是元素的顺序可以改变。 我们可以将数组分成「前后」两段: 前半段是有效部分,存储的是不等于 val 的元素。 所以,从左边找到和val相等元素位置时,只需要与该数组右边不等于val的元素交换(swap)即可,不需要将val后所有元素移动一位 也就是以下参考代码:(但是拷贝覆盖也就是上一个代码实现比较通用) 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。 将最终结果插入 nums 的前 k 个位置后返回 k 。 不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 解题思路: 使用快慢指针法, 快指针:寻找与s慢指针不相等元素的位置 慢指针:指向新数组元素更新的位置的前一位 每次判断快慢指针所指元素是否相等, 相等:快指针后移一位继续寻找 不相等:赋值,将快指针所指元素赋值给慢指针(++slow而不是slow++,因为slow指向的是相等元素位置,而下一个不相等元素位置(fast)是slow之后一位) 重复,直到快指针遍历完所有元素 注意: 边界:当数组的长度等于0时,不需要操作,直接返回0即可。 初始化条件: 快指针用于遍历数组,但第一个元素不需要检查,也就是只需要比较第二个元素和第一个是否相等即可,所以快指针初始值为1; 初始状态下,慢指针应该是在快指针前一位,所以初始值为0; 结束条件:当快指针达到数组尾部时,程序结束,返回当前slow + 1;即更新后数组长度。 参考代码: 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 为了让解法更具有一般性,我们将原问题的「保留 2 位」修改为「保留 k 位」。 解题思路: 还是使用快慢指针(和26题思路很相似),只是因为要保留k位,那么比较时,检查的是快指针指向的元素和慢指针指向的元素的前(k-1)个元素(也就是nums[slow - ( k - 1)]是否相等。(判断是否相同后的操作与26题相同) 相等,则不更新慢指针,只更新快指针。 不相等,则现将慢指针后移一位,再将快指针指向的元素赋值给慢指针指向的单元,最后更新快指针。 注意: 边界:当数组的长度小于等于2时,不需要操作,直接返回原数组即可。 初始化条件: 快指针用于遍历数组,但前两个元素不需要检查,也就是不需要操作序号小于2的元素,所以快指针初始值为2; 初始状态下,慢指针应该是在快指针前一位,所以初始值为1; 结束条件:当快指针达到数组尾部时,程序结束,返回当前slow + 1;即更新后数组长度。 参考代码:(通用解法:k为保留k个相同元素) 给你一个按 非递减顺序 排序的整数数组 解题思路: 题解1:暴力排序 这道题,最直接的想法:先将数组全部平方,再讲其排序。 但是时间复杂度为:O(n + nlogn),也可以说是O(nlogn)的时间复杂度 空间复杂度:O(log n)。除了存储答案的数组以外,我们需要 O(logn) 的栈空间进行排序。 暴力排序参考代码: 题解2:双指针法 由于暴力解决时间复杂度为O(nlogn),所以为了将时间复杂度调整为O(n),可以使用双指针解法;具体思路如下: 思考: 当数组全部平方后,最大值在数组两端(负数平方后有可能变成最大数),最小值在数组中间。整体趋势为【大,小,大】。 所以,可以考虑双指针法,两个指针i 指向起始位置,j 指向终止位置。 另外定义一个新数组result,是它和原始数组有一样的空间大小,并且定义一个k使其指向result数组终止位置(因为题目要求从小到大,所以终止位置存放最大数,与i和j扫描数组是从大到小相反,所以从终止位置往前移动依次存放后最终result中数是从小到大的顺序) 具体逻辑: 如果 如果 (当A[i] * A[i] = A[j] * A[j]时,放在哪个判断条件中都可以) 双指针法参考代码: 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。 解题思路: 本题重点: 合并这两个数组,自让会想到最简单的做法:归并排序:将两个有序数组合并放到另一个数组中,最终将该数组赋值给nums1即可。(时间复杂度O(m + n) ) 但由于本题中,nums1长度能够保证放下m+n个元素,所以可以直接将合并的结果放入num1中。 于是有以下两种思路: 因此,思路2更好,即从后向前进行比较。 具体步骤: 当m > 0 并且n > 0时,从后向前比较num1[m - 1]和 num2[n - 1]: 如果是 nums1[m - 1]大,则把 nums1[m - 1]放到 num1 的第 m + n - 1 位置,并让 m --。 当以上遍历条件结束时,此时m和n至少有一个是0 当m == 0 时,说明nums2中可能还剩元素,那么将nums2中剩下的元素复制到nums1中头部即可。 当n == 0 时,说明nums2中数字全部转移到nums1中,此时nums1中可能还剩元素,但是因为nums1本来就是有序,且这些剩余的元素一定是nums1和nums2中最小的元素,所以不需要动,直接留在原地即可。 参考代码: 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 解题思路: 题解1:暴力解法 因为是小白,想到了暴力解决方法,两层for循环,然后不断的寻找符合条件的子序列,所以时间复杂度为O(n^2),导致超时,只能是做个记录参考一下吧。。。 参考代码: 题解2:滑动窗口 滑动窗口,是使用两个指针分别指向窗口的首尾位置,通过不断调节首尾位置即窗口大小,从而找到满足条件的最优结果。由于只需要使用一个for循环,所以时间复杂度为O(n) 滑动窗口解决思路如下: for循环中,表示的是终止位置 j 。(若是起始位置,又回到暴力解法) 起始位置如何移动?精髓所在!动态调节滑动窗口起始位置 i 。(在循环中,不断变更i,也就是子序列的起始位置) 实现滑动窗口需要考虑的问题: 窗口是什么? 其和大于等于s 的 长度最小的 连续 子数组 如何移动窗口起始位置? 当前窗口值大于s,起始位置就向后移动进行缩小。 如何移动窗口结束位置? 遍历整个数组,也就是for循环里的索引 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。 总结滑动窗口框架: 疑惑解答:为什么for循环里套着while循环,时间复杂度不是O(n^2), 而是O(n)? 时间复杂度,主要看的是每一个元素被操作的次数,每个元素在滑动窗口进入和出去时分别被操作一次,所以时间复杂度是2 * n,也就是O(n)。 本题解题思路: 使用左右指针 left 和 right,left 和 right 之间的长度即为滑动窗口的大小(即连续数组的大小)。 参考代码: 你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果: 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。 这道题和上一道题(209)最大的不同就在于,上一道是在求最小窗口,而这一道在求最大窗口。 关键区别就在于: 最大窗口是在迭代右移右边界的过程中(while中)更新结果; 最小窗口是在迭代右移左边界的过程中(for中)更新结果。 解题思路: 由于tree[i] 的值表示水果的种类,所以可以使用map 思路: 滑动窗口:两种类型水果的总数量 右边界:不断向右移动,并记录对应种类水果数量。直到遇到第三种水果(wimdow.size() > 2),比较记录是否是最大窗口,并且开始移动左边界,进行缩小窗口 左边界:记录第一个种类水果的最左边界,当遇到第三种水果时,左边界开始右移,直到有一种水果数量为零,此时窗口又变成两种水果,退出左边界缩小 参考代码: 待解决 给你一个正整数 解题思路: 该题为模拟题,但容易越写越乱,最后修修改改思路混乱。 针对此类模拟题,要遵循循环不变量的原则,即对每条边都要坚持不变的处理规则。 这道题的循环不变量为:按照左闭右开的规则处理每一条边(即每条边的最后一个节点不处理,留给下一条边的起始位置) 循环转几圈? n/2—>最后判断if (n % 2 == 1) 如果是奇数,则需要将中心数字填充完整 循环中如何实现? 每一圈的起始位置如何定义? 不是每一圈起点都是(0,0),所以需要定义变量startx和starty,用来表示每一圈起始位置,并且每一圈循环后改变对应值(startx++, starty++ ),进行下一圈赋值。 每条边的终止位置如何定义? 定义一个变量offset,初始值为1,每一圈offset++; 每条边如何赋值? 使用for循环,搭配startx或starty以及终止条件(n-offset)进行依次赋值(count++) 如何定义一个二维数组? 解释:定义了一个vector容器,元素类型为vector,初始化为包含m个vector对象,每个对象都是一个新创立的vector对象的拷贝,而这个新创立的vector对象被初始化为包含n个0。 这个讲解不错:https://ashleyzhao.blog.csdn.net/article/details/123821901?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3-123821901-blog-113831570.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3-123821901-blog-113831570.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=6 参考代码: 题型总结: 数组:每次遇到二分法,都是一看就会,一写就废(opens new window) 这道题目呢,考察数组的基本操作,思路很简单,但是通过率在简单题里并不高,不要轻敌。 可以使用暴力解法,通过这道题目,如果追求更优的算法,建议试一试用二分法,来解决这道题目 在这道题目中我们讲到了循环不变量原则,只有在循环中坚持对区间的定义,才能清楚的把握循环中的各种细节。 二分法是算法面试中的常考题,建议通过这道题目,锻炼自己手撕二分的能力。 双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。 这道题目迷惑了不少同学,纠结于数组中的元素为什么不能删除,主要是因为以下两点: 双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。 本题介绍了数组操作中的另一个重要思想:滑动窗口。 本题中,主要要理解滑动窗口如何移动 窗口起始位置,达到动态更新窗口大小的,从而得出长度最小的符合条件的长度。 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。 如果没有接触过这一类的方法,很难想到类似的解题思路,滑动窗口方法还是很巧妙的。 模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟,十分考察大家对代码的掌控能力。 在这道题目中,我们再一次介绍到了循环不变量原则,其实这也是写程序中的重要原则。 相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的,大家可以在这道题目中体会到这一点。 mmercarl.com/0704.二分查找.html) 这道题目呢,考察数组的基本操作,思路很简单,但是通过率在简单题里并不高,不要轻敌。 可以使用暴力解法,通过这道题目,如果追求更优的算法,建议试一试用二分法,来解决这道题目 在这道题目中我们讲到了循环不变量原则,只有在循环中坚持对区间的定义,才能清楚的把握循环中的各种细节。 二分法是算法面试中的常考题,建议通过这道题目,锻炼自己手撕二分的能力。 双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。 这道题目迷惑了不少同学,纠结于数组中的元素为什么不能删除,主要是因为以下两点: 双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。 本题介绍了数组操作中的另一个重要思想:滑动窗口。 本题中,主要要理解滑动窗口如何移动 窗口起始位置,达到动态更新窗口大小的,从而得出长度最小的符合条件的长度。 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。 如果没有接触过这一类的方法,很难想到类似的解题思路,滑动窗口方法还是很巧妙的。 模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟,十分考察大家对代码的掌控能力。 在这道题目中,我们再一次介绍到了循环不变量原则,其实这也是写程序中的重要原则。 相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的,大家可以在这道题目中体会到这一点。class Solution {
public:
int search(vector
704. 二分查找
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
35. 搜索插入位置
class Solution {
public:
int searchInsert(vector
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
// 分别处理如下三种情况
// 目标值在数组所有元素之前
// 目标值等于数组中某一个元素
// 目标值插入数组中的位置
if (nums[i] >= target) { // 一旦发现大于或者等于target的num[i],那么i就是我们要的结果
return i;
}
}
// 目标值在数组所有元素之后的情况
return nums.size(); // 如果target是最大的,或者 nums为空,则返回nums的长度
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
int getLeftBound(vector
int getRightBound(vector
class Solution {
public:
vector
69. x 的平方根
class Solution {
public:
int mySqrt(int x) {
int l = 0, r = x, ans = -1;
while (l <= r) {
int mid = l + (r - l) / 2;
if ((long long)mid * mid <= x) { //防止溢出
ans = mid; //将上一步得到的可能值存放在ans中(防止下一步l移动后,可能ans就是答案)
l = mid + 1;
} else { //当mid * mid > x时,ans一定不再该区间内,所以mid - 1;
r = mid - 1;
}
}
return ans;
}
};
class Solution {
public:
int mySqrt(int x) {
if (x == 0) {
return 0;
}
double C = x, x0 = x;
while (true) {
double xi = 0.5 * (x0 + C / x0);
if (fabs(x0 - xi) < 1e-7) {
break;
}
x0 = xi;
}
return int(x0);
}
};
双指针
27. 移除元素
class Solution {
public:
int removeElement(vector
class Solution {
public:
int removeElement(vector
后半段是无效部分,存储的是等于 val 的元素。
最终答案返回有效部分的结尾下标。class Solution {
public:
int removeElement(vector
26. 删除有序数组中的重复项
class Solution {
public:
int removeDuplicates(vector
80. 删除有序数组中的重复项 II
class Solution {
public:
int process(vector
977. 有序数组的平方
nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。class Solution {
public:
vector
A[i] * A[i] < A[j] * A[j]
那么result[k--] = A[j] * A[j];
。赋值后,j–;A[i] * A[i] >= A[j] * A[j]
那么result[k--] = A[i] * A[i];
。赋值后,i++;class Solution {
public:
vector
88. 合并两个有序数组
如果是 nums1[n - 1]大,则把 nums2[n - 1] 放到 num1 的第 m + n - 1 位置,并让 n --。class Solution {
public:
void merge(vector
滑动窗口
209. 长度最小的子数组
class Solution {
public:
int minSubArrayLen(int target, vector
void slidwindow(vector
如果滑动窗口内的值 sum >= target,维护连续数组最短长度,left 向右移动,缩小滑动窗口。
如果滑动窗口内的值 sum < target,则 right 向有移动,扩大滑动窗口。class Solution {
public:
int minSubArrayLen(int target, vector
904. 水果成篮
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。class Solution {
public:
int totalFruit(vector
76. 最小覆盖子串
模拟行为
59. 螺旋矩阵 II
n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
vector<vector<int> > res(row, vector<int>(column, 0)); //初始化row*column二维动态数组res,初始化值为0
vector(n)表示构造一个无名且含n个0的vector对象。class Solution {
public:
vector
总结
二分法
双指针法
滑动窗口
模拟行为
双指针法
滑动窗口
模拟行为