代码随想录算法训练营第一天|704.二分查找、27.移除元素。

704.二分查找

链接:704. 二分查找 - 力扣(LeetCode)

  1. 初步代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size() - 1;
        int i = n;
        while (i >= 0) {
            if (nums[i] < target) {
                i = (i + n) / 2;
            } else if (nums[i] > target) {
                i = (i + 1) / 2;
            } else {
                return i;
            }
        }
        return -1;
    }
};
  1. 修改后代码
class Solution {

public:

    int search(vector<int>& nums, int target)
    {
        int n=nums.size()-1;
        int i=0;
        int j=n;
    while(i<=j)
    {
        int left=i;
        int right=j;
        int mid=(left+(right-left)/2);
        if(nums[mid]<target)
        {
            i=mid+1;
        }
        else
        if(nums[mid]>target)
        {
            j=mid-1;
        }
        else
        {
        return mid;
        }
    }
    return -1;
    }
};
  1. 我的问题
    1. 首先是while语句后面加不加逗号的问题
    2. 其次是二分法边界的问题
      1. 代码随想录 (programmercarl.com)
    3. 为什么mid取中值,用mid=L+(R-L)/2而不是mid=(L+R)/2
      1. 两个整数取中间值问题_求两个数的中间数_蒹葭_csdn的博客-CSDN博客
  2. 代码随想录算法
    1. 二分查找
      1. 二分法前提条件
        1. 数组为有序数组
        2. 数组中无重复元素
      2. 循环不变量规则:二分法区间定义就是不变量,每一次边界的处理都要坚持根据区间的定义来操作
      3. 区间定义
        1. 左闭右闭
          1. 循环条件:while(left<=right),因为闭区间中两个相等是有意义的(即是可以选到值的)
          2. 选择条件:if(nums[mid]>target),那么right=mid-1。因为nums(mid)已经被遍历过了,一定不是target,那么就要把范围缩小到mid-1。
        2. 左闭右开
          1. 循环条件:while(left<right),因为开区间是不可能大于和小于的是同一个值得。然后再在开区间里面寻找的时候
          2. 选择条件:if(nums[mid]>target)那么就让right=mid,因为右开,而mid被遍历过,而mid-1没有被遍历过。
  3. 时间复杂度计算
    1. 假设一个数组长度为n,
      1. 第一次查找后数组长度变成n/2;
      2. 第二次查找后数组长度变成n/2^2
      3. 第k次查找后数组长度变成n/2^k
      4. 最坏情况下,数组长度变成1
      5. n/2^k'=1=>k'=log2(n)。即查找n次
  4. 问题回顾
    1. while语句后面加逗号的话,while循环就会陷入死循环
    2. 好了,现在第二个问题可以回答了,二分法划分是都可以的,关键是你要把所有的数据都包括进去,这里面要注意全部遍历,mid的赋值,然后循环条件中=是否要加的这两个问题。
    3. 为什么mid取中值的原因是因为溢出
  5. 新的底层问题
    1. 计算机溢出原理
  6. 代码问题
    1. vector <int>&nums 创建指向int类型的指针

     vector<int>& nums 是一个函数参数,它表示一个传入的整数向量(也就是动态数组)的引用。

让我来解释一下:

  • vector<int>:这是 C++ 标准库中的一个容器,用于存储整数类型的数据。它类似于数组,但具有更多的功能和灵活性。
  • &:这是引用运算符,表示传递的是变量的引用,而不是变量的副本。通过引用传递可以避免复制大量数据,提高性能。
  • nums:这是变量的名称,通常用于表示传入的整数向量。
  1. 代码语法问题
    1. 变量的引用&,为什么是引用呢,感觉好奇怪的样子。
    2. 容器是什么
    3. &传递变量的引用在哪一章
    4. 动态数组在哪一章

27.移除元素

链接:27. 移除元素 - 力扣(LeetCode)

  1. 看到这道题的时候我的思路
    1. 首先对数组所有元素排序
      1. CHATGPT:对数组所有元素排序: 这一步骤实际上是不必要的,题目并没有要求对数组进行排序,而且排序会带来额外的时间复杂度。题目只是要求移除特定元素,并不需要改变其他元素的顺序。
      2. 我的理解:就是题目没有要求排序仅仅要求移除特定元素就不用排序。因为排序算法的时间复杂度是nlogn.特别是在移动元素的情况下。可能在某些情况下,先排序可能会让后续的算法更高效,因为排序后可以利用有序数组的性质来进行更快的操作。但是在这个特定问题中,双指针法可能更直接高效。综合考虑,直接解决问题的方法往往更为优雅和高效。
    2. 用二分法找到VAL
      1. CHATGPT:用二分法找到VAL: 二分法在有序数组中查找特定元素是一个不错的方法,但前提是数组必须是有序的。在这个题目中,并没有要求数组有序,所以使用二分法查找并不适用。
      2. 前面否定了排序的必要性,二分法要求数组有序,所以这里也不能用二分法了
    3. 删除val,val后面元素往前移
      1. CHATGPT:删除 val,val 后面元素往前移: 删除元素并将后面的元素往前移动确实是一种处理方法,但这样的操作需要移动数组中的元素,导致时间复杂度较高。题目要求的是移除特定元素,可以通过更高效的方式来实现。
    4. 检查元素是否等于val
      1. CHATGPT:检查元素是否等于 val 并进行操作: 这一步骤在实际操作中可能会导致逻辑复杂化。如果你在遍历中不断检查元素是否等于 val 并进行相应操作,可能会增加代码的复杂性,降低代码的可读性。
    5. 如果不等于,则指针往前移动,检查元素是否等于val
    6. 如果等于,统计数组长度
  2. 双指针算法:
  3.    在移除数组元素这道题中,更简洁且高效的方法是使用双指针法。具体做法是,用一个指针遍历数组,另一个指针指向当前有效元素应该放置的位置。如果当前遍历的元素不等于目标值,就将它放在第二个指针的位置,然后两个指针都向前移动。如果当前遍历的元素等于目标值,就只移动第一个指针。最终,第二个指针之前的元素都是有效的,可以得到最终的数组长度。

        错误代码 

  1. 	class Solution {
    	public:
    	    int removeElement(vector<int>& nums, int val) {
    	int i=0;
    	for(int j=0,j<nums.size,j++) //错误1:for语句中,分号;用来隔断初始化条件,循环语句,和迭代语句。
    	                             //错误2:nums.size()是函数,用来得到nums数组的长度
    	{
    	    if(nums[j]!=val)
    	    //错误3:if条件语句后面的语句应该用花括号括起来,这样保证它们一起在满足选择条件时一起被执行。
    	     nums[i]=nums[j];
    	     i++;
    	    
    	   
    	}
    	return i;
    	    }
    	};
    
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    代码随想录

    • 元素在内存地址中式连续的,不能单独删除数组中的某个元素,只能覆盖。
    1. 双指针法(快慢指针法):通过一个快指针和一个慢指针在一个for循环下完成两个for循环的工作
      1. 快指针(遍历):寻找新数组的元素,新数组就是不包含目标元素的数组。
      2. 慢指针:指向更新新数组(有效数组)下标的位置
  2. 时间复杂度:O(n)

    只有一个循环体,循环迭代次数为nums.size(),也就是n。所以是O(n)

    疑问:那这个题目for循环的循环体里面有三条语句if(nums[j]!=val) { nums[i]=nums[j]; i++; }。为什么时间复杂度不是O(3n)呢

    Chatgpt:因为在大O符号表示法中,我们通常只关注主导项,而忽略常数项,所以O(3N)和O(N)实际上是等价的,因为它们都表示线性时间复杂度。当我们分析算法的时间复杂度时,常数因子是不重要的,因为它不会随着问题规模的增长,算法的性能如何扩展,在大规模数据下,o(3n)和O(n)的性能几乎忽略不计。因此通常只关注主要的操作数量随输入规模的增长情况。

  3. 空间复杂度是衡量算法在执行过程中所需要的额外内存空间的度量。对于移除数组元素的问题,你提供的双指针法确实是 O(1) 的空间复杂度。

  4. 这是因为你的算法并没有使用额外的数据结构来存储元素,而是直接在原始数组上进行操作。算法只需要几个额外的变量,比如指针 ij 以及可能的临时变量。这些变量在算法的执行过程中只占用常数级别的额外空间,不会随着输入规模的增加而改变。

    无论输入数组的大小如何,算法所需的额外内存空间都保持不变,因此空间复杂度是 O(1),即常数级别的空间使用。这就是为什么你的双指针法的空间复杂度为 O(1)。

第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值