【有营养的算法笔记】 二分+排序/堆 求解矩阵中战斗力最弱的 K 行

news2024/11/16 5:49:14

👑作者主页:@进击的安度因
🏠学习社区:进击的安度因(个人社区)
📖专栏链接:有营养的算法笔记
✉️分类专栏:题解

文章目录

  • 一、题目描述
  • 二、思路及代码实现
    • 1. 二分 + 排序
    • 2. 二分 + 堆

一、题目描述

今天的题目其实可以暴力求解,但是我们今天主要为了讲解 二分 和 堆,以练习为主~

链接:1337. 矩阵中战斗力最弱的 K 行

描述

给你一个大小为 m * n 的矩阵 mat,矩阵由若干军人和平民组成,分别用 10 表示。

请你返回矩阵中战斗力最弱的 k 行的索引,按从最弱到最强排序。

如果第 i 行的军人数量少于第 j 行,或者两行军人数量相同但 i 小于 j,那么我们认为第 i 行的战斗力比第 j 行弱。

军人 总是 排在一行中的靠前位置,也就是说 1 总是出现在 0 之前。

示例1

输入:mat = 
[[1,1,0,0,0],
 [1,1,1,1,0],
 [1,0,0,0,0],
 [1,1,0,0,0],
 [1,1,1,1,1]], 
k = 3
输出:[2,0,3]
解释:
每行中的军人数目:
行 0 -> 2 
行 1 -> 4 
行 2 -> 1 
行 3 -> 2 
行 4 -> 5 
从最弱到最强对这些行排序后得到 [2,0,3,1,4]

示例2

输入:mat = 
[[1,0,0,0],
 [1,1,1,1],
 [1,0,0,0],
 [1,0,0,0]], 
k = 2
输出:[0,2]
解释: 
每行中的军人数目:
行 0 -> 1 
行 1 -> 4 
行 2 -> 1 
行 3 -> 1 
从最弱到最强对这些行排序后得到 [0,2,3,1]

提示

  • m == mat.length
  • n == mat[i].length
  • 2 <= n, m <= 100
  • 1 <= k <= m
  • matrix[i][j] 不是 0 就是 1

二、思路及代码实现

首先梳理一下题目大意:

给定一个矩阵,矩阵元素由 10 组成,1 为军人,0 为平民。军人数量就是矩阵的战斗力。军人 1 出现在矩阵每一行的 靠前位置

如果 第 i 行 1 数量少于第 j 行,或者第 i 行和第 j 行 1 的数量相同,但是 i < j 那么认为 第 i 行的战斗力 比第 j 行弱

题目要求返回前 k 行的索引,就是按照顺序返回 1 最少的前 k 行。

所以这道题目先得求出每行的 1 的个数

求每行 1 的个数可以通过遍历每一行来实现,但是我认为最好的方法还是 二分

由于二维数组每行是从 1 开始,到 0 结束,所以数组整体是有序的。那么我只需要二分出 1右边界点 就可以了。

但是要求出前 k 行战斗力最弱的索引仅有 战斗力 是没用的,我们需要之后比较战斗力的同时返回相应索引,并且对于战斗力相同的情况下需要比较 索引的大小 。所以需要考虑一下 用什么存储数据

了解了这些,我们接下来讲解我们的主要解法。解法分为两种:二分 + 排序二分 + 小堆

1. 二分 + 排序

这里我们采用的二分方式是 二分出右边界点 ,用之前的二分模板:

// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

每次二分需要将 每行中1的个数当前行数 存储到对应的空间中。

所以我们可以定义一个结构体,用来专门存放两种类型的数据:

typedef struct data
{
    int combat; // 战斗力
    int row; // 行数
}data;

紧接着动态开辟一个结构体数组 tmp ,用来存储数据;一个 k 个大小的数组 res 作为返回的数组。

在二分的过程中

  • 如果二分出来的边界点的值 不等于 1 ,说明二分结果错误,那么此行战斗力 combat 为 0 ,存到正确位置
  • 如果二分出来的边界点的值 等于 1 ,说明二分结果正确,将 l(r) + 1 存入结构体数组的对应位置

有了结构体数组,那么进行排序就好了,这里直接使用 qsort ,注意需要处理一下 特殊情况i 行和第 j 行 1 的数量相同,但是 i < j 那么认为 第 i 行的战斗力 比第 j 行弱

最后将数据存入返回数组中,返回即可。

过程相对简单,直接上代码:

typedef struct data
{
    int combat; // 战斗力
    int row; // 行
}data;

int cmp(const void* e1, const void* e2)
{
    data* ee1 = (data*)e1;
    data* ee2 = (data*)e2;
    // 战斗力大小 或 战斗力相等 行数不同
    return (ee1->combat > ee2->combat) || (ee1->combat == ee2->combat && ee1->row > ee2->row);
}

int* kWeakestRows(int** mat, int matSize, int* matColSize, int k, int* returnSize)
{
    // 答案数组
    int* res = (int*)malloc(sizeof(int) * k);
    data* tmp = (data*)malloc(sizeof(data) * matSize);
    int col = *matColSize;
    *returnSize = k;

    // 二分,将数据存入 tmp 中
    for (int i = 0; i < matSize; i++) {
        int l = 0, r = col - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (mat[i][mid] == 1) {
                l = mid;
            } else if (mat[i][mid] == 0) {
                r = mid - 1;
            }
        }
        if (mat[i][l] != 1) {
            tmp[i].combat = 0; // 无战斗力
        } else {
            tmp[i].combat = l + 1;
        }
        tmp[i].row = i; // 存入索引
    }
    
    qsort(tmp, matSize, sizeof(data), cmp);

    for (int i = 0; i < k; i++)
    {
        res[i] = tmp[i].row;
    }

    return res;
}

image-20221218144735841

2. 二分 + 堆

这种方法其实是我第一次就想到的,但是中途调试了很久,觉得这种思路比排序难一些就把它放到第二个。

先讲常规操作:

这里我们采用的存储结构是用 二维数组 ,将其定义为全局变量 int heap[100][2]

将每一行看作是一个元素,存放 战斗力索引

开辟一个 ans 作为返回数组。

紧接着就是二分,并将元素 战斗力存入二维数组每行的 0 下标处,将 行的索引存入二维数组每行的 1 下标处

准备工作完成,接下来开始讲解剩余步骤。

我想到堆的原因就是因为之前的堆排序和TopK当时我看了很久,看这道题题目我就觉得可以用堆解决。

之前说过建堆的优先级是 向下调整建堆 > 向上调整建堆 ,我们当前使用 向下调整算法 来构建一个大小为矩阵的行数的 小堆

直接使用 heap 这个全局数组,以它为基准来建堆。

就拿我们 示例1 给出的矩阵计算出的结果作为堆中的数据,计算结果为:

  • heap[0][0] = 2 heap[0][1] = 0
  • heap[1][0] = 4 heap[1][1] = 1
  • heap[2][0] = 1 heap[2][1] = 2
  • heap[3][0] = 2 heap[3][1] = 3
  • heap[4][0] = 5 heap[4][1] = 4

将其写成堆的样子:

image-20221218154010343

接着就是写 向下调整算法 ,向下调整算法需要注意几点:

  • 小堆是由 战斗力强弱 起主要衡量,战斗力相等需要看行之间的关系
  • 构建的是小堆,每次交换的是最小孩子
  • 求最小孩子时,需要额外判断战斗力相等时的情况
  • 判断调整时也需要判断战斗力相等的情况
  • 交换数据时,由于这里是二维数组,所以是交换一行的数据,传参传每行的地址,交换函数的参数要写成二级指针

构建过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hU59fTsu-1671350627615)(https://anduin.oss-cn-nanjing.aliyuncs.com/image-20221218155334931.png)]

最后我们需要 索引 放入返回数组中

主要方法是给定一个 end 等于 当前堆的行数

在循环 k 次,先将 二维数组第一行第二列的元素 存入返回数组 ans 中,然后交换堆顶和堆底的元素。

向下调整重新建堆,将 end-- ,每次丢弃堆中1个元素,最后 ans 中的结果就是战斗力最弱的 K 行 。

接下来看看代码怎么写:

int heap[100][2];

void Swap(int** p1, int** p2)
{
    int* tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

// 向下调整
void AdjustDown(int n, int parent)
{
    int child = 2 * parent + 1;

    while (child < n) {
        // 求最小孩子
        if (child + 1 < n && (heap[child + 1][0] < heap[child][0] || (heap[child + 1][0] == heap[child][0] && heap[child + 1][1] < heap[child][1]))) {
            child++;
        }
        // 判断是否调整
        if (heap[child][0] < heap[parent][0] || (heap[child][0] == heap[parent][0] && heap[child][1] < heap[parent][1])) {
            // 交换两行
            Swap(heap[child], heap[parent]);
            parent = child;
            child = 2 * parent + 1;
        } else {
            break;
        }
    }
}

int* kWeakestRows(int** mat, int matSize, int* matColSize, int k, int* returnSize)
{
    int cnt = 0;
    *returnSize = k;
    for (int i = 0; i < matSize; i++) {
        // 将 k 行元素的索引存入 heap 中
        int l = 0, r = *matColSize - 1;
        while (l < r) {
            int mid = l + r + 1>> 1;
            if (mat[i][mid] == 0) {
                r = mid - 1;
            }
            if (mat[i][mid] == 1) {
                l = mid;
            }
        }
        // 0 下标处存战斗力
        // 1 下标处存索引
        if (mat[i][l] != 1)
        {
            heap[cnt][0] = 0;
        } else {
            heap[cnt][0] = l + 1;
        }
        heap[cnt][1] = i;
        cnt++;
    }

    // 将 res 数组中元素建小堆,不断取出堆顶元素
    int* ans = (int*)malloc(sizeof(int) * k);
    for (int i = (cnt - 1 - 1) / 2; i >= 0; i--) {
        // 向下调整堆中元素
        AdjustDown(cnt, i);
    }

    int end = cnt - 1, j = 0;
    while (k > 0) {
        // 将索引存入 ans 数组
        ans[j++] = heap[0][1];
        Swap(heap[0], heap[end]);
        AdjustDown(end--, 0);
        k--;
    }
    return ans;
}

image-20221218160224669

完结撒花 🌹

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

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

相关文章

【学习笔记】JDK源码学习之Vector(附带面试题)

【学习笔记】JDK源码学习之Vector&#xff08;附带面试题&#xff09; 什么是 Vector &#xff1f;它的作用是什么&#xff1f;它的底层由什么组成&#xff1f;是否是线程安全的&#xff1f; 老样子&#xff0c;跟着上面的问题&#xff0c;我们层层深入了解 Vector 吧。 1、…

Linux——linux面试题

cat a.txt | cut -d "/" -f 3 | sort | uniq -c |sort -nrgrep ESTABLISHED | awk -F " " {print $5} |cut -d ":" -f 1 | sort |uniq -c | sort -nr找回mysql的root用户的密码 首先&#xff0c;进入到/etc/my.cnf&#xff0c;插入一句skip-gra…

Apache Hudi Timeline

Timeline | Apache Hudi Hudi维护了在不同时刻在表上执行的所有操作的时间线&#xff0c;这有助于提供表的即时视图&#xff0c;同时也有效地支持按到达顺序检索数据。Hudi的核心是维护表上在不同的即时时间&#xff08;instants&#xff09;执行的所有操作的时间轴&#xff08…

windows下配置chrome浏览器驱动的详细攻略

要想使用python去爬取互联网上的数据&#xff0c;尤其是要模拟登录操作。那么selenium包肯定是绕不过的。 selenium包本质上就是通过后台驱动的方式驱动浏览器去。以驱动chrome浏览器为例&#xff0c;搭建环境如下&#xff1a; 1、查看本机chrome浏览器的版本。 方式是&#x…

第三十二章 linux-模块的加载过程二

第三十二章 linux-模块的加载过程二 文章目录第三十二章 linux-模块的加载过程二HDR视图的第二次改写模块导出的符号HDR视图的第二次改写 在这次改写中&#xff0c;HDR视图中绝大多数的section会被搬移到新的内存空间中&#xff0c;之后会根据这些section新的内存地址再次改写…

[附源码]计算机毕业设计Python“小世界”私人空间(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等…

知到/智慧树——程序设计基础(C语言)进阶篇

目录 第一章测试 第二章测试 第三章测试 第四章测试 第五章测试 第一章测试 第1部分总题数: 10 1 【单选题】 (10分) 在C语言中&#xff0c;将属于不同类型的数据作为一个整体来处理时&#xff0c;常用&#xff08; &#xff09;。 A. 简单变量 B. 数组类型数据 C. 结…

论文投稿指南——中文核心期刊推荐(力学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

10.union all、N天连续登录

有日志如下&#xff0c;请写出代码求得所有用户和活跃用户的总数及平均年龄。&#xff08;活跃用户指连续两天都有访问记录的用户&#xff09; 数据准备 最后需完成的结果表 步骤1&#xff0c;所有用户的总数及平均年龄 (1). 将数据去重 with t1 as (select distinctuser_i…

如何使用交换机、路由器及防火墙进行组网以及他们之间的功能和区别

如何使用交换机、路由器及防火墙进行组网以及他们之间的功能和区别。 几乎大部分网络都有交换机、路由器和防火墙这三种基本设备,因此这三种设备对于网络而言非常重要,很多人对这三种设备的使用容易弄混。 一般网络部署: 或者抽象为这种部署模式: 几乎每个网络都有交换…

别再写jsp了,Thymeleaf它不香吗?

啥是 Thymeleaf在学 Thymeleaf 之前我们先看一下使用 jsp 开发遇到的主要问题&#xff1a;jsp 的痛点1.页面包含大量 java 代码&#xff0c;代码太混乱<% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head> &l…

webpack实现自动代码编译

前置 使用webpack构建开发的代码&#xff0c;为了运行需要有两个操作&#xff1a; 操作一&#xff1a;npm run build&#xff0c;编译相关的代码。操作二&#xff1a;通过live server或者直接通过浏览器&#xff0c;打开index.html代码&#xff0c;查看效果。为了完成自动编译&…

《图解TCP/IP》阅读笔记(第五章 5.7、5.8)—— IP隧道与其他IP相关技术

5.7 IP隧道 IP隧道技术顾名思义&#xff0c;是用于在两片网络区域间直接建立通信的通路&#xff0c;而绕过此间的其他网络的一种技术&#xff0c;如下图所示&#xff1a; 网络A与网络B使用IPv6技术&#xff0c;使用IP隧道技术&#xff0c;便可以绕过网络C。 那么其工作原理是…

机器学习 波士顿房价预测 Boston Housing

目录 一&#xff1a;前言 二&#xff1a;模型预测(KNN算法) 三&#xff1a;回归模型预测比对 一&#xff1a;前言 波士顿房价是机器学习中很常用的一个解决回归问题的数据集 数据统计于1978年&#xff0c;包括506个房价样本&#xff0c;每个样本包括波士顿不同郊区房屋的13种…

SQL - MySQL回表

一、回表概念&#xff1b;现象 回表&#xff0c;顾名思义就是回到表中&#xff0c;也就是先通过普通索引&#xff08;我们自己建的索引不管是单列索引还是联合索引&#xff0c;都称为普通索引&#xff09;扫描出数据所在的行&#xff0c;再通过行主键ID 取出索引中未包含的数据…

[附源码]计算机毕业设计Python创新创业管理系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

Trie(Trie字符串统计)【实质就是二维数组 表示 链表】【二维数组的第一行就是 头结点】

欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09; 文章字体风格&#xff1a; 红色文字表示&#xff1a;重难点✔ 蓝色文字表示&#xff1a…

探索MapReduce

文章目录一&#xff0c;案例分析&#xff08;一&#xff09;TopN分析法介绍&#xff08;二&#xff09;案例需求二&#xff0c;案例实施&#xff08;一&#xff09;准备数据文件&#xff08;1&#xff09;启动hadoop服务&#xff08;2&#xff09;在虚拟机上创建文本文件&#…

ArcGIS中的OBJECTID、FID 和 OID 的区别!不要傻傻分不清

喜欢就关注我们吧 时常有很多我朋友分不清OBJECTID、FID 和 OID有什么区别&#xff0c;不懂得怎么应用和管理&#xff0c;今天我们来说个明白。 ArcGIS Desktop 产品要求独立表和属性表均具有 ObjectID字段&#xff0c;该字段包含唯一的长整型用于标识每个记录。 此 ID 由 Esri…

第二证券|元宇宙发展规划出炉,3只元宇宙概念股估值创年内新低

本月以来&#xff0c;北上资金加仓23只元国际概念股。 12月15日晚间&#xff0c;浙江省发改委等5部门联合印发《浙江省元国际工业开展举动计划&#xff08;2023—2025年&#xff09;》&#xff08;以下简称《计划》&#xff09;。《计划》中说到&#xff0c;到2025年&#xff0…