25寒假算法刷题 | Day1 | LeetCode 240. 搜索二维矩阵 II,148. 排序链表

news2025/2/4 2:09:22

目录

  • 240. 搜索二维矩阵 II
    • 题目描述
    • 题解
  • 148. 排序链表
    • 题目描述
    • 题解

240. 搜索二维矩阵 II

点此跳转题目链接

题目描述

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

示例 1:

在这里插入图片描述

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

示例 2:

在这里插入图片描述

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -109 <= matrix[i][j] <= 109
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -109 <= target <= 109

题解

暴力算法直接遍历整个矩阵,时间复杂度为 O ( m n ) O(mn) O(mn) m 、 n m、n mn 分别为矩阵的行、列数。

由于题中矩阵在行和列上的元素都是升序的,所以想到可以从上到下逐行利用二分查找解决:

class Solution {
public:
    int binarySearch(const vector<int>& arr, int target) {
        int left = 0;
        int right = arr.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] < target) {
                left = mid + 1;
            } else if (arr[mid] > target) {
                right = mid - 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if (matrix.empty()) {
            return false;
        }

        // 逐行使用二分法查找target
        for (const vector<int>& line : matrix) {
            if (binarySearch(line, target) != -1) {
                return true;
            }
        }
        return false;
    }
};

行内 n n n 个元素做二分查找的时间复杂度为 O ( l o g n ) O(logn) O(logn) ,共 m m m 行,故时间复杂度为 O ( m l o g n ) O(mlogn) O(mlogn)

不过上面两种方法似乎都过于直白简单了,考虑到这个题目带的是“中等”tag,肯定还有更高效的算法:

🔗 以下内容来自 LeetCode官方题解

我们可以从矩阵 matrix 的右上角 (0,n−1) 进行搜索。在每一步的搜索过程中,如果我们位于位置 (x,y) ,那么我们希望在以 matrix 的左下角为左下角、以 (x,y) 为右上角的矩阵中进行搜索,即行的范围为 [x,m−1] ,列的范围为 [0,y]

  • 如果 matrix[x,y]=target ,说明搜索完成
  • 如果 matrix[x,y]>target ,由于每一列的元素都是升序排列的,那么在当前的搜索矩阵中,所有位于第 y 列的元素都是严格大于 target 的,因此我们可以将它们全部忽略,即将 y 减少 1
  • 如果 matrix[x,y]<target ,由于每一行的元素都是升序排列的,那么在当前的搜索矩阵中,所有位于第 x 行的元素都是严格小于 target 的,因此我们可以将它们全部忽略,即将 x 增加 1。

在搜索的过程中,如果我们超出了矩阵的边界,那么说明矩阵中不存在 target 。代码实现如下:

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int x = 0;
        int y = matrix[0].size() - 1;
        while (x < matrix.size() && y >= 0) {
            if (matrix[x][y] < target) {
                x++;
            } else if (matrix[x][y] > target) {
                y--;
            } else {
                return true;
            }
        }
        return false;
    }
};

时间复杂度: O ( m + n ) O(m+n) O(m+n) 。在搜索的过程中,如果我们没有找到 target ,那么我们要么将 y 减少 1,要么将 x 增加 1。由于 (x,y) 的初始值分别为 (0,n−1) ,因此 y 最多能被减少 n 次, x 最多能被增加 m 次,总搜索次数为 m+n 。在这之后, xy 就会超出矩阵的边界。


148. 排序链表

点此跳转题目链接

题目描述

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例 1:

在这里插入图片描述

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

在这里插入图片描述

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目在范围 [0, 5 * 104]
  • -105 <= Node.val <= 105

进阶: 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

题解

暴力解法无需多言,遍历一遍链表获取全部元素、排序后重新整一个新链表即可:

struct ListNode
{
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        vector<int> elements;
        while (head)
        {
            elements.push_back(head->val);
            head = head->next;
        }
        sort(elements.begin(), elements.end());
        ListNode *dummyHead = new ListNode();
        ListNode *cur = dummyHead;
        for (int element : elements) {
            cur->next = new ListNode(element);
            cur = cur->next;
        }
        return dummyHead->next;
    }
};

上述算法时间复杂度为 sort() O ( n log ⁡ n ) O(n\log{n}) O(nlogn) ,空间复杂度为 O ( n ) O(n) O(n) ——因为新建了一个链表。 直接看看进阶要求:时间复杂度为 O ( n log ⁡ n ) O(n\log{n}) O(nlogn) ,空间复杂度为常数级。

考虑算法题中常用的高效排序算法——归并排序,有:

class Solution {
public:
    ListNode *merge(ListNode *L, ListNode *R) {
        ListNode dummyHead;
        ListNode *cur = &dummyHead;
        while (L && R) {
            if (L->val < R->val) {
                cur->next = L;
                L = L->next;
            } else {
                cur->next = R;
                R = R->next;
            }
            cur = cur->next;
        }
        cur->next = L ? L : R;
        return dummyHead.next;
    }

    ListNode *sortList(ListNode *head, ListNode *tail) {
        if (!head || head == tail) return head;

        // 快慢指针找到链表中点
        ListNode *slow = head, *fast = head;
        while (fast != tail && fast->next != tail) {
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode *mid = slow->next;
        slow->next = nullptr;  // 断开链表

        return merge(sortList(head, slow), sortList(mid, tail));
    }

    ListNode *sortList(ListNode *head) { return sortList(head, nullptr); }
};

上述算法时间复杂度为 O ( n log ⁡ n ) O(n\log{n}) O(nlogn) ,即归并排序的时间复杂度。空间复杂度取决于递归调用的栈空间,为 O ( log ⁡ n ) O(\log{n}) O(logn) ,还是没到最佳的常数级别。为此,需要采用“自底向上”的归并排序实现 O ( 1 ) O(1) O(1) 的空间复杂度:

🔗 以下内容参考 LeetCode官方题解

首先求得链表的长度 length ,然后将链表拆分成子链表进行合并。具体做法如下:

  • subLength 表示每次需要排序的子链表的长度,初始时 subLength=1
  • 每次将链表拆分成若干个长度为 subLength 的子链表(最后一个子链表的长度可以小于 subLength ),按照每两个子链表一组进行合并,合并后即可得到若干个长度为 subLength×2 的有序子链表(最后一个子链表的长度可以小于 subLength×2 )。合并两个子链表仍然使用之前用过的归并算法。
  • subLength 的值加倍,重复第 2 步,对更长的有序子链表进行合并操作,直到有序子链表的长度大于或等于 length ,整个链表排序完毕。

通过数学归纳法易证最后得到的链表是有序的(每次合并用到的子链表是有序的,合并后的也是有序的)。

class Solution {
public:
    ListNode *merge(ListNode *L, ListNode *R) {
        ListNode dummyHead;
        ListNode *cur = &dummyHead;
        while (L && R) {
            if (L->val < R->val) {
                cur->next = L;
                L = L->next;
            } else {
                cur->next = R;
                R = R->next;
            }
            cur = cur->next;
        }
        cur->next = L ? L : R;
        return dummyHead.next;
    }

    ListNode *sortList(ListNode *head) {
        if (!head) {
            return nullptr;
        }

        // 获取链表长度
        int length = 0;
        ListNode *cur = head;
        while (cur != nullptr) {
            length++;
            cur = cur->next;
        }

        // 自底向上,两两合并长度为subLength的子链表
        ListNode *dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength <<= 1) {
            ListNode *prev = dummyHead;
            cur = prev->next;
            while (cur != nullptr) {
                // 获取第一个子链表
                ListNode *head1 = cur;
                for (int i = 1; i < subLength && cur->next != nullptr; ++i) {
                    cur = cur->next;
                }

                // 获取第二个子链表
                ListNode *head2 = cur->next;
                cur->next = nullptr;  // 断开第一个子链表结尾
                cur = head2;
                for (int i = 1; i < subLength && cur && cur->next; ++i) {
                    cur = cur->next;
                }

                // 预存第三个子链表(即下一轮的第一个子链表)的头节点
                // 即第二个子链表结尾节点的next节点
                ListNode *nextHead = nullptr;
                if (cur != nullptr) {
                    nextHead = cur->next;
                    cur->next = nullptr;  // 断开第二个子链表结尾
                }

                // 合并第一、二个子链表
                ListNode *merged = merge(head1, head2);
                
                // 更新prev、cur指针
                prev->next = merged;
                while (prev->next != nullptr) {
                    prev = prev->next;
                }
                cur = nextHead;
            }
        }
        return dummyHead->next;
    }
};

该算法时间复杂度为 O ( n log ⁡ n ) O(n \log{n}) O(nlogn) ,空间复杂度为 O ( 1 ) O(1) O(1)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2291552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

何谓共赢?

A和B是人或组织&#xff0c;他们怎样的合作才是共赢呢&#xff1f; 形态1:A提供自己的身份证等个人信息&#xff0c;B用来作贷款等一些事务&#xff0c;A每月得到一笔钱。 A的风险远大于收益&#xff0c;或者B从事的是非法行为&#xff1b; 形态2:A单方面提前终止了与B的合作…

Kanass基础教程-创建项目

Kanass是一款国产开源免费的项目管理工具&#xff0c;工具简洁易用&#xff0c;开源免费&#xff0c;之前介绍过kanass的一些产品简介及安装配置方法&#xff0c;本文就从如何创建第一个项目来开始kanass上手之旅吧。 1. 创建项目 点击项目->项目添加 按钮进入项目添加页面…

实验9 JSP访问数据库(二)

实验9 JSP访问数据库&#xff08;二&#xff09; 目的&#xff1a; 1、熟悉JDBC的数据库访问模式。 2、掌握预处理语句的使用 实验要求&#xff1a; 1、使用Tomcat作为Web服务器 2、通过JDBC访问数据库&#xff0c;实现增删改查功能的实现 3、要求提交实验报告&#xff0c;将代…

单片机基础模块学习——DS1302时钟芯片

一、DS1302时钟简介 1.与定时器对比 DS1302时钟也称为RTC时钟(Real Time Clock,实时时钟),说到时钟,可能会想到定时器,下表来简单说明一下两者的区别。 定时器(Timer)实时时钟(RTC)精度高,可达微秒级精度较低,多为秒级计时范围短计时范围长2.开发板所在位置 下面方框里…

Vue+Echarts 实现青岛自定义样式地图

一、效果 二、代码 <template><div class"chart-box"><chart ref"chartQingdao" style"width: 100%; height: 100%;" :options"options" autoresize></chart></div> </template> <script> …

FIR滤波器:窗函数法

一、FIR滤波器基础 FIR&#xff08;有限脉冲响应&#xff09;滤波器的三大特点&#xff1a; 绝对稳定&#xff1a;没有反馈回路&#xff0c;不会出现失控振荡 线性相位&#xff1a;信号通过后波形不失真 直观设计&#xff1a;通过窗函数法、频率采样法等方法实现 二、窗函…

【AI】探索自然语言处理(NLP):从基础到前沿技术及代码实践

Hi &#xff01; 云边有个稻草人-CSDN博客 必须有为成功付出代价的决心&#xff0c;然后想办法付出这个代价。 目录 引言 1. 什么是自然语言处理&#xff08;NLP&#xff09;&#xff1f; 2. NLP的基础技术 2.1 词袋模型&#xff08;Bag-of-Words&#xff0c;BoW&#xff…

力扣动态规划-18【算法学习day.112】

前言 ###我做这类文章一个重要的目的还是记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&#xff01;&#xff01;&#xff01; 习题 1.下降路径最小和 题目链接:931. …

DBASE DBF数据库文件解析

基于Java实现DBase DBF文件的解析和显示 JDK19编译运行&#xff0c;实现了数据库字段和数据解析显示。 首先解析数据库文件头代码 byte bytes[] Files.readAllBytes(Paths.get(file));BinaryBufferArray bis new BinaryBufferArray(bytes);DBF dbf new DBF();dbf.VersionN…

【ESP32】ESP-IDF开发 | WiFi开发 | UDP用户数据报协议 + UDP客户端和服务器例程

1. 简介 UDP协议&#xff08;User Datagram Protocol&#xff09;&#xff0c;全称用户数据报协议&#xff0c;它是一种面向非连接的协议&#xff0c;面向非连接指的是在正式通信前不必与对方先建立连接&#xff0c; 不管对方状态就直接发送。至于对方是否可以接收到这些数据内…

tiktok 国际版抖抖♬♬ X-Bogus参数算法逆向分析

加密请求参数得到乱码&#xff0c;最终得到X-Bogus

C++ Primer 命名空间的using声明

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

c语言(关键字)

前言&#xff1a; 感谢b站鹏哥c语言 内容&#xff1a; 栈区&#xff08;存放局部变量&#xff09; 堆区 静态区&#xff08;存放静态变量&#xff09; rigister关键字 寄存器&#xff0c;cpu优先从寄存器里边读取数据 #include <stdio.h>//typedef&#xff0c;类型…

ARM内核:嵌入式时代的核心引擎

引言 在当今智能设备无处不在的时代&#xff0c;ARM&#xff08;Advanced RISC Machines&#xff09;处理器凭借其高性能、低功耗的特性&#xff0c;成为智能手机、物联网设备、汽车电子等领域的核心引擎。作为精简指令集&#xff08;RISC&#xff09;的典范&#xff0c;ARM核…

Airflow:选择合适执行器扩展任务执行

Apache Airflow是面向开发人员使用的&#xff0c;以编程方式编写、调度和监控的数据流程平台。可伸缩性是其关键特性之一&#xff0c;Airflow支持使用不同的执行器来执行任务。在本文中&#xff0c;我们将深入探讨如何利用这些执行器在Airflow中有效地扩展任务执行。 理解Airfl…

CoRAG 来自微软与人大的创新RAG框架技术

微软与人大合作开发的CoRAG(Chain-of-Retrieval Augmented Generation)是一种创新的检索增强生成(RAG)框架,旨在通过模拟人类思考方式来提升大语言模型(LLM)在复杂问题上的推理和回答能力。以下是对CoRAG的深度介绍: 1. CoRAG的核心理念 CoRAG的核心思想是通过动态调…

Qt Creator 中使用 vcpkg

Qt Creator 中使用 vcpkg Qt Creator 是一个跨平台的轻量级 IDE&#xff0c;做 Qt 程序开发的同学们肯定对这个 IDE 都比较属于。这个 IDE 虽然没有 Visual Stdio 功能那么强&#xff0c;但是由于和 Qt 集成的比较深&#xff0c;用来开发 Qt 程序还是很顺手的。 早期&#xf…

mysql中in和exists的区别?

大家好&#xff0c;我是锋哥。今天分享关于【mysql中in和exists的区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; mysql中in和exists的区别&#xff1f; 在 MySQL 中&#xff0c;IN 和 EXISTS 都是用于子查询的操作符&#xff0c;但它们在执行原理和适用场景上有所不…

智慧园区管理系统推动企业智能运维与资源优化的全新路径分析

内容概要 在当今快速发展的商业环境中&#xff0c;园区管理的数字化转型显得尤为重要。在这个背景下&#xff0c;快鲸智慧园区管理系统应运而生&#xff0c;成为企业实现高效管理的最佳选择。它通过整合互联网、物联网等先进技术&#xff0c;以智能化的方式解决了传统管理模式…

物联网 STM32【源代码形式-使用以太网】连接OneNet IOT从云产品开发到底层MQTT实现,APP控制 【保姆级零基础搭建】

物联网&#xff08;IoT&#xff09;‌是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器等装置与技术&#xff0c;实时采集并连接任何需要监控、连接、互动的物体或过程&#xff0c;实现对物品和过程的智能化感知、识别和管理。物联网的核心功能包括数据采集与监…