LeetCode 1595. 连通两组点的最小成本【记忆化搜索,状压DP】2537

news2024/11/19 10:36:26

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给你两组点,其中第一组中有 size1 个点,第二组中有 size2 个点,且 size1 >= size2

任意两点间的连接成本 cost 由大小为 size1 x size2 矩阵给出,其中 cost[i][j] 是第一组中的点 i 和第二组中的点 j 的连接成本。如果两个组中的每个点都与另一组中的一个或多个点连接,则称这两组点是连通的。 换言之,第一组中的每个点必须至少与第二组中的一个点连接,且第二组中的每个点必须至少与第一组中的一个点连接。

返回连通两组点所需的最小成本。

示例 1:

输入:cost = [[15, 96], [36, 2]]
输出:17
解释:连通两组点的最佳方法是:
1--A
2--B
总成本为 17

示例 2:

输入:cost = [[1, 3, 5], [4, 1, 1], [1, 5, 3]]
输出:4
解释:连通两组点的最佳方法是:
1--A
2--B
2--C
3--A
最小成本为 4 。
请注意,虽然有多个点连接到第一组中的点 2 和第二组中的点 A ,但由于题目并不限制连接点的数目,所以只需要关心最低总成本。

示例 3:

输入:cost = [[2, 5, 1], [3, 4, 7], [8, 1, 2], [6, 2, 4], [3, 8, 8]]
输出:10

提示:

  • size1 == cost.length
  • size2 == cost[i].length
  • 1 <= size1, size2 <= 12
  • size1 >= size2
  • 0 <= cost[i][j] <= 100

相似题目(状压 DP):

    1. 两个数组最小的异或值之和
    1. 数组的最大与和
    1. 最小的必要团队
    1. 公平分发饼干
    1. 并行课程 II
  • LCP 53. 守护太空城
    1. 完成任务的最少工作时间段

以后可以补两个解法,时间复杂度 O ( n 3 ) O(n^3) O(n3) 的二分图匹配,或者网络流 min ⁡ ( O ( n 3 ) , O ( n 2 m log ⁡ ( m ) ) ) \min(O(n ^ 3), O(n^2 m \log(m))) min(O(n3),O(n2mlog(m)))。由于牵涉到数据转换,转换后可能是稀疏图,m可能远小于n。

解法 记忆化搜索

1. 寻找子问题

下文中 n = size 1 n=\textit{size}_1 n=size1 m = size 2 m=\textit{size}_2 m=size2 。假设 n = 5 , m = 3 n=5,m=3 n=5,m=3 。第一组的点编号为 0 , 1 , 2 , 3 , 4 0,1,2,3,4 0,1,2,3,4 ,第二组的点编号为 0 , 1 , 2 0,1,2 0,1,2

用「枚举选哪个」来思考。考虑第一组的最后一个点( 4 4 4),可以枚举它和第二组的 0 , 1 , 2 0,1,2 0,1,2 相连。假设和第二组的 1 1 1 相连,那么问题变成:「第一组的 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3 和第二组的 0 , 1 , 2 0,1,2 0,1,2 相连,且第二组的 0 , 2 0,2 0,2 未被连接时,最小成本是多少」。然后考虑第一组的点 3 3 3 和第二组的哪个相连(可以连之前连过的点),接着考虑第一组的点 2 2 2,点 1 1 1,最后是点 0 0 0

第一组的点全部连完后,第二组的某些点可能未被连接,这些点可以去第一组找个成本最小的点连上

上述做法枚举了第一组的每个点连接第二组的所有情况,并在最后用贪心的思路处理第二组剩余没有连的点,所以一定可以算出(枚举出)最优解。

2. 状态定义与状态转移方程

根据上面的讨论,定义 dfs ( i , j ) \textit{dfs}(i,j) dfs(i,j) 表示第一组的 0 , 1 , ⋯   , i 0,1,\cdots,i 0,1,,i 和第二组的 0 , 1 , ⋯   , m − 1 0,1,\cdots,m-1 0,1,,m1 相连,且第二组的集合 j j j 中元素未被连接时,最小成本是多少。

枚举第一组的点 i i i 和第二组的 0 , 1 , ⋯   , m − 1 0,1,\cdots,m-1 0,1,,m1 其中一个点相连,取最小值,即
dfs ( i , j ) = min ⁡ k = 0 m − 1 dfs ( i − 1 , j ∖ { k } ) + cost [ i ] [ k ] \textit{dfs}(i,j) =\min_{k=0}^{m-1} \textit{dfs}(i-1,j\setminus\{k\}) + \textit{cost}[i][k] dfs(i,j)=k=0minm1dfs(i1,j{k})+cost[i][k]
​其中 j ∖ { k } j\setminus\{k\} j{k} 表示从集合 j j j 中去掉元素 k k k 后的集合。注意,如果 k k k 不在 j j j 中,那么 j j j 不变。

递归边界:第一组点已经连接完了,我们设集合 j j j 中第二组的点 x x x 与第一组的点连接时,最小成本是 minCost [ x ] \textit{minCost}[x] minCost[x] ,那么有
dfs ( − 1 , j ) = ∑ k ∈ j minCost [ k ] \textit{dfs}(-1,j) = \sum_{k\in j} \textit{minCost}[k] dfs(1,j)=kjminCost[k]
递归入口: dfs ( n − 1 , { 0 , 1 , ⋯   , m − 1 } ) \textit{dfs}(n-1,\{0,1,\cdots,m-1\}) dfs(n1,{0,1,,m1})

代码实现时, m i n C o s t minCost minCost 数组可以提前预处理出来。

3. 记忆化搜索

下文用 x − y x-y xy 表示第一组的点 x x x 连第二组的点 y y y

假设 n = 5 , m = 3 n=5,m=3 n=5,m=3 。由于无论是先 4 − 0 4-0 40 3 − 1 3-1 31 ,还是先 4 − 1 4-1 41 3 − 0 3-0 30 ,都会递归到 dfs ( 2 , { 2 } ) \textit{dfs}(2,\{2\}) dfs(2,{2}) 这个状态上。一叶知秋,整个递归中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:

  • 如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 memo \textit{memo} memo 数组(或哈希表)中。
  • 如果一个状态不是第一次遇到,那么直接返回 memo \textit{memo} memo 中保存的结果。

问:能不能枚举第二组的点,去连接第一组的点?
答:也可以,但这样做的时间复杂度是 O ( n m 2 n ) \mathcal{O}(nm2^n) O(nm2n) ,相比 O ( n m 2 m ) \mathcal{O}(nm2^m) O(nm2m) 更慢。注意本题 n ≥ m n\ge m nm

class Solution {
public:
    int connectTwoGroups(vector<vector<int>>& cost) {
        int n = cost.size(), m = cost[0].size();
        vector<int> minCost(m, INT_MAX);
        for (int j = 0; j < m; ++j)
            for (auto &c : cost)
                minCost[j] = min(minCost[j], c[j]);

        vector<vector<int>> memo(n, vector<int>(1 << m, INT_MAX));
        function<int(int, int)> dfs = [&](int i, int j) -> int {
            if (i < 0) {
                int ans = 0;
                for (int k = 0; k < m; ++k)
                    if (j >> k & 1) // 第二组的点集中k未连接
                        ans += minCost[k]; // 去第一组找个成本最小的点连接
                return ans;
            }
            int &ans = memo[i][j]; // 引用
            if (ans != INT_MAX) return ans; // 之前算过了
            for (int k = 0; k < m; ++k) // 第一组的点i与第二组的点k
                ans = min(ans, dfs(i - 1, j & ~(1 << k)) + cost[i][k]);
            return ans;
        };
        return dfs(n - 1, (1 << m) - 1);
    }
};

复杂度分析:

  • 时间复杂度: O ( n m 2 m ) \mathcal{O}(nm2^m) O(nm2m),其中 n n n m m m 分别为 cost \textit{cost} cost 的行数和列数。动态规划的时间复杂度 == 状态个数 × \times × 单个状态的计算时间。本题中状态个数等于 O ( n 2 m ) \mathcal{O}(n2^m) O(n2m) ,单个状态的计算时间为 O ( m ) \mathcal{O}(m) O(m) ,所以总的时间复杂度为 O ( n m 2 m ) \mathcal{O}(nm2^m) O(nm2m)
  • 空间复杂度: O ( n 2 m ) \mathcal{O}(n2^m) O(n2m)

解法2 递推

去掉递归中的「递」,只保留「归」的部分,即自底向上计算。通用做法:

  • dfs \textit{dfs} dfs 改成 f f f 数组;
  • 递归改成循环;
  • 递归边界改成 f f f 的初始状态。相当于原来是用递归去计算每个状态,现在是用循环去计算每个状态。

具体来说, f [ i ] [ j ] f[i][j] f[i][j] 的含义和状态转移方程和上面的记忆化搜索是一样的,即:
f [ i ] [ j ] = min ⁡ k = 0 m − 1 f [ i − 1 ] [ j ∖ { k } ] + cost [ i ] [ k ] f[i][j] =\min_{k=0}^{m-1} f[i-1][j\setminus\{k\}] + \textit{cost}[i][k] f[i][j]=k=0minm1f[i1][j{k}]+cost[i][k]
但当 i = 0 i=0 i=0 时,等号右边会出现负数下标。或者说,这种定义方式没有状态能表示递归边界。

解决办法:在 f f f 数组的上边加一排,把原来的 f [ i ] f[i] f[i] 改成 f [ i + 1 ] f[i+1] f[i+1] f [ i − 1 ] f[i-1] f[i1] 改成 f [ i ] f[i] f[i] 。此时 f [ 0 ] [ j ] f[0][j] f[0][j] 就对应着 dfs ( − 1 , j ) \textit{dfs}(-1,j) dfs(1,j)

修改后的递推式为
f [ i + 1 ] [ j ] = min ⁡ k = 0 m − 1 f [ i ] [ j ∖ { k } ] + cost [ i ] [ k ] f[i+1][j] =\min_{k=0}^{m-1} f[i][j\setminus\{k\}] + \textit{cost}[i][k] f[i+1][j]=k=0minm1f[i][j{k}]+cost[i][k]
注意只需要把 f f f 中的 i i i 加一, cost \textit{cost} cost 中的 i i i 不受影响。初始值 f [ 0 ] [ j ] = ∑ k ∈ j minCost [ k ] f[0][j]= \sum\limits_{k\in j} \textit{minCost}[k] f[0][j]=kjminCost[k]
答案为 f [ n ] [ { 0 , 1 , ⋯   , m − 1 } ] f[n][\{0,1,\cdots,m-1\}] f[n][{0,1,,m1}]

class Solution {
public:
    int connectTwoGroups(vector<vector<int>>& cost) {
        int n = cost.size(), m = cost[0].size();
        vector<int> minCost(m, INT_MAX);
        for (int j = 0; j < m; ++j)
            for (auto &c : cost)
                minCost[j] = min(minCost[j], c[j]);

        vector<vector<int>> f(n + 1, vector<int>(1 << m));
        for (int j = 0; j < 1 << m; ++j) 
            for (int k = 0; k < m; ++k)
                if (j >> k & 1) // 第二组的点k未连接
                    f[0][j] += minCost[k]; //去第一组找个成本最小的点连接
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < 1 << m; ++j) {
                int ans = INT_MAX;
                for (int k = 0; k < m; ++k) // 第一组的点i与第二组的点k连接
                    ans = min(ans, f[i][j & ~(1 << k)] + cost[i][k]);
                f[i + 1][j] = ans;
            }
        }
        return f[n][(1 << m) - 1];
    }
};

复杂度分析:

  • 时间复杂度: O ( n m 2 m ) \mathcal{O}(nm2^m) O(nm2m),其中 n n n m m m 分别为 cost \textit{cost} cost 的行数和列数。动态规划的时间复杂度 == 状态个数 × \times × 单个状态的计算时间。本题中状态个数等于 O ( n 2 m ) \mathcal{O}(n2^m) O(n2m) ,单个状态的计算时间为 O ( m ) \mathcal{O}(m) O(m) ,所以总的时间复杂度为 O ( n m 2 m ) \mathcal{O}(nm2^m) O(nm2m)
  • 空间复杂度: O ( n 2 m ) \mathcal{O}(n2^m) O(n2m)
两个优化

由于 f [ i + 1 ] f[i+1] f[i+1] 只和 f [ i ] f[i] f[i] 有关,因此可以仿照 0-1 背包,优化掉第一个维度,只用一个长为 2 m 2^m 2m 的一维数组。

去掉第一个维度后,考虑 f f f 数组初始值的计算,还可以进一步优化

假设现在算出了 f [ 00 ] , f [ 01 ] , f [ 10 ] , f [ 11 ] f[00],f[01],f[10],f[11] f[00],f[01],f[10],f[11](这里用二进制数表示集合),那么结合 minCost [ 2 ] \textit{minCost}[2] minCost[2] ,可以算出
f [ 100 ] = f [ 00 ] + minCost [ 2 ] f [ 101 ] = f [ 01 ] + minCost [ 2 ] f [ 110 ] = f [ 10 ] + minCost [ 2 ] f [ 111 ] = f [ 11 ] + minCost [ 2 ] \begin{aligned} f[100] = f[00] + \textit{minCost}[2]\\ f[101] = f[01] + \textit{minCost}[2]\\ f[110] = f[10] + \textit{minCost}[2]\\ f[111] = f[11] + \textit{minCost}[2]\end{aligned} f[100]=f[00]+minCost[2]f[101]=f[01]+minCost[2]f[110]=f[10]+minCost[2]f[111]=f[11]+minCost[2]
这样就得到了 f [ 0 ] f[0] f[0] f [ 111 ] f[111] f[111] 。按照同样的方式,结合 minCost [ 3 ] \textit{minCost}[3] minCost[3] ,可以算出 f [ 0 ] f[0] f[0] f [ 1111 ] f[1111] f[1111] 。依此类推。

这样每个初始值只需要 O ( 1 ) \mathcal{O}(1) O(1) 的时间就能递推算出来。

class Solution {
public:
    int connectTwoGroups(vector<vector<int>>& cost) {
        int n = cost.size(), m = cost[0].size();
        int f[1 << m];
        f[0] = 0;
        for (int j = 0; j < m; ++j) {
            int mn = INT_MAX;
            for (auto &c : cost) mn = min(mn, c[j]);
            int bit = 1 << j;
            for (int mask = 0; mask < bit; ++mask)
                f[bit | mask] = f[mask] + mn;
        }
        for (auto &row : cost) {
            for (int j = (1 << m) - 1; j >= 0; --j) {
                int ans = INT_MAX;
                for (int k = 0; k < m; ++k) // 第一组的点i与第二组的点k
                    ans = min(ans, f[j & ~(1 << k)] + row[k]);
                f[j] = ans;
            }
        }
        return f[(1 << m) - 1];
    }
};

复杂度分析:

  • 时间复杂度: O ( n m 2 m ) \mathcal{O}(nm2^m) O(nm2m) ,其中 n n n m m m 分别为 cost \textit{cost} cost 的行数和列数。计算 f f f 数组的初始值,循环次数为 2 0 + 2 1 + 2 2 ⋯ + 2 m − 1 = 2 m − 1 2^0+2^1+2^2\cdots+2^{m-1} = 2^m-1 20+21+22+2m1=2m1 ,这比优化前的 m 2 m m2^m m2m 要快。动态规划的时间复杂度 = 状态个数 × \times × 单个状态的计算时间。本题中状态个数等于 O ( n 2 m ) O(n2^m) O(n2m) ,单个状态的计算时间为 O ( m ) \mathcal{O}(m) O(m) ,所以总的时间复杂度为 O ( n m 2 m ) \mathcal{O}(nm2^m) O(nm2m)
  • 空间复杂度: O ( 2 m ) \mathcal{O}(2^m) O(2m)

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

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

相关文章

环形链表的约瑟夫问题

前言&#xff1a; 据说著名犹太历史学家Josephus有过如下故事&#xff1a; 在罗马人占领乔塔帕特后&#xff0c;39个犹太人和Josephus及他的朋友躲进一个洞里&#xff0c;39个犹太人决定宁愿死也不要被敌人抓到&#xff0c;于是决定了一个自杀方式&#xff0c;41个人排成一个…

24、Flink 的table api与sql之Catalogs(java api操作分区与函数、表)-4

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

Unity3D 关于过大的UI帧动画如何处理详解

Unity3D是一款流行的游戏开发引擎&#xff0c;它可以用来创建各种类型的游戏&#xff0c;包括2D和3D游戏。在游戏中&#xff0c;UI帧动画是一个常见的元素&#xff0c;它可以增加游戏的交互性和视觉效果。然而&#xff0c;当UI帧动画过大时&#xff0c;可能会导致游戏的性能下降…

linux安装新版本git2、配置github-ssh。(centos、aws)

一、安装Git 1、yum默认版本git #1.安装git sudo yum install git -y #2.确认Git已经安装成功 git --version如果要安装较新版本&#xff0c;可以安装一个repo &#xff0c;但是我这第一次尝试失败了&#xff0c;执行完提示找不到git2u&#xff0c;ius repo也连不上。而且每次…

02HTML功能元素

1.功能元素 1.1.列表标签 ​ 列表标签的作用: 给一堆数据添加列表语义, 也就是告诉搜索引擎告诉浏览器这一堆数据是一个整体 - HTML中列表标签的分类 ​ 无序列表(最多)(unordered list) ​ 有序列表(最少)(ordered list) ​ 定义列表(其次)(definition list) 1.1.1.无序列…

解析Apache Kafka中的事务机制

这篇博客文章并不是关于使用事务细节的教程&#xff0c;我们也不会深入讨论设计细节。相反&#xff0c;我们将在适当的地方链接到JavaDocs或设计文档&#xff0c;以供希望深入研究的读者使用。 为什么交易? 我们在Kafka中设计的事务主要用于那些显示“读-进程-写”模式的应用…

基于SSM的传统文化网站

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

Spring frame :基于 jdk 动态代理实现连接池复用

前言 在数据库开发中&#xff0c;连接池是一种重要的技术手段&#xff0c;它可以提高数据库连接的复用性和性能。连接池的原理是在应用启动时创建一定数量的数据库连接&#xff0c;并将这些连接保存在一个池中&#xff0c;应用程序需要数据库连接时&#xff0c;从连接池中获取…

静态路由与双线BFD热备份

✍ 路由具体是什么概念&#xff1f; ✍ 路由表和路由协议有什么关系&#xff1f; ✍ 电信联通双线如何做路由热备份&#xff1f; ---- 什么叫路由&#xff1f; ---- 路由器 - 网络设备 - 转发数据 - 要有一张地图 - 路由表 ---- 路由表 - 指明要到达某个目…

.NET开源且免费的Windows远程桌面管理软件

前言 今天要给大家推荐一款由.NET开源且免费的远程桌面管理软件&#xff1a;1Remote。 1Remote官方项目介绍 1Remote是一款现代的远程会话管理和启动器&#xff0c;它让你能够在任何时候快速开启一个远程会话。目前1Remote已支持 微软远程桌面(RDP)、VNC、SSH、Telnet、SFTP、…

Unity3D 游戏框架搭建的过程是什么详解

Unity3D 是一款广泛使用的游戏开发引擎&#xff0c;它提供了丰富的功能和工具&#xff0c;使得开发者可以轻松地创建高质量的游戏。在开始开发一个新的游戏项目之前&#xff0c;我们需要搭建一个游戏框架&#xff0c;这个框架将提供一些基本的功能和结构&#xff0c;为后续的开…

CRM自动化意味着什么?企业如何从中受益?

客户关系管理&#xff08;CRM&#xff09;软件不再仅仅适用于大公司或销售周期长的行业&#xff0c;它越来越成为各种规模企业的重要工具。 在日常工作中&#xff0c;当你陷入流程的所有细节时&#xff0c;可能会产生不必要的工作。因此&#xff0c;如果你想要CRM提供的组织和…

Python特征选择

1 特征选择的目的 机器学习中特征选择是一个重要步骤&#xff0c;以筛选出显著特征、摒弃非显著特征。这样做的作用是: 减少特征&#xff08;避免维度灾难&#xff09;&#xff0c;提高训练速度&#xff0c;降低运算开销&#xff1b; 减少干扰噪声&#xff0c;降低过拟合风险…

Leetcode 第 365 场周赛题解

Leetcode 第 365 场周赛题解 Leetcode 第 365 场周赛题解题目1&#xff1a;2873. 有序三元组中的最大值 I思路代码复杂度分析 题目2&#xff1a;2874. 有序三元组中的最大值 II思路代码复杂度分析思路2 题目3&#xff1a;2875. 无限数组的最短子数组思路代码复杂度分析 题目4&a…

Oracle的立场:官网更换首页与以色列站在一起

Oracle公司的官网&#xff0c;更换了首页内容&#xff0c;明确表明立场&#xff1a;Oracle与以色列站在一起。 声明指出&#xff1a; Oracle谴责针对以色列及其公民的恐怖袭击。Oracle将为其员工、以色列政府和国防机构提供一切必要的支持。 Magen David Adom是一家为以色列公民…

Java代码审计-因酷网校在线教育系统-越权漏洞分析

登录个人账号后&#xff0c;点击基本资料。有更新资料的功能。 查看这个页面的html源码&#xff0c;进行代码审计。&#xff08;这点怎么通过源码怎么找到的就不提了&#xff0c;写上实在啰嗦了。&#xff09; 代码jsp页面源码如下&#xff0c;查看这个表单信息 注意&#xf…

【Mysql】Mysql中的B+树索引

概述 从上一章节我们了解到InnoDB 的数据页都是由7个部分组成&#xff0c;然后各个数据页之间可以组成一个双向链表 &#xff0c;而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表 &#xff0c;每个数据页都会为存储在它里边儿的记录生成一个页目录 &#xff…

【AIGC核心技术剖析】大型语言和视觉助手——LLaVA(论文+源码)

🔥 [新!LLaVA-1.5 在 11 个基准测试上实现了 SoTA,只需对原始 LLaVA 进行简单的修改,利用所有公共数据,在单个 1-A8 节点上在 ~100 天内完成训练,并超越使用数十亿级数据的方法。 LLaVA代表了一种新颖的端到端训练大型多模态模型,结合了视觉编码器和骆马 对于通用的视…

微信小程序开发的OA会议之会议个人中心的页面搭建及模板,自定义组件的学习

目录 一.自定义组件及会议效果编写 效果显示 二.个人中心布局 编写结果 ​编辑 一.自定义组件及会议效果编写 在页面中创建一个以components命名的项目来存放组件 再在components文件夹中创建一个组件&#xff0c;名为 :tabs &#xff0c;创建操作如图所示 刚刚创建好会报…

Android中级——MVVM

MVVM MVVM是什么&#xff1f;MVVM实现前提ModelViewModelView MVVM是什么&#xff1f; Model-View-ViewMode架构&#xff0c;可看作MVP改进版&#xff0c;将此前Presenter的逻辑操作交给ViewMode中的Binder去处理 Mode&#xff1a;封装数据存储及相关操作逻辑&#xff0c;与MV…