剑指 Offer day3, day4

news2024/9/25 1:24:09

剑指 Offer day3, day4

字符串和数组的操作。

剑指 Offer 05. 替换空格

剑指 Offer 05. 替换空格 - 力扣(Leetcode)

方法二:原地修改

在 C++ 语言中, string 被设计成「可变」的类型(参考资料),因此可以在不新建字符串的情况下实现原地修改。

由于需要将空格替换为 “%20” ,字符串的总字符数增加,因此需要扩展原字符串 s 的长度,计算公式为:新字符串长度 = 原字符串长度 + 2 * 空格个数 ,示例如下图所示。

在这里插入图片描述

算法流程:

  1. 初始化:空格数量 count ,字符串 s 的长度 len ;
  2. 统计空格数量:遍历 s ,遇空格则 count++ ;
  3. 修改 s 长度:添加完 “%20” 后的字符串长度应为 len + 2 * count ;
  4. 倒序遍历修改:i 指向原字符串尾部元素, j 指向新字符串尾部元素;当 i = j 时跳出(代表左方已没有空格,无需继续遍历);
    当 s[i] 不为空格时:执行 s[j] = s[i] ;
    当 s[i] 为空格时:将字符串闭区间 [j-2, j] 的元素修改为 “%20” ;由于修改了 3 个元素,因此需要 j -= 2 ;
  5. 返回值:已修改的字符串 s ;

复杂度分析:

时间复杂度 O(N): 遍历统计、遍历修改皆使用 O(N)时间。
空间复杂度 O(1): 由于是原地扩展 s 长度,因此使用 O(1)额外空间。

class Solution {
public:
    string replaceSpace(string s) {
        int count = 0, len = s.size();
        // 统计空格数量
        for (char c : s) {
            if (c == ' ') count++;
        }
        // 修改 s 长度
        s.resize(len + 2 * count);
        // 倒序遍历修改
        for(int i = len - 1, j = s.size() - 1; i < j; i--, j--) {
            if (s[i] != ' ')
                s[j] = s[i];
            else {
                s[j - 2] = '%';
                s[j - 1] = '2';
                s[j] = '0';
                j -= 2;
            }
        }
        return s;
    }
};

剑指 Offer 58 - II. 左旋转字符串

剑指 Offer 58 - II. 左旋转字符串 - 力扣(Leetcode)

由于 python 中 字符串是不可变对象,提供一个 c++ 实现,大概原理就是 进行三次翻转,代码如下:

class Solution {
public:
    string reverse(string& s, int start, int end) {
        while (start < end) {
            swap(s[start++], s[end--]);
        }
        return s;
    }
    string reverseLeftWords(string s, int n) {
        reverse(s, 0, n - 1);
        reverse(s, n, s.size() - 1);
        reverse(s, 0, s.size() - 1);
        return s;
    }
};

剑指 Offer 03. 数组中重复的数字

剑指 Offer 03. 数组中重复的数字 - 力扣(Leetcode)

原地交换的方法很有意思,哈希表就比较常规了

方法二:原地交换

题目说明尚未被充分使用,即 在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内 。 此说明含义:数组元素的 索引 和 值 是 一对多 的关系。 因此,可遍历数组并通过交换操作,使元素的 索引 与 值 一一对应(即 nums[i]=i )。因而,就能通过索引映射对应的值,起到与字典等价的作用。

在这里插入图片描述

遍历中,第一次遇到数字 x 时,将其交换至索引 x 处;而当第二次遇到数字 x 时,一定有 nums[x]=x,此时即可得到一组重复数字。

算法流程:

  1. 遍历数组 nums ,设索引初始值为 i=0 :
  2. 若 nums[i]=i : 说明此数字已在对应索引位置,无需交换,因此跳过;
  3. 若 nums[nums[i]]=nums[i] : 代表索引 nums[i] 处和索引 i 处的元素值都为 nums[i] ,即找到一组重复值,返回此值 nums[i];
  4. 否则: 交换索引为 i 和 nums[i] 的元素值,将此数字交换至对应索引位置。
  5. 若遍历完毕尚未返回,则返回 −1。

复杂度分析:

时间复杂度 O(N) : 遍历数组使用 O(N) ,每轮遍历的判断和交换操作使用 O(1) 。
空间复杂度 O(1) : 使用常数复杂度的额外空间。

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        int i = 0;
        while(i < nums.size()) {
            if(nums[i] == i) {
                i++;
                continue;
            }
            if(nums[nums[i]] == nums[i])
                return nums[i];
            swap(nums[i],nums[nums[i]]);
        }
        return -1;
    }
};

剑指 Offer 53 - I. 在排序数组中查找数字 I

本来的想法是找到target了就直接遍历了,题解的方法是边界也要用二分的方法找,难度增加了。

注意边界条件。

解题思路:

排序数组中的搜索问题,首先想到 二分法 解决。

排序数组 nums中的所有数字 target 形成一个窗口,记窗口的 左 / 右边界 索引分别为 left 和 right ,分别对应窗口左边 / 右边的首个元素。

本题要求统计数字 target 的出现次数,可转化为:使用二分法分别找到 左边界 left 和 右边界 right ,易得数字 target 的数量为 right−left−1。

在这里插入图片描述

算法解析:

  1. 初始化: 左边界 i=0,右边界 j=len(nums)−1。
  2. 循环二分: 当闭区间 [i,j][i, j][i,j] 无元素时跳出;
    a. 计算中点 m=(i+j)/2(向下取整);
    b. 若 nums[m]<target,则 target 在闭区间 [m+1, j]中,因此执行 i=m+1;
    c. 若 nums[m]>target,则 target 在闭区间 [i, m−1]中,因此执行 j=m−1;
    d. 若 nums[m]=target,则右边界 right在闭区间 [m+1, j] 中;左边界 left在闭区间 [i, m−1] 中。因此分为以下两种情况:
    1. 若查找 右边界 right,则执行 i=m+1;(跳出时 i 指向右边界)
    2. 若查找 左边界 left,则执行 j=m−1;(跳出时 j 指向左边界)
  3. 返回值: 应用两次二分,分别查找 right 和 left ,最终返回 right−left−1 即可。

效率优化:

以下优化基于:查找完右边界 right=i 后,则 nums[j] 指向最右边的 target(若存在)。

  1. 查找完右边界后,可用 nums[j]=j判断数组中是否包含 target,若不包含则直接提前返回 0 ,无需后续查找左边界。
  2. 查找完右边界后,左边界 left 一定在闭区间 [0, j] 中,因此直接从此区间开始二分查找即可。

复杂度分析:

时间复杂度 O(logN) : 二分法为对数级别复杂度。
空间复杂度 O(1) : 几个变量使用常数大小的额外空间。

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

剑指 Offer 53 - II. 0~n-1中缺失的数字

注意题目的特殊数据状况,可以优化时间复杂度到O(logN)。

解题思路:

排序数组中的搜索问题,首先想到 二分法 解决。
根据题意,数组可以按照以下规则划分为两部分。
左子数组: nums[i]=i ;
右子数组: nums[i]≠i ;
缺失的数字等于 “右子数组的首位元素” 对应的索引;因此考虑使用二分法查找 “右子数组的首位元素” 。

在这里插入图片描述

算法解析:

  1. 初始化: 左边界 i=0 ,右边界 j=len(nums)−1 ;代表闭区间 [i,j] 。
  2. 循环二分: 当 i≤j 时循环 (即当闭区间 [i,j] 为空时跳出) ;
    a. 计算中点 m=(i+j)//2 ,其中 “//” 为向下取整除法;
    b. 若 nums[m]=m ,则 “右子数组的首位元素” 一定在闭区间 [m+1,j] 中,因此执行 i=m+1;
    c. 若 nums[m]≠m ,则 “左子数组的末位元素” 一定在闭区间 [i,m−1] 中,因此执行 j=m−1 ;
  3. 返回值: 跳出时,变量 i 和 j 分别指向 “右子数组的首位元素” 和 “左子数组的末位元素” 。因此返回 i 即可。

复杂度分析:

时间复杂度 O(logN): 二分法为对数级别复杂度。
空间复杂度 O(1): 几个变量使用常数大小的额外空间。

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;

        while(left <= right){
            int mid = left + (right - left) / 2;
            if(nums[mid] == mid){
                left = mid + 1;
            } else{
                right = mid - 1;
            }
        }
        return left;
    }
};

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

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

相关文章

python海龟绘图

一、基础 &#xff08;一&#xff09;介绍 海龟绘图&#xff08;Turtle Graphics&#xff09;&#xff1a;“小海龟”turtle是Python语言中一个很流行的绘制图像的函数库&#xff0c;想象一个小乌龟&#xff0c;在一个横轴为x、纵轴为y的坐标系原点&#xff0c;(0,0)位置开始…

Git分支的合并策略有哪些?Merge和Rebase有什么区别?关于Merge和Rebase的使用建议

Git分支的合并策略有哪些&#xff1f;Merge和Rebase有什么区别&#xff1f;关于Merge和Rebase的使用建议1. 关于Git的一些基本原理1.1 Git的工作流程原理2. Git的分支合并方式浅析2.1 分支是什么2.2 分支的合并策略2.2.1 Three-way-merge&#xff08;三向合并原理&#xff09;2…

前端已死?我看未必,但「低代码」已剑指前端程序员

本文笔者会从以下几个方面分享&#xff0c;希望能够帮助正在迷茫的前端小伙伴提供一点思路&#xff01; 逛技术博客 不局限框架 全栈工程师兴起 关注前沿 写技术文章 录制前端视频 总结 2023第一季度快过去了&#xff0c;没工作的找到工作了吗&#xff1f;有工作的加薪了…

Hbase的基本概念与架构

一、Hbase的概念 HBase是Hadoop的生态系统&#xff0c;是建立在Hadoop文件系统&#xff08;HDFS&#xff09;之上的分布式、面向列的数据库&#xff0c;通过利用Hadoop的文件系统提供容错能力。如果你需要进行实时读写或者随机访问大规模的数据集的时候&#xff0c;请考虑使用H…

Canal数据同步配置

文章目录Canal数据同步配置0.canal工作原理1.**检查binlog功能是否有开启**2.如果显示状态为OFF表示该功能未开启&#xff0c;开启binlog功能3.**在mysql里面添加以下的相关用户和权限**4.下载安装Canal服务5.修改配置文件6.进入bin目录下启动7.idea中配置Canal数据同步配置 c…

记录第一次接口上线过程

新入职一家公司后&#xff0c;前三天一直在学习公司内部各种制度文化以及考试。 一直到第三天组长突然叫我过去&#xff0c;给了一个需求的思维导图&#xff0c;按照这个需求写这样一个接口&#xff0c; 其实还不错&#xff0c;不用自己去分析需求&#xff0c;按照这上面直接开…

工业机器人有哪些类型?如何利用工业网关集中监测管理?

工业机器人在制造业中的应用与日俱增&#xff0c;使用工业机器人&#xff0c;不仅提高了设备和场地的利用率&#xff0c;还能保持稳定的产品水平。随着工业机器人的大规模部署&#xff0c;对于数量众多、类型各异、功能不一的机器人的监测、管理和维护&#xff0c;也成为企业面…

Java 异常

文章目录1. 异常概述2. JVM 的默认处理方案3. 异常处理之 try...catch4. Throwable 的成员方法5. 编译异常和运行异常的区别6. 异常处理之 throws7. 自定义异常8. throws 和 throw 的区别1. 异常概述 异常就是程序出现了不正常的情况。 ① Error&#xff1a;严重问题&#xff…

Nessus: 漏洞扫描器-网络取证工具

Nessue 要理解网络漏洞攻击&#xff0c;应该理解攻击者不是单独攻击&#xff0c;而是组合攻击。因此&#xff0c;本文介绍了关于Nessus历史的研究&#xff0c;它是什么以及它如何与插件一起工作。研究了Nessus的特点&#xff0c;使其成为网络取证中非常推荐的网络漏洞扫描工具…

maven高级知识。

目录 一、分模块开发 1、分模块开发设计 2、依赖管理 二、继承和聚合 1、聚合 2、继承 三、属性 1、基本介绍 2、版本管理 四、多环境配置与应用 1、多环境开发 2、跳过测试 五、私服 1、私服安装 2、私服仓库分类 一、分模块开发 1、分模块开发设计 ▶ 示意图 …

【测绘程序设计】——计算卫星位置

本文分享了根据广播星历计算卫星于瞬时地固系下位置的计算程序(C#版)(注:瞬时地球坐标系坐标经极移改正即可获得协议地球坐标系坐标),相关源代码(完整工程,直接运行;包含实验数据)及使用示例如下。 目录 Part.Ⅰ 使用示例Part.Ⅱ 代码分析Chap.Ⅰ 数据结构Chap.Ⅱ 计…

原生javascript手写一个丝滑的轮播图

通过本文&#xff0c;你将学到: htmlcssjs 没错&#xff0c;就是html&#xff0c;css,js,现在是框架盛行的时代&#xff0c;所以很少会有人在意原生三件套&#xff0c;通过本文实现一个丝滑的轮播图&#xff0c;带你重温html,css和js基础知识。 为什么选用轮播图做示例&…

网络运维和网络安全运维有什么区别?就业前景如何?

随着互联网的高速发展&#xff0c;运维安全已经成了大多数企业安全保障的基石。在如今的信息时代&#xff0c;无论是网络运维还是网络安全运维都成了不可缺少的一部分。因此导致很多人都容易把两者弄混淆。首先我们来了解一下网络运维和网络安全运维有什么区别呢&#xff1f;网…

Linux vi/vim教程

所有的 Unix Like 系统都会内建 vi 文本编辑器&#xff0c;其他的文本编辑器则不一定会存在。 但是目前我们使用比较多的是 vim 编辑器。 vim 具有程序编辑的能力&#xff0c;可以主动的以字体颜色辨别语法的正确性&#xff0c;方便程序设计。 ** 什么是 vim&#xff1f;** Vim…

将vue-devtools打包成edge插件

文章目录一、从github拉vue-devtools源码二、用npm安装yarn三、使用yarn安装并编译源码四、将vue-devtools打包成edge插件五、离线安装edge插件一、从github拉vue-devtools源码 目前最新的版本是v6.5.0&#xff0c;地址&#xff1a;https://github.com/vuejs/devtools 二、用n…

第四阶段17-关于Redis中的list类型,缓存预热,关于Mybatis中的`#{}`和`${}`这2种格式的占位符

关于Redis中的list类型 Redis中的list是一种先进后出、后进先出的栈结构的数据。 在使用Redis时&#xff0c;应该将list想像为以上图例中翻转了90度的样子&#xff0c;例如&#xff1a; 在Redis中的list数据&#xff0c;不仅可以从左侧压入&#xff0c;也可以选择从右侧压入…

Linux04-冯诺依曼体系结构||操作系统||进程概念

1.认识冯诺依曼系统 1.1冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成 输入单元&#xff1a;包括…

Linux: 中断只被GIC转发到CPU0问题分析

文章目录1. 前言2. 分析背景3. 问题4. 分析4.1 ARM GIC 中断芯片简介4.1.1 中断类型和分布4.1.2 拓扑结构4.2 问题根因4.2.1 设置GIC SPI 中断CPU亲和性4.2.2 GIC初始化&#xff1a;缺省的CPU亲和性4.2.2.1 boot CPU亲和性初始化流程4.2.2.1 其它非 boot CPU亲和性初始化流程5.…

【蓝桥杯入门不入土】变幻莫测的链表

文章目录一&#xff1a;链表的类型单链表双链表循环链表二&#xff1a;链表的存储方式三&#xff1a;链表的定义删除节点添加节点四&#xff1a;实战练习1.设计链表2. 移除链表元素最后说一句一&#xff1a;链表的类型 单链表 什么是链表&#xff0c;链表是一种通过指针串联在…

最经典的黑客技术入门知识大全

第一节、什么是黑客 以我的理解&#xff0c;“黑客”大体上应该分为“正”、“邪”两类&#xff0c;正派黑客依靠自己掌握的知识帮助系统管理员找出系统中的漏洞并加以完善&#xff0c;而邪派黑客则是通过各种黑客技能对系统进行攻击、入侵或者做其他一些有害于网络的事情&…