目录
(2)34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
(4)367. 有效的完全平方数 - 力扣(LeetCode)
(1)26. 删除有序数组中的重复项 - 力扣(LeetCode)
(3)844. 比较含退格的字符串 - 力扣(LeetCode)
(一)数组理论基础
vector和array的区别:vector的底层实现是array,严格来讲vector是容器,不是数组。
补充二维数组初始化和赋值:
vector<vector<int>> count(n, vector<int>(n, 0));
vector<int> temp;
for(int i = 0; i < n; i++){
temp.push_back(i);
}
for(int i = 0; i < n; i++){
count.push_back(temp);
}
(二)二分查找
1. 题目描述
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
2. 思路
前提是数组为有序数组,同时题目还强调数组中无重复元素,当看到题目描述满足如上条件的时候,考虑用二分法。二分法重点要想清楚区间的定义,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。(可以 mid = left + (right - left) / 2
防止溢出)
3. 解题过程
难易程度:简单
标签:数组、二分查找
class Solution {
public:
int search(vector<int>& nums, int target) {
// 左闭右开,[left, right)
int left = 0, right = nums.size();
// 坚持左闭右开,left < right
while(left < right){
int mid = (left + right) / 2;
if(target == nums[mid]){
return mid;
}
// mid比较过了,左闭右开所以left = mid + 1
else if(target > nums[mid]){
left = mid + 1;
}
// 同理,mid比较过,right不会取到,所以right = mid
else{
right = mid;
}
}
// 全部比较完成,不存在目标值
return -1;
}
};
4. 相关题目
(1)35. 搜索插入位置 - 力扣(LeetCode)
① 题目描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
② 解题过程
难易程度:简单
标签:数组、二分查找
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while(left < right){
int mid = (left + right) / 2;
if(nums[mid] == target){
return mid;
}
else if(nums[mid] > target){
right = mid;
}
else{
left = mid + 1;
}
}
return left;
}
};
(2)34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
① 题目描述
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
② 解题过程
难易程度:中等
标签:数组、二分查找
❗ 注意:输入是否为空、结束时直接使用 left 是否会导致越界
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0, right = nums.size();
vector<int> result(2);
while(left < right){
int mid = (left + right) / 2;
if(nums[mid] >= target){
right = mid;
}
else{
left = mid + 1;
}
}
// 踩坑,没有考虑target大于数组最大元素导致left越界
if(left==nums.size() || nums[left] != target){
result[0] = -1;
result[1] = -1;
}
else{
result[0] = left;
// 踩坑,没有考虑最后一个也是相同元素导致left越界
while(left < nums.size() && nums[left] == target){
left++;
}
result[1] = left - 1;
}
return result;
}
};
更好的办法:两次二分,找左右边界,根据三种情况,写结果。
(3)69. x 的平方根 - 力扣(LeetCode)
① 题目描述
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
② 解题过程
难易程度:简单
标签:数学、二分查找
Ⅰ. 二分查找
注意int范围,如果left、right和mid都是int类型,当x在int表示最大边界处,mid*mid会超出int的范围,可以使用强制类型转换(long long)解决,我选择直接定义为long long类型。
class Solution {
public:
int mySqrt(int x) {
long long left = 0, right = x;
while(left < right){
long long mid = (left + right) / 2;
if(mid * mid == x){
return mid;
}
else if(mid * mid > x){
right = mid;
}
else{
left = mid + 1;
}
}
if(left * left <= x){
return left;
}
return left - 1;
}
};
Ⅱ. 牛顿迭代
牛顿迭代法是一种可以用来快速求解函数零点的方法。
为了叙述方便,我们用 C 表示待求出平方根的那个整数。显然,C 的平方根就是函数的零点。
牛顿迭代法的本质是借助泰勒级数,从初始值开始快速向零点逼近。我们任取一个作为初始值,在每一步的迭代中,我们找到函数图像上的点,过该点作一条斜率为该点导数的直线,与横轴的交点记为。相较于而言距离零点更近。在经过多次迭代后,我们就可以得到一个距离零点非常接近的交点。
(4)367. 有效的完全平方数 - 力扣(LeetCode)
① 题目描述
给你一个正整数 num
。如果 num
是一个完全平方数,则返回 true
,否则返回 false
。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt
。
② 解题过程
难易程度:简单
标签:数学、二分查找
class Solution {
public:
bool isPerfectSquare(int num) {
long long i;
for(i = 1; i * i <= num; i++);
return (i - 1) * (i - 1) == num;
}
};
(三)移除元素
1. 题目描述
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素。元素的顺序可能发生改变。然后返回 nums
中与 val
不同的元素的数量。
假设 nums
中不等于 val
的元素数量为 k
,要通过此题,您需要执行以下操作:
- 更改
nums
数组,使nums
的前k
个元素包含不等于val
的元素。nums
的其余元素和nums
的大小并不重要。 - 返回
k
。
2. 思路
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
-
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
-
慢指针:指向更新 新数组下标的位置
3. 解题过程
难易程度:简单
标签:数组、双指针
我的思路:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
// 记录有多少个数值等于val的元素
int count = 0;
for(int i = 0; i < nums.size(); i++){
if(nums[i] == val){
count++;
}
// 遇到不相等的,让前面相等的被这个替换掉
else{
nums[i - count] = nums[i];
}
}
return nums.size() - count;
}
};
快慢指针法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
4. 相关题目
(1)26. 删除有序数组中的重复项 - 力扣(LeetCode)
① 题目描述
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
② 解题过程
难易程度:简单
标签: 数组、双指针
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int fast = 1, slow = 1;
int temp = nums[0];
for(; fast < nums.size(); fast++){
if(nums[fast] != temp){
nums[slow++] = nums[fast];
temp = nums[fast];
}
}
return slow;
}
};
(2)283. 移动零 - 力扣(LeetCode)
① 题目描述
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
② 解题过程
难易程度:简单
标签:数组、双指针
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0, fast = 0;
for(; fast < nums.size(); fast++){
if(nums[fast] != 0){
nums[slow++] = nums[fast];
}
}
// 剩下的填上0
for(; slow < nums.size(); slow++){
nums[slow] = 0;
}
}
};
(3)844. 比较含退格的字符串 - 力扣(LeetCode)
① 题目描述
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
② 解题过程
难易程度:简单
标签:栈、双指针、字符串、模拟
【string类型支持
pop_back()
弹出最后一个元素】
Ⅰ. 我的思路:
class Solution {
public:
// 把字符串的最终结果表示出来
string change(string s){
string result_s;
for(int i = 0; i < s.size(); i++){
if(s[i] != '#'){
result_s.push_back(s[i]);
}
else{
if(!result_s.empty()){
result_s.pop_back();
}
}
}
return result_s;
}
bool backspaceCompare(string s, string t) {
string result_s, result_t;
result_s = change(s);
result_t = change(t);
return result_s == result_t;
}
};
Ⅱ. 双指针法
双指针法:同时从后向前遍历
S
和T
(i
初始为S
末尾,j
初始为T
末尾),记录#
的数量,模拟消除的操作,如果#
用完了,就开始比较S[i]
和S[j]
。如果S[i]
和S[j]
不相同返回false
,如果有一个指针(i
或者j
)先走到的字符串头部位置,也返回false
class Solution { public: bool backspaceCompare(string S, string T) { int sSkipNum = 0; // 记录S的#数量 int tSkipNum = 0; // 记录T的#数量 int i = S.size() - 1; int j = T.size() - 1; while (1) { while (i >= 0) { // 从后向前,消除S的# if (S[i] == '#') sSkipNum++; else { if (sSkipNum > 0) sSkipNum--; else break; } i--; } while (j >= 0) { // 从后向前,消除T的# if (T[j] == '#') tSkipNum++; else { if (tSkipNum > 0) tSkipNum--; else break; } j--; } // 后半部分#消除完了,接下来比较S[i] != T[j] if (i < 0 || j < 0) break; // S 或者T 遍历到头了 if (S[i] != T[j]) return false; i--;j--; } // 说明S和T同时遍历完毕 if (i == -1 && j == -1) return true; return false; } };