算法纪实|Day1

数组01

理解数组

​ 数组是存放在连续内存空间上的相同类型数据的集合,不难看出数组的一些特点,数组内的元素,数据类型相同;数组存放在连续的存储空间,可以实现随机存取;也是因为存放在连续的存储空间,导致数组元素不能删除只能覆盖。

​ 由此,不难理解,当我们对数组进行删除、增加,就会涉及到其它元素的移动,这也是我们对数组进行运用的一个关键所在。

LeetCode 704-二分查找

二分查找

思考

​ 二分查找也称折半查找,作用于去重的有序序列,一种较为快速的查找方式,主要思路为,每次查找,都与序列中位数mid进行比较,然后舍去不满足条件的一半,在满足条件的半个序列中重复述操作,直至中位数mid等于目标值或者left>right没有目标值。

算法实现
区间为左闭右闭的写法:
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;//中位数,这样计算也是满足题目要求的
            // 如果元素的数值再扩大,为避免溢出,处理如下
            int middle = left + ((right - left)>>2);
            if(nums[middle] > target){
                right = middle -1;//目标值小于中位数,在左侧,[left,middle - 1]
            }else if(nums[middle] < target){
                left = middle + 1;//目标值大于中位数,在右侧,[middle + 1,right]
            }else{
                return middle;//目标值等于中位数,返回中位数
            }
        }
        return -1;//没找到目标值
    }
};
区间为左闭右开的写法:
class Solution {
public:
    int search(vector<int>& nums, int target) {//左闭右开的情况
        int left = 0;
        int right = nums.size();
        while(left < right){
            int middle = left + ((right - left)>>2);
            if(nums[middle] > target){
                right = middle;//目标值小于中位数,在左侧,[left,middle)
            }else if(nums[middle] < target){
            left = middle + 1;//目标值大于中位数,在右侧,[middle + 1,right)   
            }else{
                return middle;
            }
        }
        return -1;
    };
};

​ 时间复杂度:假设总数组长度为n,那么第k次循环过后长度变为n/2(k),不难看出n/2(k)>=1,那么k<=logn,时间复杂度为O(logn)

​ 空间复杂度:创建了三个变量,粗略的认为,空间复杂度为O(1)

总结

​ 中间值的计算,按照题目的范围,正常计算也是满足的,考虑到范围扩大到int的最大值,换成左端点加偏移量的形式可避免溢出;位运算代替除法,可以提高代码的运行效率。

​ 二分查找的就业场景,查找某个数据,且数据列表为去重有序序列。

​ 学习对于数组边界的处理,理解循环不变量规则,初始为左闭右开后续循环内就都是按照左闭右开进行处理。

LeetCode 27-移除元素

移除元素

思考

​ 不使用额外的数组空间,也就是要求原地删除,放弃拷贝数组删除的想法,那么不妨暴力直接删除,那么难点就是数组理论中说明的,不可删除,只可覆盖,遍历数组找到目标元素,把后续的元素向前移动(此时又是一遍遍历,就是不是从头遍历而已),然后继续遍历。

​ 我们来继续思考一下,可不可以只遍历一遍,假设我们设置两个指针,不是目标元素且两指针指向同一元素时,两指针同时向下遍历,当遇到目标元素时,快指针继续向下遍历,慢指针暂停一轮,如果此时快指针指向的元素不是目标元素,快慢指针元素交换,并同时继续向下遍历;如过快指针指向的是目标元素,慢指针继续暂停一轮,快指针向下遍历,重复上述操作,当快指针遍历完整个数组时,慢指针指向的长度就是删除目标数据后的长度。

算法实现
暴力解
class Solution {//暴力解
public:
    int removeElement(vector<int>& nums, int val) {
        int len = nums.size();
        for(int i = 0; i < len; i++){//第一层循环,寻找需要删除的元素
            if(nums[i] == val){
                for(int j = i; j < len - 1; j++){//把需要删除的元素都往前移
                    nums[j] = nums[j + 1];
                }
                i--;//把nums[i]删除后,需要重新遍历第i个元素
                len --;//删除一个元素,长度减一
            }
            
        }
        return len;
    }
};

​ 时间复杂度,不难看出,算法存在两次层嵌套的for循环,第一个循环为n轮,第二个循环为n-i-1轮,时间复杂度为O(n²)

快慢指针法
class Solution {//双指针
public:
    int removeElement(vector<int>& nums, int val) {
        int fast = 0;
        int slow = 0;
        for(fast; fast < nums.size(); fast++){//快指针会遍历完整个数组
            if(fast == slow && nums[fast] != val){//当快慢指针相等时且指向的元素不是目标元素时,快慢指针同时向后移动
                slow++;
            }else if(nums[fast] != val){//当快慢指针不相等,且快指针指向不是目标元素时,快慢指针指向元素替换,并同时向后移动
                int a = nums[slow];
                nums[slow] = nums[fast];
                nums[fast] = a;
                slow++;
            }//当快指针指向元素为目标元素时,快指针向后移动,慢指针不变
        }
        return slow;
    }
};

​ 时间复杂度,只进行了一轮for循环,时间复杂度为O(n)

总结

​ 更深入的理解数组的特性,不能删除,只能覆盖。

​ 算法调优的一种思路,双指针,简化算法的循环轮数,从而提高算法效率。

​ 上述两种算法都保证了元素相对位置不变。

posted on 2023-05-17 17:37  那一抹紫筠  阅读(12)  评论(0)    收藏  举报