代码随想录算法训练营第一天| 数组理论基础,704. 二分查找,27. 移除元素

在训练营开始的第17天开始补第1天的内容也是没谁了。。。努力! 

时间空间复杂度

补充内容

数组理论基础

文档讲解:

状态:没什么问题

重点知识:数组是存放在连续内存空间内的相同类型数据的集合

重点:

内置数组类型

数组是非常基础的数据结构

数组是存放在连续内存空间上的相同类型数据的集合

数组可以方便的通过下标索引的方式获取到下标下对应的数据

 需要两点注意的是

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的

因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

如果使用C++的话,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。

数组的元素是不能删的,只能覆盖。

 在C++中二维数组也是连续分布的。

 


 704. 二分查找 

题目描述:

视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili

拓展题:

状态:已理解定义target在左闭右闭区间[left, right],左闭右开区间[left, right),区间的定义就是不变量。

重点知识:使用二分法的前提条件:数组为有序数组,且数组中无重复元素

因为定义target在[left, right]区间,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1 

因为定义target在[left, right)区间,所以有如下两点

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。


示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
 

提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。

重点:

使用二分法的前提条件:数组为有序数组,且数组中无重复元素

因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的)

1.二分法中target定义在一个左闭右闭区间[left,right]

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
// 版本一
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};
时间复杂度:O(log n)
空间复杂度:O(1)

2.二分法中target定义在一个左闭右开区间[left,right)

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
// 版本二
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};
时间复杂度:O(log n)
空间复杂度:O(1)

35.搜索插入位置

题目描述:

35.搜索插入位置

文档讲解:

代码随想录

视频讲解:

思路:

        目标值在数组所有元素之前

        目标值等于数组中某一个元素

        目标值插入数组中的位置

        目标值在数组所有元素之后

状态:看了一遍没问题,注意返回right + 1;

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

1. 暴力解法C++代码

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的长度
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

2. 二分法

既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。

使用二分法的前提条件:数组为有序数组,且数组中无重复元素

要在二分查找的过程中,保持不变量,这也就是循环不变量

(1)二分法第一种写法

定义 target 在[left, right]区间

while(left <= right),right = middle - 1

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0;
        int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle;
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前  [0, -1]
        // 目标值等于数组中某一个元素  return middle;
        // 目标值插入数组中的位置 [left, right],return  right + 1
        // 目标值在数组所有元素之后的情况 [left, right],这是右闭区间,所以  return right + 1
        return right + 1;
    }
};

时间复杂度:O(logn)
时间复杂度:O(1)

(2) 二分法第二种写法

定义 target 在[left, right) 区间

while (left < right),right = middle

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0;
        int right = n; // 定义target在左闭右开的区间里,[left, right)  target
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在 [middle+1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值的情况,直接返回下标
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前 [0,0)
        // 目标值等于数组中某一个元素 return middle
        // 目标值插入数组中的位置 [left, right) ,return right 即可
        // 目标值在数组所有元素之后的情况 [left, right),这是右开区间,return right 即可
        return right;
    }
};

时间复杂度:O(logn)

空间复杂度:O(1)

34.在排序数组中查找元素的第一个和最后一个位置

题目描述:

拓展题:

状态:从左右边界方向考虑

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 :
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

输入:nums = [], target = 0
输出:[-1,-1]

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
  • 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
  • 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}

这三种情况都考虑到,说明就想的很清楚了。

接下来,在去寻找左边界,和右边界了。

采用二分法来去寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。

刚刚接触二分搜索的同学不建议上来就想用一个二分来查找左右边界,很容易把自己绕进去,建议扎扎实实的写两个二分分别找左边界和右边界

#寻找右边界

先来寻找右边界,至于二分查找,如果看过704.二分查找 (opens new window)就会知道,二分查找中什么时候用while (left <= right),有什么时候用while (left < right),其实只要清楚循环不变量,很容易区分两种写法。

确定好:计算出来的右边界是不包含target的右边界,左边界同理。

这份代码在简洁性很有大的优化空间,例如把寻找左右区间函数合并一起。

但拆开更清晰一些,而且把三种情况以及对应的处理逻辑完整的展现出来了。

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) {
        return {searchLeftOrRightBound(nums, target, "left"), searchLeftOrRightBound(nums, target, "right")};
    }
private:
    int searchLeftOrRightBound(vector<int>& nums, int target, const string& bound) {
        int left = 0, right = nums.size() - 1;
        int res = -1;
        while (left <= right) {
            int mid = (left + right) >> 1;
            if (nums[mid] < target) {
                left = mid + 1;
            }
            else if (nums[mid] > target) {
                right = mid - 1;
            }
            else {
                res = mid;
                if (bound == "left") {
                    right = mid - 1;
                }
                else if (bound == "right") {
                    left = mid + 1;
                }
                else {
                    // 异常处理段
                }
            }
        }
        return res;
    }
};

69.x 的平方根

题目描述:

状态:

重点知识:

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 :
输入:x = 4
输出:2

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
    int mySqrt(int x) {
        if (x == 1) return 1;
        int left = 0, right = x;
        while (right - left > 1) {
            int middle = left + ((right - left) >> 1);
            if ( (long long)middle * middle > x) {
                right = middle;
            }
            else {
                left = middle;
            }
        }
        return left;
    }

367.有效的完全平方

题目描述:

状态:

重点知识:

给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如  sqrt 。

示例 :
输入:num = 16
输出:true
解释:返回 true ,因为 4 * 4 = 16 且 4 是一个整数。

输入:num = 14
输出:false
解释:返回 false ,因为 3.742 * 3.742 = 14 但 3.742 不是一个整数。
class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = 0, right = num;
        while (left <= right) {
            int mid = ((right - left) >> 1) + left;
            long square = (long) mid * mid;
            if (square < num) {
                left = mid + 1;
            } else if (square > num) {
                right = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
};
本人回答:
class Solution {
public:
    bool isPerfectSquare(int num) {
        if(num == 1) return 1;
        int left = 0;
        int right = num;
        while(right - left > 1){
            int middle = left + ((right - left) >> 1);
            if((long long) middle * middle > num){
                right = middle;
            }else if((long long) middle * middle < num){
                left = middle;
            }else{
                return true;
            }
        }
        return false;
    }
};


27. 移除元素

题目描述:

27. 移除元素

文档讲解:

27. 移除元素

视频讲解:数组中移除元素并不容易! | LeetCode:27. 移除元素_哔哩哔哩_bilibili

思路:暴力解法和快慢指针法没有改变数组位置,相向指针改变了数组的位置

状态:

相关题目推荐:

  • 26.删除排序数组中的重复项
  • 283.移动零
  • 844.比较含退格的字符串
  • 977.有序数组的平方

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。

示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

你不需要考虑数组中超出新长度后面的元素。

要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

暴力解法

这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

删除过程如下:

很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

双指针法

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

很多同学这道题目做的很懵,就是不理解 快慢指针究竟都是什么含义,所以一定要明确含义,后面的思路就更容易理解了。

删除过程如下:

 双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

// 时间复杂度:O(n)
// 空间复杂度:O(1)
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;
    }
};

注意这些实现方法并没有改变元素的相对位置!

/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.size() - 1;
        while (leftIndex <= rightIndex) {
            // 找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        return leftIndex;   // leftIndex一定指向了最终数组末尾的下一个元素
    }
};
/*
两个while循环中检查leftIndex <= rightIndex的条件。这个条件的作用是确保在数组中找到目标值val的左侧指针leftIndex不会超过右侧指针rightIndex。
*/

26.删除排序数组中的重复项

题目描述:26.删除排序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。

示例 :
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 :

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

暴力解法:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int size = nums.size();
        for (int i = 0; i < size - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--;
                size--;
            }
        }
        return size;
    }
};
时间复杂度:0(n^2)
空间复杂度:O(1)

快慢双指针法:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {                 
        int slowIndex = 1;
        for(int fastIndex = 1;fastIndex < nums.size();fastIndex++){
            if(nums[fastIndex] != nums[fastIndex - 1]){
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

283.移动零

题目描述:283.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:

输入: nums = [0]
输出: [0]
暴力解法:
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int size = nums.size();
        for(int i = 0;i < size;i++){
            if(nums[i] == 0){
                for(int j = i + 1;j < size;j++){
                    nums[j - 1] = nums[j]; 
                }
                i--;
                size--;
            }
        }
        for(int i = size;i < nums.size();i++){
            nums[i] = 0;
        }
    }
};

双指针法:
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slowIndex = 0;
        for(int fastIndex = 0;fastIndex < nums.size();fastIndex++){
            if(nums[fastIndex] != 0){
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        for(int i = slowIndex;i < nums.size();i++){
            nums[i] = 0;
        }
    }
};

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slowIndex = 0;
        for(int fastIndex = 0;fastIndex < nums.size();fastIndex++){
            if(nums[fastIndex] != 0){
                if(fastIndex > slowIndex){/*避免了数组开头是非零元素的交换也就是阻止(fastIndex==slowIndex)时交换。*/
                    nums[slowIndex] = nums[fastIndex];
                    nums[fastIndex] = 0;
                }
                slowIndex++;
            }
        }
    }
};

844.比较含退格的字符串

题目描述:844.比较含退格的字符串

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。

示例 :

输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"。

输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 ""。

输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 "c",但 t 仍然是 "b"。
暴力解法:
class Solution {
public:
    bool backspaceCompare(string s, string t) {
        int size = s.size();
        int size1 = t.size();

        if ((size == 1 && s[0] == '#') || (size == 2 && s[1] == '#')) {
            s = "";
        }
        if ((size1 == 1 && t[0] == '#') || (size1 == 2 && t[1] == '#')) {
            t = "";
        }
        for (int i = 0; i < size; i++) {
            if (s[i] == '#') {
                for (int j = i + 1; j < size; j++) {
                    s[j - 2] = s[j];
                }
                i -= 2;
                size -= 2;
            }
        }
        for (int i = size; i < s.size(); i++) {
            s[i] = ' ';
        }
        
        for (int i = 0; i < size1; i++) {
            if (t[i] == '#') {
                for (int j = i + 1; j < size1; j++) {
                    t[j - 2] = t[j];
                }
                i -= 2;
                size1 -= 2;
            }
        }
        for (int i = size1; i < t.size(); i++) {
            t[i] = ' ';
        }

        if (t == s) {
            return true;
        }else{
            return false;
        }
    }
};


双指针法:
// 挺难的
// 特殊用例
// a a#a 所以要用while (i >= 0 || j >= 0) 要用||,确保碰到满足false的情况,或者双方都遍历完毕
// a ba 返回false的第一种情况,双方skip均为0时进行比较,发现当且仅当一边出界了(可以都出界)
// aa aa# 这也是第一种情况
// a b 返回false第二种情况,双方skip均为0时进行比较,发现双方均未出界,但不相等
// 除了上面两种情况,一律返回true
// 另一难点,处理#的时候,退出循环的唯一条件就是skip == 0并且不为#

class Solution {
public:
    bool backspaceCompare(string S, string T) {
        int i, j, skipS, skipT;
        i = S.size() - 1;
        j = T.size() - 1;
        skipS = skipT = 0;
        while (i >= 0 || j >= 0) {
            while (i >= 0) {
                if (S[i] == '#') {
                    skipS++;
                    i--;
                }
                else if (skipS > 0) {
                    skipS--;
                    i--;
                }
                else break;
            }
            while (j >= 0) {
                if (T[j] == '#') {
                    skipT++;
                    j--;
                }
                else if (skipT > 0) {
                    skipT--;
                    j--;
                }
                else break;
            }
            // 此时双方的skip都是0,即应该进行判断(前提是都没出界)
            if (i >= 0 && j >= 0) {
                if (S[i] != T[j]) return false; // 返回false的第一个条件
            }
            else { // 至少有一个出界,包含 1. 都出界(不做处理) 2. 当且仅当一个出界(返回false的第二个条件)
                if (i >= 0 || j >= 0) { // 这行代码表示当且仅当一个出界
                    return false;
                }
            }
            // 下面这两行容易忘
            // 在经历了两轮考察(即判断你是否满足false的条件)之后,如果你满足,就两边各退一步
            // 我在官方的基础上加了if语句避免不必要的--操作,虽然不影响最后的答案,但是增强了可读性
            // 因为这里的--实质上是针对此时还未出界的指针进行的操作
            if (i >= 0) i--;
            if (j >= 0) j--;
        }
        return true; // 执行到这,说明遍历完两边,都没发现满足false的情况,故返回true
    } 
};

 

class Solution {
public:
    bool backspaceCompare(string s, string t) {
        return build(s) == build(t);
    }
    
    string build(string str){
        string ret;
        for(auto c : str){
            if(c != '#'){
                ret.push_back(c);
            }else if(!ret.empty()){
                ret.pop_back();
            }
        }
        return ret;
    }
};

977.有序数组的平方

 题目描述:977.有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 :

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 :
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
方法一:直接排序
class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for(int &c : nums){
            c *= c;
        }
        sort(nums.begin(),nums.end());
        return nums;
    }
};

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int size = nums.size();
        for(int i = 0;i < size;i++){
            long square = nums[i] * nums[i];
            nums[i] = square;
        }
        sort(nums.begin(),nums.end());
        return nums;
    }
};

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> ans;
        for (int num: nums) {
            ans.push_back(num * num);
        }
        sort(ans.begin(), ans.end());
        return ans;
    }
};
时间复杂度:O(nlog⁡n),其中 n 是数组 nums 的长度。
空间复杂度:O(log⁡n)。除了存储答案的数组以外,我们需要 O(log⁡n) 的栈空间进行排序。

排序操作:这部分使用了标准库的sort函数对ans向量进行排序,其时间复杂度为O(nlogn),其中n是ans向量的大小。排序操作的时间复杂度是由快速排序等高效排序算法所决定的。

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> ans;
        int n = nums.size();
        int negative = -1;
        for (int i = 0; i < n; i++) {
            if (nums[i] < 0) {
                negative = i;
            }
            else {
                break;
            }
        }
        int i = negative;
        int j = negative + 1;
        while (i >= 0 || j < n) {
            if (i < 0) {
                ans.push_back(nums[j] * nums[j]);
                j++;
            }
            else if (j == n) {
                ans.push_back(nums[i] * nums[i]);
                i--;
            }
            else if (-(nums[i]) < nums[j]) {
                ans.push_back(nums[i] * nums[i]);
                i--;
            }
            else {
                ans.push_back(nums[j] * nums[j]);
                j++;
            }
        }
        return ans;
    }
};

时间复杂度:O(n),其中 n 是数组 nums 的长度。

空间复杂度:O(1)。除了存储答案的数组以外,我们只需要维护常量空间

 

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)。除了存储答案的数组以外,我们只需要维护常量空间。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值