day02 | 代码随想录 训练营

这是一个关于代码随想录训练营的博客,内容涵盖算法练习,主要涉及LeetCode上的问题,如二分查找、链表操作等。博主分享了不同链表问题的解决方案,包括移除元素、设计链表、反转链表等,并探讨了哈希表在解决实际问题中的应用,如查找字母异位词、求两个数组的交集和两数之和。
摘要由CSDN通过智能技术生成

目录

Day01

题目1

模板总结

练习

题目2

Day02

题目1 977. 有序数组的平方

题目2 209. 长度最小的子数组

题目3:59. 螺旋矩阵 II

Day03

题目1:移除元素

题目2:设计链表

题目3 反转链表

Day04

题目1 24. 两两交换链表中的节点

题目2  19. 删除链表的倒数第 N 个结点

面试题 02.07. 链表相交

142.环形链表

Day6 哈希表

242. 有效的字母异位词

349.两个数组的交集

202.快乐数

1.两数之和


Day01

题目1

704 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

class Solution {
    public int search(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;
        while(low <= high)
        {
            int mid = low + ((high - low) >> 1);
            if(nums[mid] > target){
                high = mid - 1;
            }else if(nums[mid] < target){
                low = mid + 1;
            }else{
                return mid;
            }
        }
        return -1;
    }
};
class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int right=nums.size();
        int left=-1;//请注意此种情况我们需要查找的区间是(left,right);
        int mid= left + ((right - left) >> 1);//这样写是为了防止int型数据过大导致溢出
        while(left+1<right) //当left=right-1时易知区间(left,right)为空,需要在此时跳出循环
        {
            if(nums[mid]==target) return mid;
            else if(nums[mid]>target)//证明答案只可能存在于区间(left,mid)中
                right=mid;//将要查找的区间改为(left,mid)
            else left=mid;//证明答案只可能存在于区间(mid,right)中,将要查找的区间改为(mid,right)      
            mid=(right-left)/2+left;
        }
        return -1;
    }
};

总结:C++中string的size与length的区别

在C++的string类中,有两种函数:length和size。他们的作用都是返回字符串的长度
那么,问题来了,他们两者有什么区别?
为了钻研,我们要先找到他们两者的源代码
让我们先找到length的源代码
首先,我们随便定义一个字符串,并调用length

#include <iostream>
#include <string>
using namespace std;
int main(){
	string s;
	s.length();
	return 0;
}

然后,重点来了!按住Ctrl,点击length,就会跳到C++的库文件(如果看不懂,就选中length,右键到实现)
我们就可以看到length的源代码

length() const _GLIBCXX_NOEXCEPT
{ return _M_rep()->_M_length; }

然后,按照同样的步骤找到size的源代码

size() const _GLIBCXX_NOEXCEPT
{ return _M_rep()->_M_length; }

可以看到两者的源代码一摸一样,所以这两者其实没有区别
但是为什么要搞两个呢?
其实string一开始只有length,这是C语言延续下来的习惯
而size则是在出现STL之后,为了兼容加入了和STL容器求长度的函数一个名字的size,这样就可以方便的使用于STL的算法
 

模板总结

用C++实现二分查找的算法:

模版三步走

1.设置判别函数check

2.根据check的结果来查看区间更新方式

3.查看区间更新方式来决定是否要+1

算法思路:

假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。

版本1

当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

版本2

当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

假设有一个总区间,经由我们的 check 函数判断后,可分成两部分,

这边以o作 true,.....作 false 示意较好识别

如果我们的目标是下面这个v,那麽就必须使用模板 1

................vooooooooo

假设经由 check 划分后,整个区间的属性与目标v如下,则我们必须使用模板 2

oooooooov...................

所以下次可以观察 check 属性再与模板1 or 2 互相搭配就不会写错啦

模板1和模板2本质上是根据代码来区分的,而不是应用场景。如果写完之后发现是l = mid,那么在计算mid时需要加上1,否则如果写完之后发现是r = mid,那么在计算mid时不能加1。

需不需要“+1”和check没关系,是根据check结束后所得的结果来判定到底是取上整还是取下整,如果取上整则该段程序都取上整,取下整则都取下整。其最终本质都是为了不重复的划分区间以二分找到最后的边界。详见链接

边界 [left, right] 的二分查找模板

int bSearch(vector<int>& arr, int target) {
    int left = 0, right = arr.size() - 1;
    while (left <= right) {
        // 使用下面代码代替 (left + right) >> 1 防止相加后整型数据溢出
        // >> 运算符的优先级比较小,要加括号
        int mid = left + ((right - left) >> 1);
        if (arr[mid] > target) {
            right = mid - 1;
        }else if (arr[mid] < target) {
            left = mid + 1;
        }else {
            return mid;
        }
    }
    return -1;    
}

边界[left, right)的二分查找模板

注意边界条件,[left, right] 和 [left, right) 在处理 while(left <= right) 和 if (arr[mid] > target) 的时候代码有所不同。

模板返回的是找到满足某个条件下的数的下标,如果没找到返回 -1,可以根据题目适当做改变。

变体一:查找第一个值等于给定值的元素

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == 0) || (a[mid - 1] != value)) return mid;
      else high = mid - 1;
    }
  }
  return -1;
}

变体二:查找最后一个值等于给定值的元素

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}

重点还是第11行代码,如果 a[mid] 这个元素已经是数组中的最后一个元素了,那它肯定是我们要找的;如果或者后一个值不等于给定值,那也说明 a[mid] 就是我们要找的最后一个值等于给定值的元素。

如果我们经过检查之后,发现 a[mid] 后面的一个元素 a[mid+1] 也等于 value,那说明当前的这个 a[mid] 并不是最后一个值等于给定值的元素。我们就更新 low=mid+1,因为要找的元素肯定出现在 [mid+1, high] 之间。

变体三:查找第一个大于等于给定值的元素

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] >= value) {
      if ((mid == 0) || (a[mid - 1] < value)) return mid;
      else high = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  return -1;
}

第10行,如果 a[mid] 小于要查找的值 value,那要查找的值肯定在 [mid+1, high] 之间,所以,我们更新 low=mid+1。

对于 a[mid] 大于等于给定值 value 的情况,我们要先看下这个 a[mid] 是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid] 前面已经没有元素,或者前面一个元素小于要查找的值 value,那 a[mid] 就是我们要找的元素。这段逻辑对应的代码是第 7 行。

如果 a[mid-1] 也大于等于要查找的值 value,那说明要查找的元素在 [low, mid-1] 之间,所以,我们将 high 更新为 mid-1。

变体四:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值