二分查找(leetcode.704)
情况一:给定区间为左闭右闭 [left,right]
初始left值为0,right值为numsize-1
循环条件 left <= right
更新右边界 mid-1 左边界 mid+1
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left <= right){
int mid = (right - left) / 2 + left;
int num = nums[mid];
if (num == target) {
return mid;
} else if (num > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
};
情况二:给定区间为左闭右开 [left,right)
初始left值为0,right值为numsize
循环条件 left < right
更新右边界 mid 左边界 mid+1
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while(left < right){
int mid = (right - left) / 2 + left;
int num = nums[mid];
if (num == target) {
return mid;
} else if (num > target) {
right = mid;
} else {
left = mid + 1;
}
}
return -1;
}
};
数组移除指定元素(leetcode.27)
解法一:双指针
class Solution {
public int removeElement(int[] nums, int val) {
int n = nums.length;
int left = 0;
for(int right = 0; right < n; right++){//如果不需要删除,什么都不用做,right继续移动就好
if(nums[right] != val){//不需要删除,直接移到下标为left处
nums[left] = nums[right]; left++;
}
}
return left;
}
}
解法二:双指针优化
题目只要求返回最终的数组大小,数组内的元素顺序可以任意
class Solution {
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length;
while(left < right){
if(nums[left] == val){//由于顺序不影响结果,可以直接把右端元素取代左端元素,通过循环实现不断取代
nums[left] = nums[right-1];
right--;
}else{
left++;//如果不需要删除,left移动即可
}
}
return left;
}
}
有序数组的平方(leetcode.977)
双指针
原数组从小到大排列且有负数,那么平方后最大值只能在两个边界取到,因此思路是从边界向中间靠拢,逐步走出大值并从动态数组末尾依次往前添加
class Solution {
public: vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
for (int i = 0, j = n - 1, pos = n - 1; i <= j;) {
if (nums[i] * nums[i] > nums[j] * nums[j]) {
ans[pos] = nums[i] * nums[i]; ++i;
} else {
ans[pos] = nums[j] * nums[j]; --j;
}
--pos;
}
return ans;
}
}
复杂度分析
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。
- 空间复杂度:O(1)。除了存储答案的数组以外,我们只需要维护常量空间。
推荐另一种形式
class Solution {
public: vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size() - 1;
vector<int> ans(n);
for (int i = 0, j = nums.size()-1 ; i <= j;) {
if (nums[i] * nums[i] > nums[j] * nums[j]) {
ans[n] = nums[i] * nums[i]; ++i;
} else {
ans[n] = nums[j] * nums[j]; --j;
}
--n;
}
return ans;
}
}
长度最小的子数组(leetcode.209)
滑动窗口
关键点:
- 子数组元素要连续
- 当满足sum>=target时,不能停止(即不能使用if),因为如1,1,1,1,1,100的情况,如果target为100,则只有end到100时才第一次满足sum>=target,但这实际上不是最短的子数组,辅以start往后移动以确保得到最短的子数组
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
if(n == 0){
return 0;
}
int ans = INT_MAX;
int start = 0, end = 0;
int sum = 0;
for(end = 0; end < n; end++){
sum += nums[end];
while(sum >= target){
ans = min(ans, end - start + 1);
sum -= nums[start];
start++;
}
}
return ans == INT_MAX ? 0 : ans;
}
};
复杂度分析
- 时间复杂度:O(n),其中 n 是数组的长度。指针 start 和 end 最多各移动 n 次。
- 空间复杂度:O(1)。
除自身以外数组的乘积(leetcode.75)
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> ans(nums.size(),1);
int left = 0, right = nums.size() - 1;
int lp = 1, rp = 1;
while(left < nums.size() && right >= 0){
// 某个位置的右边所有元素乘积
ans[right] *= rp;
rp *= nums[right--];
// 某个位置的左边所有元素乘积
ans[left] *= lp;
lp *= nums[left++];
}
return ans;
}
};
注意点:
- 巧妙于使用双指针分别求左右两边的元素乘积
递增的三元子序列(leetcode.75)
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int n = nums.size();
if (n < 3) {
return false;
}
vector<int> leftMin(n);
leftMin[0] = nums[0];
for (int i = 1; i < n; i++) {
leftMin[i] = min(leftMin[i - 1], nums[i]);
}
vector<int> rightMax(n);
rightMax[n - 1] = nums[n - 1];
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = max(rightMax[i + 1], nums[i]);
}
for (int i = 1; i < n - 1; i++) {
if (nums[i] > leftMin[i - 1] && nums[i] < rightMax[i + 1]) {
return true;
}
}
return false;
}
};
注意点:
- 遍历到某个元素,查找它的左边是否有小于它的元素、右边是否有大于它的元素
- 左边有小于它的元素,等价于左边的最小元素必定小于它
- 右边有大于它的元素,等价于右边的最大元素必定大于它
- 寻找左边的最小元素,可以采用递归的方法,等价于找在i-1处左边的最小元素与i进行比较。同理,寻找右边的最大元素也是递归比较
- 最后遍历每个位置判断条件即可
压缩字符串(leetcode.433)
class Solution {
public:
int compress(vector<char>& chars) {
int n = chars.size();
int write = 0, left = 0;
for(int read = 0; read < n; read++){
// 如果遍历到了最后一个字符 或者 到达了相同元素字符串的末尾
if(read == n - 1 || chars[read] != chars[read + 1]){
// 将字符存入chars数组
chars[write++] = chars[read];
// 字符重复出现的次数
int num = read - left + 1;
// 如果重复出现的次数大于1,则需要压缩
if(num > 1){
// 记录重复字符的次数下标
int anchor = write;
// 短除法----倒序地存入重复次数
while(num > 0){
// 得到的各位数字 + '0' 转为字符
chars[write++] = num % 10 + '0';
num /= 10;
}
// 反转倒序的次数
reverse(&chars[anchor],&chars[write]);
}
left = read + 1;
}
}
return write;
}
};
注意点:
- 为啥要用短除法倒序地存入,还不是很理解(TODO)
- 数字值 + 0的ASCII码(48) = 对应数字的字符ASCII码