【数组】文章链接:代码随想录
目录
目录
34. 在排序数组中查找元素的第一个和最后一个位置【不好懂】
二分法
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
704. 二分查找
文章讲解:代码随想录
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
【左闭右闭】的写法
移位符号:向右是除,向左是乘。注意移位符号的优先级很低,要加括号。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
while (left <= right){
//int middle = (left + right)/2;//.5会抹掉
//int middle = left + (right-left)/2;
int middle = left + ((right - left)>>1);//防止溢出,注意括号
if(target < nums[middle]){
right = middle - 1;//一定要写-1
}
else if(target > nums[middle]){
left = middle + 1;
}
else{
return middle;
}
}
return -1;
}
};
35.搜索插入位置
题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章:代码随想录
如果没找到那个数,那么最后会走完while,right在left左边,这个值介于nums[right]和nums[left]之间,插入位置对应left。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int middle = 0;
while (left <= right){
int middle = left + ((right-left)>>1);
if (target < nums[middle]){
right = middle - 1;
}else if(target > nums[middle]){
left = middle + 1;
}else return middle;// case 1 : in
}
// cout << left << right << endl; //Q : 怎么用leetcode debug?
// printf("%d",left);
return left;//case 2 : not in
// 或者right + 1 ; 但是不能含有middle,因为如果nums为空,middle会没有被定义
}
};
34. 在排序数组中查找元素的第一个和最后一个位置【不好懂】
题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章:代码随想录
我的方法还挺简单,用的双指针法,可以通过
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0 , right = nums.size() - 1;
if(nums.size() == 0) return {-1, -1};
while(left <= right && left < nums.size() && right >= 0){
if(nums[right] == target && nums[left] == target){
return {left, right};
}
if(nums[left] < target) left ++;
if(nums[right] > target) right --;
}
return {-1, -1};
}
};
代码随想录提供的方法
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
// 情况二
return {-1, -1};
}
private:
int getRightBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
};
其他道友提供的方法
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int first = -1;
int last = -1;
// 找第一个等于target的位置
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] == target) {
first = middle;
right = middle - 1; //重点
} else if (nums[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
// 最后一个等于target的位置
left = 0;
right = nums.size() - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] == target) {
last = middle;
left = middle + 1; //重点
} else if (nums[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return {first, last};
}
};
思路:假设我们有一个数组 nums = [1,2,3,3,3,3,4,5,9],我们想要找到目标值 target = 3 的第一个和最后一个位置。
首先,我们找到第一个等于 target 的位置:
初始化 left = 0 和 right = 8。计算 middle = (0 + 8) / 2 = 4,nums[4] = 3 等于 target,所以 first = 4,然后 right = 4 - 1 = 3。
更新 middle = (0 + 3) / 2 = 1,nums[1] = 2 小于 target,所以 left = 1 + 1 = 2。
更新 middle = (2 + 3) / 2 = 2,nums[2] = 3 等于 target,所以 first = 2,然后 right = 2 - 1 = 1。
现在 left = 2 大于 right = 1,所以循环结束。我们找到了第一个等于 target 的位置,first = 2。
然后,我们找到最后一个等于 target 的位置:
重新初始化 left = 0 和 right = 8。计算 middle = (0 + 8) / 2 = 4,nums[4] = 3 等于 target,所以 last = 4,然后 left = 4 + 1 = 5。
更新 middle = (5 + 8) / 2 = 6,nums[6] = 4 大于 target,所以 right = 6 - 1 = 5。
更新 middle = (5 + 5) / 2 = 5,nums[5] = 3 等于 target,所以 last = 5,然后 left = 5 + 1 = 6。
现在 left = 6 大于 right = 5,所以循环结束。我们找到了最后一个等于 target 的位置,last = 5。
所以,target = 3 的第一个和最后一个位置分别是 2 和 5,返回结果为 [2, 5]。
69.x的平方根
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
我的暴力解法也不知道对不对:
class Solution {
public:
int mySqrt(int x) {
int i = 0;
for(; i <= x; i++){
if((long long)i * i > x){
break;
}
}
return i - 1;
}
};
MoonLight提供了三种方法,空间复杂度都是O(1)
牛顿迭代法(时间复杂度O(logn)):
首先,判断输入的数字是否为0,如果是则直接返回0;
定义一个误差值epsilon,用于判断迭代是否达到精度要求;
初始化变量c为输入的数字x,x0也为x;
使用牛顿迭代法的公式x = 1/2(x0 + c/x0)来更新x0,直到x0与xi之间的差值小于epsilon为止;
返回x0的整数部分作为结果袖珍计算器算法(时间复杂度O(1)):
判断输入的数字是否为0,如果是则直接返回0;
使用指数函数exp和对数函数log来计算平方根;
将x的平方根计算结果ans转换为整型后返回;
如果(ans+1)的平方小于等于x,则将ans加1作为结果;否则,直接返回ans二分查找(时间复杂度O(logn)):
判断输入的数字是否为0,如果是则直接返回0;
初始化左边界left为0,右边界right为x,结果ans初始化为-1;
进行二分查找,每次取中间值mid,如果mid的平方小于等于x,则更新ans为mid,并将左边界left更新为mid+1;否则,将右边界right更新为mid-1;
当左边界大于右边界时,结束查找,返回ans作为结果
class Solution {
public:
int mySqrt(int x) {
if(x == 0)
return 0;
int left = 0, right = x, ans = -1;
while(left <= right){
int mid = (left + right) / 2;
if((long long)mid * mid <= x){//0 <= x <= 231 - 1
ans = mid;
left = mid + 1;
}else{
right = mid - 1;
}
}
return ans;
}
};
TIP:
1、在二分法的while中的每一种if下,除非直接return,都得至少有个left = middle +1 或者 right = middle -1要不就会成死循环。
2、题目暗示了0 <= x <= 2^31 - 1,mid*mid有可能大于x,因此限制其为双长整型long long,如果不加这个限制会报错超出时间限制。
int:int 是最常用的整数类型,通常占用4个字节(32位),可以表示大多数整数值。范围大约为 -2^31 到 2^31 - 1。
short:short 是短整型,占用2个字节(16位),适合表示较小的整数。范围大约为 -2^15 到 2^15 - 1。
long:long 是长整型,通常占用4或8个字节(32位或64位),用于表示较大的整数。范围大约为 -2^31 到 2^31 - 1 或 -2^63 到 2^63 - 1,具体取决于编译器和操作系统。
long long: long long 是更大的整数类型,通常占用8个字节(64位),用于表示更大范围的整数。范围大约为 -2^63 到 2^63 - 1。
unsigned int: unsigned int 是无符号整数类型,与 int 类型具有相同的大小,但仅表示非负整数,范围为 0 到 2^32 - 1 或 0 到 2^64 - 1。
unsigned short、unsigned long、unsigned long long: 这些是无符号的短整型、长整型和更大的整数类型,用于表示非负整数。
367.有效的完全平方数
题目: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
记得移位操作的优先级很低,注意加括号!
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 1;
int right = num;
while(left <= right){
int middle = left +((right-left)>>1);
if((long long) middle * middle < num){
left = middle + 1;
}else if((long long) middle * middle > num){
right = middle - 1;
}else{
return true;
}
}
return false;
}
};
移除元素
27.移除元素
题目:题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章:代码随想录
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// int fast = 0;
int slow = 0;
int n = nums.size()-1;
for (int fast = 0; fast<=n; fast++){
if (val != nums[fast]){
nums[slow++]=nums[fast];
// slow = slow + 1;
}
}
return slow;
}
};
26.删除有序数组中的重复项
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if (n == 0) {
return 0;
}
int slow = 1;
int fast = 1;
for (; fast < n ;fast ++){
if (nums[fast] != nums[slow-1])
nums[slow ++] = nums[fast];
}
return slow;
}
};
283.移动零
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0;
int fast = 0;
int i = 0;
int n = nums.size();
for( ; fast < n ; fast++){
if(nums[fast] != 0){
nums[slow++] = nums[fast];
}
//不能左右一起变,因为是同一个数组
// else{
// nums[n-i-1] = 0;
// i++;
// }
}
while( slow < n){
nums[slow++] = 0;
}
}
};
844. 比较含退格的字符串
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class Solution {
public:
bool backspaceCompare(string s, string t) {
return res(s)==res(t);//c++中字符串可以直接比较是否相同
}
string res(string str){
int slow = 0;
int fast = 0;
for(; fast<str.size(); fast++){
if(str[fast] != '#'){
str[slow++]=str[fast];
}
else if (slow > 0){//第一个是#的话,无效
slow--;
}
}
return str.substr(0,slow);//记住字符串的这个函数
}
};
其他
977.有序数组的平方
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章讲解:代码随想录
视频讲解: 双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
int right = n - 1;
int left = 0;
int i = 0;
vector<int> result(n , 0);
while(left <= right){
if((long)nums[left] * nums[left] >= (long)nums[right] * nums[right]){
result[n - 1 - i] = nums[left] * nums[left];
left ++ ;
}
else{
result[n -1 - i] = nums[right] * nums[right];
right -- ;
}
i++;
}
return result;
}
};
209.长度最小的子数组【滑动窗口】
题目建议: 本题关键在于理解滑动窗口,这个滑动窗口看文字讲解 还挺难理解的,先看视频讲解。 拓展题目。
题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
文章讲解:代码随想录
视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili
这种方法更好一些~!
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int res =INT32_MAX;
int left = 0;
int right = 0;
int sum = 0;
int subLength = 0;
while(right<nums.size()){
sum += nums[right];
while(sum >= target){
sum -= nums[left];
subLength = right - left + 1;
res = res < subLength? res : subLength;
left ++;
}
right++;
}
// return res < INT32_MAX? res : INT_MAX;
return res == INT32_MAX? 0 : res;
}
};
注意:
+= 中间不能有空格
right++别忘了写了
另一种方法:
注意:如果条件设置为 while (left < nums.size() && right < nums.size()),那么当 right 指针达到数组的末尾时,即使 left 指针还没有遍历完整个数组,循环也会停止。这会导致算法错过在数组后半部分可能存在的更短的子数组。
class Solution {
public:
int minSubArrayLen(int target, std::vector<int>& nums) {
int left = 0, right = 0;
int sum = 0, length = INT_MAX;
while (left < nums.size()) {
if (sum < target) {
if (right == nums.size()) break; // 防止越界
sum += nums[right++];
} else {
length = std::min(length, right - left); //题干:大于等于
sum -= nums[left++];
}
}
return length == INT_MAX ? 0 : length;
}
};
59.螺旋矩阵II【难】
题目建议: 本题关键还是在转圈的逻辑,在二分搜索中提到的区间定义,在这里又用上了。
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
文章讲解:代码随想录
视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 1; // 用来给矩阵中每一个空格赋值
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < n - offset; j++) {
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};
//error
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n,vector<int>(n));
int loop = 0, num = 1;
while(loop < n/2){
int x0 = loop, y0 = loop, xd = n - loop, yd = n - loop -1;
int i = x0;
int j = y0;
for( ; i == x0 && j <= yd - 1; j++){
res[i][j] = num ++;
}
for( ; j == yd && i <= xd - 1; i++){
res[i][j] = num ++;
}
for(; i == xd && j >= y0 ; j--){
res[i][j] = num ++;
}
for(; j == y0 && i <= x0; i--){
res[i][j] = num ++;
}
loop ++;
}
return res;
}
};
54.螺旋矩阵【矩阵不是正方形的情况】:
class Solution {
public:
vector<int> spiralArray(vector<vector<int>>& array) {
if(array.size() == 0) return {};
vector<int> vec;
int l = 0, r = array[0].size() - 1, t = 0, b = array.size() - 1;
while(true){
for(int i = l; i <= r; i++) vec.push_back(array[t][i]);
t++;
if(t > b) break;
for(int j = t; j <= b; j++) vec.push_back(array[j][r]);
r--;
if(r < l) break;
for(int i = r; i >= l; i--) vec.push_back(array[b][i]);
b--;
if(b < t) break;
for(int j = b; j >= t; j--) vec.push_back(array[j][l]);
l++;
if(l > r) break;
}
return vec;
}
};
总结
文章链接:代码随想录