简单记录
题目来源:leetcode
刷题顺序:代码随想录
刷题工具:VSCode+leetcode插件
补充:延毕时间充裕,会结合LeetCode 101: A LeetCode Grinding Guide (C++ Version)相似题目一起做。
二分查找也常被称为二分法或者折半查找,每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度为O(n) 的数组,二分查找的时间复杂度为O(logn)。
题目:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -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 l = 0, r = nums.size()-1;
while (l <= r){
int middle = (l+r)/2;
if (nums[middle] > target){
r = middle-1;
}
else if(nums[middle] < target){
l = middle+1;
}
else{
return middle;
}
}
return -1;
}
};
题目: 示例: 代码: 题目: 示例: 代码: 题目: 示例: 代码: 题目: 示例: 代码: 题目: 示例: 代码1 (双指针,一快一慢): 代码2(自己的解法,也用的双指针,一前一后): 题目: 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 示例: 代码: 题目: 示例: 代码: 还有两道不想做了。 本日的刷题主要集中在数组方面:要注意二分查找中的区间问题:
如果题目为左闭右闭。那么while的范围应该在l<=r,每次右边缩减范围时应该将r = middle-1。
如果题目为左闭右开。那么while范围是l2. 35搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。输入: nums = [1,3,5,6], target = 2
输出: 1
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size()-1;
while (l <= r){
int m = (l+r)/2;
if (nums[m] > target){
r = m-1;
}
else if (nums[m] < target){
l = m+1;
}
else{
return m;
}
}
return l;
}
};
3. 34在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int l =0, r = nums.size()-1;
while (l <= r){
int m = (l+r)/2;
if ( nums[m] > target){
r = m-1;
}
else if (nums[m] < target){
l = m+1;
}
else{
int first = m, last = m;
while (first >=0 && nums[first] == target){
--first;
}
while (last <= nums.size()-1 && nums[last] == target){
++last;
}
return {first+1, last-1};
}
}
return {-1, -1};
}
};
4. 69x的平方根
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
class Solution {
public:
int mySqrt(int x) {
if (x == 0) return 0;
int l = 1, r= x;
while (l <= r){
int m =l+(r-l)/2; //注意,这里要用m=l+(r-l)/2;
int sqrt = x/m;
if(sqrt < m){
r = m-1;
}
else if (sqrt > m){
l = m+1;
}
else return m;
}
return r;
}
};
注意:这道题不能用m=(l+r)/2,因为l+r可能会出现超出int范围的情况。这么看来一般情况下还是用m=l+(r-l)/2比较保险。
5. 81搜索旋转排序数组 II
已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。
给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。
你必须尽可能减少整个操作步骤。输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true
class Solution {
public:
bool search(vector<int>& nums, int target) {
int l = 0, r = nums.size()-1;
while (l <= r){
int m = (l+r)/2;
if (nums[m] == target){ //找到了直接返回成功
return true;
}
//这里有三个判断条件,如果mid大于左端,说明左半边是升序,如果mid小于左端,说明右半边是升序。如果相等
//将无法判断哪半边升序,所以将左端+1。
if (nums[m] > nums[l]){
if (nums[m] > target && target >= nums[l]){
r = m-1;
}
else{
l = m+1;
}
}
else if (nums[m] < nums[l]){
if (nums[m]< target && target <= nums[r]){
l = m+1;
}
else{
r = m-1;
}
}
else{
++l;
}
}
return false;
}
};
这道题要好好理解一下,因为有相同元素的存在。
二、移除元素
1. 27移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
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];
}
}
return slow;
}
};
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
if (nums.size() == 0){
return 0;
}
int l = 0, r = nums.size()-1;
while (l < r){
if (nums[l] == val){
nums[l] = nums[r];
nums[r] = val;
--r;
}
else{
++l;
}
}
if (nums[l] == val){
--l;
}
return l+1;
}
};
2. 26删除排序数组中的重复项
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
size_t s = 0;
for (size_t f = 0; f <nums.size(); ++f){
if (nums[f] != nums[s]){
nums[++s] = nums[f];
}
}
return s+1;
}
};
快慢指针太好用了。
3. 283移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
class Solution {
public:
void moveZeroes(vector<int>& nums) {
size_t s = 0;
for (size_t f = 0; f <nums.size(); ++f){
if (nums[f] != 0){
nums[s++] = nums[f];
}
}
for (size_t i =s; i < nums.size(); ++i){
nums[i] = 0;
}
}
};
题解上用的swap,性能应该比我这个方法好。偷懒了。
总结
提示:如有错误欢迎指正交流。
1.学习了二分查找,重点注意区间的开闭。
2.学习了删除元素,使用双指针的方法。
3.算法思路能写个七七八八,但每次都处理不好边界情况,还经常出现数组越界的情况,要注意。