每日两道力扣,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

突破网页数据集获取难题:Web Unlocker API 助力 AI 训练与微调数据集全方位解决方案

突破网页数据集获取难题:Web Unlocker API 助力 AI 训练与微调数据集全方位解决方案

突破网页数据集获取难题:Web Unlocker API 助力 AI 训练与微调数据集全方位解决方案 背景 随着AI技术的飞速发展,诸如DeepSeek R1、千问QWQ32、文小言、元宝等AI大模型迅速崛起。在AI大模型训练和微调、AI知识库建设中,数据集的获取已成为不可或缺的基础。尤其是在面对各式各样的网页数据结构时,将其整理成可用的数据集是一项极具挑战的任务。开发者不仅需要付出大量的开发和人工成本,还需应对复杂的网页数据获取难题。在这种情况下,一款能够自动化解决网页数据获取问题的工具变得尤为重要。 本文将介绍网页解锁器Web Unlocker API、网页抓取Web-Scraper以及搜索引擎结果页SERP API等工具,特别适合中小企业解决商业化网页数据集问题,展示其如何解决AI数据集网页抓取的难题,提供高效、自动化的数据获取解决方案。 什么是Web Unlocker API工具? Web Unlocker API是基于Bright Data的代理基础设施开发的,具备三个关键组件:请求管理、浏览器指纹伪装和内容验证。通过这些功能,它能够自动化处理所有网页解锁操作

使用Docker安装Ollama及Open-WebUI完整教程

作者:吴业亮 博客:wuyeliang.blog.ZEEKLOG.net 一、Ollama 简介及工作原理 1. Ollama 简介及原理 * 简介:Ollama 是一款轻量级、开源的大语言模型(LLM)运行工具,旨在简化本地部署和运行大语言模型的流程。它支持 Llama 3、Mistral、Gemini 等主流开源模型,用户无需复杂配置即可在本地设备(CPU 或 GPU)上快速启动模型,适用于开发测试、本地智能应用搭建等场景。 * 工作原理: * 采用模型封装机制,将大语言模型的运行环境、依赖库及推理逻辑打包为标准化格式,实现模型的一键下载、启动和版本管理。 * 通过优化的推理引擎适配硬件架构,支持 CPU 基础运行和 GPU 加速(如 NVIDIA CUDA),减少资源占用并提升响应速度。 * 提供简洁的

Apache SeaTunnel Web 完整使用指南:从零搭建可视化数据集成平台

Apache SeaTunnel Web 完整使用指南:从零搭建可视化数据集成平台 【免费下载链接】seatunnel-webSeaTunnel is a distributed, high-performance data integration platform for the synchronization and transformation of massive data (offline & real-time). 项目地址: https://gitcode.com/gh_mirrors/se/seatunnel-web Apache SeaTunnel Web 是基于 SeaTunnel Connector API 和 Zeta Engine 开发的可视化管理平台,让数据集成工作变得前所未有的简单。无论您是数据工程师、开发人员还是运维人员,这个强大的 Web 控制台都能帮助您轻松管理海量数据的同步和转换任务。

【测试理论与实践】(十)Web 项目自动化测试实战:从 0 到 1 搭建博客系统 UI 自动化框架

【测试理论与实践】(十)Web 项目自动化测试实战:从 0 到 1 搭建博客系统 UI 自动化框架

目录 前言 一、项目背景与测试规划:先明确 "测什么" 和 "怎么测" 1.1 项目介绍 1.2 测试目标 1.3 测试范围与用例设计 编辑 二、环境搭建:3 步搞定自动化测试前置准备 2.1 安装核心依赖包 2.2 浏览器配置 2.3 项目目录结构设计 三、核心模块开发:封装公共工具,提高代码复用性 3.1 驱动管理与截图工具封装(common/Utils.py) 3.2 代码说明与优化点 四、测试用例开发: