算法 2.5 贪心算法:行相等的最少多米诺旋转【leetcode 1007】

题目描述

在一排多米诺骨牌中,A[i] 和 B[i] 分别代表第 i 个多米诺骨牌的上半部分和下半部分
一个多米诺是两个从 1 到 6 的数字同列平铺形成的 —— 该平铺的每一半上都有一个数字
我们可以旋转第 i 张多米诺,使得 A[i] 和 B[i] 的值交换
返回能使 A 中所有值或者 B 中所有值都相同的最小旋转次数
如果无法做到,返回 -1

示例:
输入:A = [2,1,2,4,2,2], B = [5,2,6,2,3,2]
输出:2

示例:
输入:A = [3,5,1,2,3], B = [3,6,3,3,4]
输出:-1

数据结构

  • 数组

算法思维

  • 遍历、贪心

关键知识点:贪心算法(greedy algorithm)

  • 适用于求解 最优子结构 的问题,最优子结构就是局部最优解能决定全局最优解
  • 贪心法可以解决 图中的最小生成树、求哈夫曼编码 等问题
  • 但有些问题贪心算法并不能获得最优解,如背包问题
  • 贪心法也可以用作辅助算法或者直接解决一些近似最优解的问题
核心理念
  • 通过每一步的局部最优解来获得全局最优解
求解步骤
  1. 创建数学模型来描述问题
  2. 把求解的问题分成若干个子问题
  3. 对每一子问题求解,得到子问题的局部最优解
  4. 把子问题的解合并成原来问题的一个解


解题步骤


一. Comprehend 理解题意
行相等的最小多米诺旋转
  • 多米诺骨牌
    • 每张牌由2个1-6的数字A[i], B[i]组成
  • 多米诺旋转
    • 交换每张牌的A[i]与B[i]
  • 行相等的最小多米诺旋转
    • 使得A或B中元素全部相同的最小旋转次数
细节问题
  • 可能不存在多米诺旋转使 A 中所有值或者 B 中所有值都相同


二. Choose 选择数据结构与算法
数据结构选择
  • 输入的数据类型为两个整形数组表示每张多米诺骨牌的两个数字
  • 输出的数据类型为一个整数
  • 因为需要处理的是两个由1到6数字组成的整数集合,我们使用数组作为数据结构
算法思维选择
  • 题目要求寻找使得行相等的最小多米诺旋转
  • 首先判断是否存在这样的旋转,再求解最小旋转次数
  • 统计 1-6 这六个数字的出现次数
      在A中出现的次数
      在B中出现的次数
      在骨牌出现的次数(在多少个骨牌中出现过,骨牌上下都出现需记作一次)

三. Code 编码实现基本解法
解题思路剖析
  • 统计1到6这6个数字出现在多少张多米诺骨牌中以及分别在A、B出现的次数
  • 只有数字2在所有牌中出现
  • 且在A中出现的次数4 > 在B中出现的次数3
代码实现
class Solution {
    public int minDominoRotations1(int[] A, int[] B) {
        int n = A.length;
        //记录1-6数字分别在A,B中出现的次数、在多米诺骨牌出现的次数
        int[] countA = new int[6];
        int[] countB = new int[6];
        int[] countAB = new int[6];
        
        //遍历骨牌,统计出现次数
        for (int i = 0; i < n; i++) {
            countA[A[i] - 1]++;
            countB[B[i] - 1]++;
            countAB[A[i] - 1]++;
            if (A[i] != B[i])
                countAB[B[i] - 1]++;
        }
        
        //在骨牌统计表找到出现次数等于骨牌总数的数字
        for (int i = 0; i < 6; i++) {
            if (countAB[i] == n) { //计算最小的旋转次数
                return (n - Math.max(countA[i], countB[i]));
            }
        }

        //找不到返回-1
        return -1;
    }
}

时间复杂度:O(n)
  • 统计 1 - 6 数字出现的次数,遍历多米诺骨牌 O(n)
  • 寻找在所有多米诺骨牌出现的数字及最小多米诺旋转,需要对 6×3 数组进行操作 O(1)

空间复杂度:O(1)
  • 常数级变量空间 O(1)

执行耗时:7 ms,击败了 33.33% 的Java用户
内存消耗:46.1 MB,击败了 85.16% 的Java用户

四. Consider 思考更优解
剔除无效代码 优化空间消耗
  • 遍历所有骨牌是必须的吗?
寻找更好的算法思维
  • 每张多米诺骨牌上只有两个数字,如果存在行相等则旋转的最终结果
    • A中的数字都是 A[0] 或者 A中的数字都是 B[0]
    • B中的数字都是 A[0] 或者 B中的数字都是 B[0]
  • 故只需考察A[0]、B[0]在其他骨牌上的出现情况
  • 若某骨牌中不存在A[0]、B[0],则提前终止,无需浏览后面的骨牌
贪心算法
  1. 创建数学模型来描述问题
    • 给定两个由 1-6 数字组成的数组 A 和 B
    • 通过多米诺旋转(交换 A[i] 和 B[i] ),使得 A 或 B 中元素全部相同
    • 要求进行尽可能少的交换来完成任务
  2. 划分子问题
    最小多米诺旋转如果存在,一定是下面两种情况中的一种:
    • 将 A 或 B 中的元素全部变成 A[0]
    • 将 A 或 B 中的元素全部变成 B[0]
  1. 求解子问题
    • 尝试将A中的元素全部变成A[0]
      • 需要交换(A[1], B[1]),(A[3], B[3])
      • 执行2次多米诺旋转操作
    • 再尝试将B中的元素全部变成A[0]
      • 需要交换(A[0], B[0]),(A[2], B[2]) ,(A[4], B[4])
      • 执行3次多米诺旋转操作
    • 尝试将A中的元素或B中的元素全部变成B[0]
      • 很快发现第二张牌的两个数字没有5,那么不可能将A中或B中元素全部变成B[0]
  2. 合并子问题的解
    • 尝试将A中的元素全部变成A[0]
      执行2次多米诺旋转操作
    • 再尝试将B中的元素全部变成A[0]
      执行3次多米诺旋转操作
    • 尝试将A中的元素或B中的元素全部变成B[0]
      不可能将A中或B中元素全部变成B[0]

综上最小多米诺旋转次数为 2

五. Code 编码实现最优解
解题思路剖析
  • 判断能否将A中或B中全部元素变成A[0]
    • 若能,返回旋转次数少的那个,不用再检查B[0]
      • 如果A[0] == B[0],再检查B[0]没有意义
      • 如果A[0] != B[0],仅当骨牌中只有A[0]、B[0]两个数字才能将A中或B中全部元素变成B[0],此时最小旋转次数将和A[0]一样
    • 若不能,并且A[0] != B[0],再判断能否将A中或B中全部元素变成B[0]
class Solution {
    public int minDominoRotations(int[] A, int[] B) {
        int n = A.length;
        int rotations = check(A[0], A, B, n);//元素全部变成A[0],最少需要多少次旋转

        //如果A[0]==B[0], 那么不用继续检查B[0]
        //如果A[0]!=B[0] 且可以将A或B中的元素全部变成A[0],那么也不用再检查B[0]
        if (rotations != -1 || A[0] == B[0])
            return rotations;
        else
            return check(B[0], A, B, n); //全部变成B[0],最少需要多少次旋转
    }

    //检查将A或者B中元素全部变成x需要多少次旋转
    public int check(int x, int[] A, int[] B, int n) {
        //rotationsA存将A中元素变成x需要多少次旋转,rotationsB存将B中元素变成x需要多少次旋转
        int rotationsA = 0, rotationsB = 0;

        //遍历骨牌判断是否能完成任务(在A中完成或者在B中完成)
        for (int i = 0; i < n; i++) {
            // 如果当前多米诺骨牌上没有数字x,那么不可能完成任务
            if (A[i] != x && B[i] != x) return -1;
            // 如果当前多米诺骨牌上A[i]不是x,那么rotationsA需要+1
            else if (A[i] != x) rotationsA++;
            // 如果当前多米诺骨牌上B[i]不是x,那么rotationsB需要+1
            else if (B[i] != x) rotationsB++;
        }
        
        // 返回最小旋转次数
        return Math.min(rotationsA, rotationsB);
    }
}

时间复杂度:O(n)
  • 最差情况:需要遍历所有骨牌 O(n)

空间复杂度:O(1)
  • 常数级内存空间 O(1)

执行耗时:4 ms,击败了 99.06% 的Java用户
内存消耗:46.2 MB,击败了 77.42% 的Java用户

六. Change 变形与延伸
题目变形
  • (练习)每一张骨牌上有3个数字,每次旋转可互换3个数字中相邻的两个,求使得A、B、C中某一个数组数字全部一样的最小多米诺旋转次数?
延伸扩展
  • 贪心算法是一种基础的算法思维
    • 适用条件:最优子结构可获得全局最优解
  • 贪心算法在实际工作中的应用
    • 图中的最小生成树算法
    • 单源最短路径迪杰斯特拉算法
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容