每日两道力扣,day4

每日两道力扣,day4

每日两道力扣,day4

每日两道力扣,day4

在这里插入图片描述

每日两道力扣,今日是:

74. 搜索二维矩阵 - 力扣(LeetCode)

4. 寻找两个正序数组的中位数 - 力扣(LeetCode)

第一题:搜索二维矩阵

74. 搜索二维矩阵 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述
1.思路:

基于咱们前三天的努力,想必各位大佬也大致摸清了二分查找的应用场景。这个题依旧是搜素 + 有序数组,所以咱们直接掏一手二分查找拿下他。可是这个题有点硬,咱们得细细分析,才能做到万无一失。

(1)方法一:既然咱们已经知道需要运用二分查找,面对这个二维数组,而且数据要求并不是那么的苛刻,咱们其实可以考虑运用for循环,一行一行进行二分查找(或一列一列,因为小编功力有限,咱们这里采用一行一行进行二分查找)。因为这个二维数组的高是m,宽是n。所以按照这个思路进行二分查找的时间复杂度是O(mlogn)。依旧超过了100.00%的人。

(2)方法二:但你要是问我有没有更快的方法。我的回答是:“有的有的,兄弟”,在方法一中咱们执行了m次二分查找。可是咱们大可以用一次二分查找就秒掉这个题。降维打击 —— 当成一个一维数组来二分,仔细看题目的两个条件:

  1. 每行从左到右递增。
  2. 每行的第一个整数大于前一行的最后一个整数。

这两个条件连在一起说明什么?说明如果你把这个矩阵像贪吃蛇一样拉直,它完完全全就是一个完美的升序一维数组!既然它本质是个一维有序数组,我们为什么不直接对“整个矩阵”做一次二分查找呢?

核心魔法:一维下标转二维坐标

假设矩阵是 M M M 行 N N N 列。一维数组的下标 idx,怎么还原成二维矩阵里的行 row 和列 col 呢?

  • 行号 row = idx / n (除了列数,就知道在第几行)
  • 列号 col = idx % n (对列数取余,就知道在该行的第几列)

注:row是高,column是宽。

2.代码实现:

(1)方法一:

class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { int m = matrix.size(),n = matrix[0].size(); bool res = false; for(int i = 0; i < m;i++) { int left = 0, right = n-1; while(left <= right) { int mid = left + (right - left)/2; if(matrix[i][mid] > target) { right = mid - 1; } else if(matrix[i][mid] < target) { left = mid + 1; } else { res = true; break; } } if(res == true) break; } return res; } }; 

(2)方法一:(优化版)

class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { int m = matrix.size(), n = matrix[0].size(); for(int i = 0; i < m; i++) { int left = 0, right = n - 1; while(left <= right) { int mid = left + (right - left) / 2; if(matrix[i][mid] > target) { right = mid - 1; } else if(matrix[i][mid] < target) { left = mid + 1; } else { // 修复:找到了直接返回 true,结束战斗! return true; } } } // 如果所有的行都找完了还没 return true,说明真的没有 return false; } }; 

(3)方法二:

class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { int m = matrix.size(); int n = matrix[0].size(); // 虚拟的一维数组的首尾指针 int left = 0; int right = m * n - 1; while(left <= right) { int mid = left + (right - left) / 2; // 魔法:把一维的 mid 还原成二维的行列坐标 int row = mid / n; int col = mid % n; int mid_val = matrix[row][col]; // 下面就是最最普通的标准二分查找了! if(mid_val == target) { return true; } else if(mid_val < target) { left = mid + 1; } else { right = mid - 1; } } return false; } }; 
3.细节
在这里插入图片描述

如果我是力扣出题人,我为了恶心人。我会毫不犹豫得扩大 m ,n。使得方法一运行超时。由此这个题就变成了hard题。不会方法二的人,看到了这个题就只能干瞪眼,在心里咒骂出题的臭老头(小编今年刚好18,正值青春大好年华,可不是臭老头,顶多是个死宅男)

可惜了,可惜我不是。

第二题:寻找两个正序数组的中位数

4. 寻找两个正序数组的中位数 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述
1.思路

hard题…作为一个力扣才写了80多道的初学者,小编也只能尝试一下。讲一下小编遇见这个题的第一思路:我想创建一个大小为(m+n)的数组nums3用来合并nums1,nums2,然后直接在nums3内部直接二分查找就ok了,可事实真的如此嘛?我这种方法的时间复杂度是O(m+n),不符合题目要求.

难道普通人真的就写不出hard题了吗?我不信,然后我尝试了半小时。没有任何思绪,还是功夫不够深。我暂时认了。

于是,我求助了gemini,它提出了一种我从所未见的方法。

寻找“完美切割线”。

(1)我们不需要真的合并数组。中位数的本质是什么?中位数就是一条“切割线”,它把所有数字分成了长度相等的左右两半,并且左半边的所有数字都小于右半边的所有数字。

假设我们在 nums1 的第 i i i 个位置切一刀,在 nums2 的第 j j j 个位置切一刀。

切割线左边共有 i + j i + j i+j 个元素,右边也有这么多元素。

为了让左右两半元素个数相等(或左边多 1 个),必须满足:

i + j = m + n + 1 2 i + j = \frac{m + n + 1}{2} i+j=2m+n+1​

既然 j j j 可以通过 i i i 算出来,我们只需要在较短的那个数组(假设是 nums1)中,对切割线的位置 i i i 进行二分查找就行了!

(2)什么样的切割线是“完美的”?

切割线左边的最大值,必须小于等于右边的最小值。也就是产生交叉对比:

  1. nums1 切割线左边元素 ≤ \le ≤nums2 切割线右边元素
  2. nums2 切割线左边元素 ≤ \le ≤nums1 切割线右边元素

即切完之后,切口附近有 4 个关键数字:

  • nums1 切口左边的最大值:nums1LeftMax
  • nums1 切口右边的最小值:nums1RightMin
  • nums2 切口左边的最大值:nums2LeftMax
  • nums2 切口右边的最小值:nums2RightMin

因为数组本身就是有序的,所以 nums1LeftMax 肯定 ≤ \le ≤nums1RightMin

要想满足“总左半边 ≤ \le ≤ 总右半边”,我们只需要交叉对比

  • 必须满足:nums1LeftMax <= nums2RightMin
  • 必须满足:nums2LeftMax <= nums1RightMin

只要这两个交叉条件同时成立,恭喜你,这“一刀切”就是完美的!(对应代码里的 if (nums1LeftMax <= nums2RightMin && nums2LeftMax <= nums1RightMin))

(3)用二分查找来“挪动”这把刀

既然 j j j 是由 i i i 决定的,那问题就变得极其简单了:我们只需要在 nums1 这个数组里,寻找正确的切割点 i i i。

怎么找 i i i 最快?用二分查找!

初始状态:left = 0, right = m。每次试一个中点 i = left + (right - left) / 2

试了一刀后,看看交叉对比的结果:

  1. 完美命中:交叉条件都满足。直接算中位数!(奇数就取左边两个的最大值,偶数就取左边最大和右边最小的平均值)。
  2. nums1LeftMax > nums2RightMin:这说明 nums1 左边的数字太大了。既然大了,说明我们在 nums1 里切得太靠右了,划进来了太多大数字。动作:刀往左挪,right = i - 1;
  3. 否则(nums2LeftMax > nums1RightMin:这说明我们在 nums2 左边的数字太大了。因为 j j j 和 i i i 是此消彼长的,nums2 左边太大,说明 j j j 太大,也就说明我们在 nums1 里切得太靠左了, i i i 给得太少了。动作:刀往右挪,left = i + 1;

(最后,代码里那几个 INT_MININT_MAX 是干嘛的?如果一刀切在了数组最边缘,左边没数字了,就当它是极小值 − ∞ -\infty −∞;右边没数字了,就当它是极大值 + ∞ +\infty +∞​。这样就不妨碍我们进行交叉对比,也不会发生越界报错。)

2.代码实现:
#include <vector> #include <algorithm> #include <climits> using namespace std; class Solution { public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { // 强制保证 nums1 是较短的数组,这样我们只在短数组上做二分,速度最快,也最安全 if(nums1.size() > nums2.size()) { return findMedianSortedArrays(nums2,nums1); } int m = nums1.size(); int n = nums2.size(); // 分割线左边应该有的总元素个数 int totalLeft = (m + n + 1)/2; // 在 nums1 的区间 [0, m] 里二分查找切割线位置 i int left = 0, right = m; while(left <= right) { // nums1 的切割点 int i = left + (right - left)/2; // nums2 的切割点(由 i 推导出来) int j = totalLeft - i; // 获取切割线左右两侧的 4 个元素 // 如果切割线在最边缘,为了不越界,赋予极值(INT_MIN 或 INT_MAX) int nums1LeftMax = (i==0) ? INT_MIN : nums1[i-1]; int nums1RightMin = (i==m) ? INT_MAX : nums1[i]; int nums2LeftMax = (j==0) ? INT_MIN : nums2[j-1]; int nums2RightMin = (j==n) ? INT_MAX : nums2[j]; // 灵魂拷问:这条切割线完美吗? if(nums1LeftMax <= nums2RightMin && nums2LeftMax <= nums1RightMin) { // 完美!已经找到了正确的切割线 // 如果总长度是奇数,中位数就是左半边最大的那个元素 if((m + n) % 2 == 1) { //这里编译器帮我做了隐式类型转换,将int优化为double return max(nums1LeftMax,nums2LeftMax); } // 如果总长度是偶数,中位数是 (左半边最大 + 右半边最小) / 2.0 else { return (max(nums1LeftMax,nums2LeftMax) + min(nums1RightMin,nums2RightMin)) / 2.0; } } // 如果 nums1 左边的元素太大了,说明 nums1 的切割线划得太靠右了,往左逼近 else if(nums1LeftMax > nums2RightMin) { right = i - 1; } // 否则说明 nums1 的切割线划得太靠左了,往右逼近 else { left = i + 1; } } // 语法需要,正常情况一定会在 while 里 return return 0.0; } }; 
3.细节

(1)我们必须保证nums1是一个短数组,不然数组会越界。

在这里插入图片描述
在这里插入图片描述

(2)关于切割点i可能的疑惑

在这里插入图片描述

(3)最后面我们一定要return 0.0;作为占位返回,来哄骗编译器,不然编译器这个认死理的家伙,跑到一半就给咱们这个完美的程序停了。

在这里插入图片描述

好了,今天的每日两道力扣到这里就算是结束了,看完是不是感觉有所收获呢?如果学有所获的话,麻烦给个三连支持一下呗。感谢观看,您的支持,将是我前进路上的重要动力。

Read more

ROS2 :Node 与 Topic 初探(Python)

在 ROS2 中,节点(Node) 和 话题(Topic) 是最基础、最常用的通信机制。掌握如何查询、监控、调试它们,是每一位 ROS2 开发者必备的技能。本文将简单介绍 ROS2 中 Node 和 Topic 的基本操作,包括命令行工具和 Python 代码实现,并教你如何快速定位“谁在发布、谁在订阅”。 1. 概念速览 * Node:ROS2 图中的一个计算单元,可以完成特定任务(如读取传感器、控制电机)。每个节点都有自己的命名空间。 * Topic:节点间传递消息的“总线”。节点可以发布(publish) 消息到某个话题,也可以订阅(subscribe) 该话题接收消息。话题采用异步、多对多的通信模式。 2.

RPGMZ游戏引擎 宠物战斗游戏基础功能实现

此文章为个人记录存储  要想用RPGMZ游戏引擎制作出宠物战斗系统的游戏 需要有以下几个特点 1. 玩家控制的角色不参与战斗 只有其他角色可以战斗 2. 需要在菜单界面 战斗界面 不显示玩家 只显示可战斗角色 正文 我们需要把玩家控制的角色设为编号1 并且用以下代码显示和隐藏 0 入队 1 离队 //0入队 1离队 编号1 $gameMap._interpreter.command129([1, 1, true]); 首先定义一个变量 let addActor_bool = false; 判断是菜单和战斗进入还是事件入队功能区分开 如果 addActor_bool == true 则加入到顶部第一个 否则加入到底部最后一个 在菜单编写代码 const _Scene_MenuBase_prototype_create = Scene_MenuBase.prototype.create Scene_

嵌入式就业岗位有哪些?

嵌入式就业岗位细分可以分为驱动、应用、单片机等方面。 今天帮你分清嵌入式三大核心岗位(驱动/应用/单片机)的区别,包括工作内容、能力要求、就业方向和薪资参考,帮你精准定位,避开投递误区,选对适合自己的赛道。 * 单片机开发:“控制硬件的‘手脚’”——直接操作芯片外设(GPIO、串口、定时器),实现基础控制功能,多是裸机或简单RTOS开发,不涉及复杂操作系统。 * 嵌入式应用开发:“让硬件‘有灵魂’”——在操作系统(Linux/RTOS)上,开发业务逻辑、实现具体功能(如数据采集、网络通信、界面显示),不直接操作底层硬件。 * 嵌入式驱动开发:“搭建硬件与软件的‘桥梁’”——编写驱动程序,让操作系统(Linux)能识别和控制硬件(如串口、ADC、WiFi模块),衔接底层硬件和上层应用。 简单类比:单片机开发是“

处理通用产品时使用变量

嗨!在今天的课程中,我们将继续学习仿制药。碰巧的是,这是一个大话题,但无法回避它——这是语言中极其重要的一部分:)当您学习甲骨文关于通用文档或阅读在线教程时,您将遇到不可重复类型和可重复类型这两个术语。可重复类型是指信息在运行时完全可用的类型。在Java中,此类类型包括原始类型、原始类型和非通用类型。相比之下,不可重新的类型是指信息被删除并在运行时无法访问的类型。碰巧的是,这些是泛型——List<String>、List<Integer>等。 顺便说一句,你还记得什么是varargs吗? In case you forgot, this is a variable-length argument. They are useful in situations where we don't know how many