剑指offer第2版:搜索算法(二分/DFS/BFS)

news2025/5/11 17:29:13

        查找本质就是排除的过程,不外乎顺序查找、二分查找、哈希查找、二叉排序树查找、DFS/BFS查找  

一、p39-JZ3 找出数组中重复的数字(利用特性)

数组中重复的数字_牛客题霸_牛客网

方法1:全部排序再进行逐个扫描找重复。  时间复杂度n*logn  空间复杂度1  

class Solution {
  public:
    int duplicate(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return -1;
        sort(nums.begin(), nums.end());
        for (int i = 1; i < n; ++i)
            if (nums[i] == nums[i - 1]) return nums[i];
        return -1;
    }
};

方法2:构造哈希——找到第一个重复的数就返回,否则正常插入    时间复杂度n  空间复杂度n

class Solution {
  public:
    int duplicate(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return -1;
        unordered_set<int> s;
        for (int i = 0; i < n; ++i)
            if (s.count(nums[i])) return nums[i];
            else s.insert(nums[i]);
        return -1;
    }
};

那么我们能否避免n的空间复杂度呢?? 

方法3:边排序边比较——我们会发现数组中的数字都在0-n-1的范围内,如果这个数组中没有重复的数字,那么当这个数组排序之后数字i将出现在下标为i的位置,同时数组中有些位置可能没有数据    因此我们可以重拍这个数组,当扫描到下标为i的数字m的时候,首先看看m和i是否相同,如果不相同则拿他和第m个数字进行比较,如果相等说明找到了重复数字,如果不相等则把第i个数和第m个数进行交换,把m放在属于他的位置,接下来再重复这个比较、交换的过程,直到我们发现了一个重复的数字。          此时我们会发现每个数字至多比较2次就可以找到自己的位置,所以时间复杂度是n 空间复杂度1

class Solution {
  public:
    int duplicate(vector<int>& nums) {
        //重排 边排序边比较
        int n = nums.size();
        if (n == 0)return -1;
        for (int i = 0; i < n; ++i) {
            while (nums[i] != i)
                if (nums[i] == nums[nums[i]]) return nums[i];
                else swap(nums[i], nums[nums[i]]);
        }
        return -1;
    }
};

二、扩展p41-JZ3 不修改原数组基础上找出重复数(二分)

287. 寻找重复数 - 力扣(LeetCode)

     这题和上一题相似,但是区别1:不可修改原数组。区别2:该题中1-n只有n个数,但是数组中包含超过n个数,所以根据鸽巢原理,一定至少有一个数字是重复的!

     我们把1-n的数字从中间的数字m分成两部分(概数一定出现在左半部分或者右半部分),前半部分为1->m 后面一半为m->n-1,如果1-m的数字超过了m,那么这一半钟一定包含了重复的数字,否则另一半m+1->n-1的区间里一定包含重复的数字,这样我们可以继续把包含重复数字的区间一分为2,直到找到一个重复的数字。

     按照上面的二分查找的思路,那么countrange会给调用logn次,而每次需要n的时间,所以时间复杂度为n*logn,空间复杂度1   相比于直接用哈希的做法而言,等同于用时间换空间!!但是要注意的是这个算法并不能保证找出所有重复的数字!!也不能确定该数究竟出现了几次  (所以一定要问清面试官的要求,看是要找出任意一个 还是找出所有,或者是对时间性能有什么要求,比如是空间还是时间优先) 

    这里使用左端点区间二分法

class Solution {
public:
    int countrange(vector<int>& nums, int begin, int end) {
        int count = 0;
        int n = nums.size();
        for (int i = 0; i < n; ++i)
            if (nums[i] >= begin && nums[i] <= end)
                ++count;
        return count;
    }
    int findDuplicate(vector<int>& nums) {
        // 不修改原数组的基础上找到重复的数字
        int n = nums.size();
        if (n == 0)
            return -1;
        int left = 1, right = n - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            int count = countrange(nums, left, mid);
            if (count > (mid - left + 1))
                right = mid; // 说明区间一定在左边
            else
                left = mid + 1;
        }
        // 此时left==right了 我们去看看该数的数目
        if (countrange(nums, left, right) > 1)
            return left;
        return -1;
    }
};

三、p44-JZ4 二维数组中的查找某数(杨氏矩阵)(利用特性)

二维数组中的查找__牛客网

       要利用他每一行和每一列都递增的特性!!先从右上角或者左下角进行比较(比如右上角,如果比右上角小,此时就可以排除列,比右上角大就可以排除行,所以无论如何都可以排除一行或者一列!!)。这样的时间复杂度就是O(m+n)

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

四、p82-JZ11 旋转数组的最小数字(二分)

旋转数组的最小数字_牛客题霸_牛客网

        我们要注意旋转之后的数组实际上可以划分为两个排序的子数组,而前面的元素普遍大于等于后面子数组的元素!最小的元素恰好是这两个子数组的分界线!

       假如该题是严格升序的,那么图应该如下所示,此时我们会发现他具有二段性(根据某个规律可以将数组一分为二,然后再舍去其中一部分!!)

    此时我们要找的是右边区间的左端点,所以要用左端点区间二分法,此时当中间的数比左边大的时候,说明该数字在左区间,左区间要跳过去,当中间的数比右边小的时候,说明该数字在右区间,此时要右区间要跟过来。

但是有两种特殊情况:1、如果恰好旋转了n次,此时相当于原数组没有变化,那么此时直接返回第一个就可以了!!2、因为该题不是严格增,而是非递减,所以存在相等的情况,比如{1,0,1,1,1} 很有可能出现nums[mid]==left==right 此时我们就无法进行二分了!!这个时候我们只能按照笨方法进行一个个遍历了!! 

class Solution {
  public:
    int minNumberInRotateArray(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            if (nums[left] < nums[right]) return nums[left];//情况1
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[left]) left = mid + 1;
            else if (nums[mid] < nums[right]) right = mid;
            else ++left;//此时说明nums[left]==nums[right]==nums[mid] 情况2
        }
        return nums[left];
    }
};

五、p263-JZ53 排序数组中查找数字(二分)

数字在升序数组中出现的次数_牛客题霸_牛客网

如果我们直接用朴素二分找到一个3,那么我们并不确定左边有多少右边有多少,所以我们需要尝试用左端点区间和右端点区间法找到这个数字的范围。 即找到第1个k和最后1个k。

class Solution {
public:
    int GetNumberOfK(vector<int>& nums, int k) {
      //只要找到第一个k和最后一个k  就可以统计出k在数组中出现的次数
      int n=nums.size();
      if(n==0) return 0;
      //第一个k要用左端点区间法
      int left=0,right=n-1;
      while(left<right){
        int mid=left+(right-left)/2;
        if(nums[mid]<k) left=mid+1;
        else right=mid; //最终会落在区间的左端点
      }
      if(nums[left]!=k) return 0;//找不到就返回
      //第二个k要用右端点区间法
      int begin=0,end=n-1;
      while(begin<end){
        int mid=begin+(end-begin+1)/2;
        if(nums[mid]<=k) begin=mid;//最终会落在区间的右端点
        else end=mid-1; 
      }
      return end-right+1;
    }
};

 六、扩展p266-JZ53 0~n-1中缺失的数字(二分)

LCR 173. 点名 - 力扣(LeetCode)

        这题有一个只管的做法就是直接用等差求和公式n(n-1)/2求出数字0~n-1所有数字之和,然后减掉整个数组的数字,就可以得到缺失的数字了,但是这种做法需要n的时间复杂度

       我们会发现该题具有二段性,即1、缺席位置之前的数和下标是一样的 2、缺席位置之后的数和下标是不一致的。所以此时我们就可以通过下标和数是否一致来进行二分查找!!

       因为我们要找的是不符合要求的第一个,所以要用左端点区间法

class Solution {
public:
    int takeAttendance(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        while(left<right){
            int mid=left+(right-left)/2;
            if(nums[mid]==mid) left=mid+1;//说明mid在左区间 要跳跃
            else right=mid;
        }
        //此时right指向的就是第一个缺席的同学的位置
        //但是极端情况如果缺席的正好是最后一个同学 
        if(right==nums[right]) return right+1;
        return right;
    }
};

七、p197-JZ38 字符串的排列(DFS) 

字符串的排列_牛客题霸_牛客网

      我们可以把他拆分成小问题,根据实例2我们知道有重复的数字,所以我们可以先排序一下让相同的字母挨着一块,当前面的数跟自己相等且没有选的时候,那么自己也不能选。 

class Solution {
  public:
    vector<string> ret;
    string path;
    bool check[10] = {0};
    void dfs(string s) {
        if (path.size() == s.size()) {
            ret.emplace_back(path);
            return;
        }
        for (int i = 0; i < s.size(); ++i) {
            if(check[i]||i>0&&s[i-1]==s[i]&&!check[i-1])  continue;//但是前面的数没选
            path.push_back(s[i]);
            check[i] = true;
            dfs(s);
            path.pop_back();
            check[i] = false;

        }
    }
    vector<string> Permutation(string str) {
        if (str.empty()) return {};
        //要去重 所以排序一下
        sort(str.begin(), str.end());//保证相同的放在一起
        dfs(str);
        return ret;
    }
};

八、P89-JZ12 矩阵中的路径(DFS)

矩阵中的路径_牛客题霸_牛客网

用标记数组和向量数组 

class Solution {
  public:
    bool check[21][21] = {0}; //标记数组
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0}; //向量数组
    int m,n; //长度和宽度
    bool hasPath(vector<vector<char>>& nums, string word) {
        m = nums.size();
        if (m == 0) return false;
        n = nums[0].size();
        for (int i = 0; i < m; ++i)
            for (int j = 0; j < n; ++j)
                if (nums[i][j] == word[0])
                    if (dfs(nums, word, i, j, 1)) return true;
        return false;
    }
    bool dfs(vector<vector<char>>& nums, string& word, int i, int j, int pos) {
        if (pos == word.size()) return true;
        check[i][j] = true;
        for (int k = 0; k < 4; ++k) {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && !check[x][y] &&
                    word[pos] == nums[x][y])
                if (dfs(nums, word, x, y, pos + 1)) return true;
        }
        check[i][j] = false; //找错了就回溯
        return false;
    }
};

 九、p92-JZ13 机器人的运动范围(DFS/BFS)

机器人的运动范围_牛客题霸_牛客网

思路1:DFS 

class Solution {
public:
    int dx[4]={-1,1,0,0};
    int dy[4]={0,0,1,-1};
    int vis[101][101]={0};//标记数组
    int ret=0;//统计格子数
    int movingCount(int threshold, int rows, int cols) {
        if(threshold==0) return 1;
        dfs(threshold,rows,cols,0,0);
        return ret;
    }
     void dfs(int threshold, int m, int n,int i,int j){
        if(i==m||j==n) return;
        vis[i][j]=true;
        ++ret;
        for(int k=0;k<4;++k){
            int x=dx[k]+i,y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&!vis[x][y]&&check(threshold,x,y))
                dfs(threshold,m,n,x,y);
        }
     }
     bool check(int threshold,int x,int y){
         int sum=0;
         while(x){
            sum+=x%10;
            x/=10;
         }
         if(sum>threshold) return false;
         while(y){
            sum+=y%10;
            y/=10;
         } 
         return sum<=threshold;
     }
};

思路2:BFS

class Solution {
  public:
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, 1, -1};
    int vis[101][101] = {0}; //标记数组
    int movingCount(int threshold, int rows, int cols) {
        if (threshold == 0) return 1;
        int ret = 1; //统计格子数
        queue<pair<int, int>> q;
        q.push({0, 0});
        vis[0][0] = true;
        while (!q.empty()) {
            auto&[i, j] = q.front();
            q.pop();
            for (int k = 0; k < 4; ++k) {
                int x = dx[k] + i, y = dy[k] + j;
                if (x >= 0 && x < rows && y >= 0 && y < cols && !vis[x][y] &&
                        check(threshold, x, y)) {
                    q.push({x, y});
                    ++ret;
                    vis[x][y] = true;
                }
            }
        }
        return ret;
    }
    bool check(int threshold, int x, int y) {
        int sum = 0;
        while (x) {
            sum += x % 10;
            x /= 10;
        }
        if (sum > threshold) return false;
        while (y) {
            sum += y % 10;
            y /= 10;
        }
        return sum <= threshold;
    }
};

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

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

相关文章

在 CentOS 上更改 SSH 默认端口以提升服务器安全性

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall ︱vue3-element-admin︱youlai-boot︱vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; GitCode︱ Gitee ︱ Github &#x1f496; 欢迎点赞 &#x1f44d; 收藏 ⭐评论 …

2025年:边缘计算崛起下运维应对新架构挑战

一、引言 随着科技的飞速发展&#xff0c;2025年边缘计算正以前所未有的速度崛起&#xff0c;给运维行业带来了全新的架构挑战。在这个充满机遇与挑战的时代&#xff0c;美信时代公司的美信监控易运维管理软件成为运维领域应对这些挑战的有力武器。 二、边缘计算崛起带来的运维…

怎么理解 Spring Boot 的约定优于配置 ?

在传统的 Spring 开发中&#xff0c;大家可能都有过这样的经历&#xff1a;项目还没开始写几行核心业务代码&#xff0c;就已经在各种配置文件中耗费了大量时间。比如&#xff0c;要配置数据库连接&#xff0c;不仅要在 XML 文件里编写冗长的数据源配置&#xff0c;还要处理事务…

学习总结2.14

深搜将题目分配&#xff0c;如果是两个题目&#xff0c;就可以出现左左&#xff0c;左右&#xff0c;右左&#xff0c;右右四种时间分配&#xff0c;再在其中找最小值&#xff0c;即是两脑共同处理的最小值 #include <stdio.h> int s[4]; int sum0; int brain[25][25]; …

Electron 客户端心跳定时任务调度库调研文档 - Node.js 任务调度库技术调研文档

Electron 客户端心跳定时任务调度库调研文档 - Node.js 任务调度库技术调研文档 本文将对七个流行的定时任务调度库&#xff1a;node-cron、rxjs、bull、node-schedule、agenda、bree、cron。这些库都可以用来处理定时任务&#xff0c;但它们的特点和适用场景有所不同。我们将从…

【学术投稿-第四届智能电网和绿色能源国际学术会议(ICSGGE 2025)】CSS基本选择器详解:掌握基础,轻松布局网页

可线上 官网&#xff1a;www.icsgge.org 时间&#xff1a;2025年2月28-3月2日 目录 前言 一、基本选择器简介 1. 元素选择器&#xff08;Type Selector&#xff09; 基本语法 示例 注意事项 2. 类选择器&#xff08;Class Selector&#xff09; 基本语法 示例 注意…

singleTaskAndroid的Activity启动模式知识点总结

一. 前提知识 1.1. 任务栈知识 二. Activity启动模式的学习 2.1 standard 2.2 singleTop 2.3.singleTask 2.4.singleInstance 引言&#xff1a; Activity作为四大组件之一&#xff0c;也可以说Activity是其中最重要的一个组件&#xff0c;其负责调节APP的视图&#xff…

Vue 入门到实战 十

第10章 Vue Router​​​​​​​ 目录 10.1 什么是路由 10.2 Vue Router的安装 10.2.1 本地独立版本方法 10.2.2 CDN方法 10.2.3 NPM方法 10.2.4 命令行工具&#xff08;Vue CLI&#xff09;方法 10.3 Vue Router的基本用法 10.3.1 跳转与传参 10.3.2 配置路由 10.…

jenkins-获取当前时间戳

一. 简述&#xff1a; 很多场景下&#xff0c;需要获取当前时间戳。 二. 使用方法&#xff1a; 1. 安装&#xff1a; 最简单的&#xff0c; 莫过于直接部署相关插件&#xff1a; Build Timestamp Plugin 2. 配置&#xff1a; 3. 使用&#xff1a; post {success {script…

【Jenkins流水线搭建】

Jenkins流水线搭建 01、SpringBoot项目 - Jenkins基于Jar持续集成搭建文档基于手动方式发布项目基于dockerfile基于jenkins + dockerfile + jenkinsfile +pieline基于jenkins + jar方式的发布01、环境说明01、准备项目02、准备服务器03、安装git04、安装jdk1.805、安装maven依赖…

【Java】规则引擎 Drools

https://www.bilibili.com/video/BV1nW421R7qJ 来自尚硅谷 背景 /*** 设置订单积分*/ public void setOrderPoint(Order order){if (order.getAmout() < 100){order.setScore(0);}else if(order.getAmout() > 100 && order.getAmout() < 500){order.setScore(…

深入浅出Java反射:掌握动态编程的艺术

小程一言反射何为反射反射核心类反射的基本使用获取Class对象创建对象调用方法访问字段 示例程序应用场景优缺点分析优点缺点 注意 再深入一些反射与泛型反射与注解反射与动态代理反射与类加载器 结语 小程一言 本专栏是对Java知识点的总结。在学习Java的过程中&#xff0c;学习…

数据挖掘智能Agent

&#x1f917; CodeGenie - 智能编程助手 数据处理和分析对于数据分析工作人员来说&#xff0c;往往既复杂又令人头疼&#xff0c;需要耗费大量精力进行重复性工作。为了解决这一问题&#xff0c;我们开发了一款集成了自然语言处理和代码生成功能的智能编程助手——CodeGenie。…

动手学深度学习11.7. AdaGrad算法-笔记练习(PyTorch)

以下内容为结合李沐老师的课程和教材补充的学习笔记&#xff0c;以及对课后练习的一些思考&#xff0c;自留回顾&#xff0c;也供同学之人交流参考。 本节课程地址&#xff1a;72 优化算法【动手学深度学习v2】_哔哩哔哩_bilibili 本节教材地址&#xff1a;11.7. AdaGrad算法…

基础算法# 求一个数的二进制表示当中有几个1 (C++)

文章目录 题目链接题目解读思路完整代码参考 题目链接 题目解读 给定L,R。统计[L,R]区间内的所有数在二进制下包含的“1”的个数之和。 如5的二进制为101&#xff0c;包含2个“1”。 思路 直接将该数字转为二进制表示,求其有几个1即可。 完整代码 #include<bits/stdc.…

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题 【承接商业广告,如需商业合作请+v17740568442】 文章目录 【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题问题描述:解决方法方法一:手动中断并重启下载方法二:使用 Bash 脚本自动化下载在…

SpringBoot+Vue+微信小程序的高校食堂点餐系统

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 食堂点餐系统&#xff0c;作为一款融合现代信息技术的高效餐饮服务利器&#xff0c;以其…

大预言模型|微调大预言模型初探索(LLaMA-Factory)(1)

前言 微调模型通常比从零开始训练一个模型的技术要求低。公司不需要拥有大量的深度学习专家&#xff0c;利用现有的开源工具和库&#xff08;如Hugging Face的Transformers等&#xff09;&#xff0c;中小型公司可以轻松地使用和微调大型模型&#xff0c;从而快速实现AI能力的集…

IOTDB安装部署

IOTDB一般用于工业互联网&#xff0c;至于具体的介绍请自行搜索 1.环境准备 安装前需要保证设备上配有 JDK>1.8 的运行环境&#xff0c;并配置好 JAVA_HOME 环境变量。 设置最大文件打开数为 65535。 关闭防火墙 systemctl stop firewalld.service systemctl disable …

如何通过MDM高效管理企业的Android平板?

目录 1. 批量配置设备&#xff08;Batch Device Provisioning&#xff09; 2. 应用推送与管理&#xff08;App Deployment & Management&#xff09; 3. 远程控制与故障排除&#xff08;Remote Control & Troubleshooting&#xff09; 4. 数据安全管理&#xff08;…