• 【LeetCode】超简单!猜数字游戏!


    作者:我脱下短袖

    公众号:算法无遗策

    今天分享一个LeetCode题,题号是299,标题是猜数字游戏,题目标签是哈希表,题目难度是简单。

    这个题是简单题,但里面的思路很有意思,用到了反证法。

    题目描述

    你正在和你的朋友玩 猜数字(Bulls and Cows)游戏:你写下一个数字让你的朋友猜。每次他猜测后,你给他一个提示,告诉他有多少位数字和确切位置都猜对了(称为“Bulls”, 公牛),有多少位数字猜对了但是位置不对(称为“Cows”, 奶牛)。你的朋友将会根据提示继续猜,直到猜出秘密数字。

    请写出一个根据秘密数字和朋友的猜测数返回提示的函数,用 A 表示公牛,用 B 表示奶牛。

    请注意秘密数字和朋友的猜测数都可能含有重复数字。

    示例 1:

    输入: secret = "1807", guess = "7810"
    
    输出: "1A3B"
    
    解释: 1 公牛和 3 奶牛。公牛是 8,奶牛是 0, 1 和 7。
    

    示例 2:

    输入: secret = "1123", guess = "0111"
    
    输出: "1A1B"
    
    解释: 朋友猜测数中的第一个 1 是公牛,第二个或第三个 1 可被视为奶牛。
    

    说明: 你可以假设秘密数字和朋友的猜测数都只包含数字,并且它们的长度永远相等。

    解题

    当我做这道题的时候,有点过分关注公牛和奶牛数量的统计,忽略掉了既不是公牛也不是奶牛的数量统计。

    当然不是说仅仅关注公牛和奶牛的数量统计而不能得到答案,是因为我后面想到的一个优化,需要使用到其它性质的数字。

    既然题目标签是哈希表,而且题目描述挺适合使用直接寻址表,使用的输入示例都是0~9组成的,可以直接创建10个空间的数组,分别放置0~9

    直接寻址表也是哈希表,适合于不是很散的范围使用。

    我们假设输入示例是“1123”“0111”,公牛数字的统计很简单,遍历一次,判断相同位置上的数字是否相等;而奶牛数字的统计需要借助两个直接寻址表,分别统计两个输入字符串中不是公牛数字的数量。

    两个直接寻址表

    但我想要一个直接寻址表应该怎么办呢?

    可以借助既不是公牛也不是奶牛的数量统计。

    还是刚才的示例“1123”“0111”,在“1123”中可以看到‘2’‘3’不属于公牛数字和奶牛数字,可以统计到两者不属于的数量。

    如果得到了公牛数量,也得到了两者不属于的数量,就可以得到奶牛的数量。

    这是因为公牛数量 + 奶牛数量 + 两者不属于的数量,刚好等于一个字符串“1123”的长度。

    既然是使用一个直接寻址表,怎么才能得到‘2’‘3’呢?

    这时候我们就需要一个正负判断了,可以将“1123”中所有的数字都是正数,而“0111”中所有的数字都是负数。公牛数字在同一个位置上相等,而奶牛数字有了正负可以互相抵消掉了,剩下的就是不属于公牛和奶牛的数字了。

    正负选择

    前几天分享的文章 (天际线问题 和 完美矩形) 也有类似的小技巧,正负选择,例如遇左边界 (正) ,高度入堆;遇右边界 (负) ,高度出堆。

    以后看题的时候,遇到类似的情况,一定要能巧妙地想到这点。

    Java代码
    class Solution {
        public String getHint(String secret, String guess) {
            int len = secret.length();
            int A = 0; // 公牛数量
            int B = 0; // 奶牛数量
            // 创建直接寻址表
            int[] address = new int[10]; // secret和guess奶牛数字的相互抵消
            for (int i = 0; i < len; i++) {
                if (secret.charAt(i) == guess.charAt(i)) A++;
                else {
                    address[secret.charAt(i) - '0']++;
                    address[guess.charAt(i) - '0']--;
                }
            }
            for (int addr : address)
                if (addr > 0)
                    B += addr;
            B = len - A - B;
            return A + "A" + B + "B";
        }
    }
    
    Go语言代码
    import (
        "fmt"
    )
    
    func getHint(secret string, guess string) string {
        len := len(secret)
        A := 0 // 公牛统计
        B := 0 // 奶牛统计
        // 创建直接寻址表
        address := [10]int{0}
        for i := 0; i < len; i++ {
            if secret[i] == guess[i] {
                A++
                continue
            }
            address[secret[i]-'0']++
            address[guess[i]-'0']--
        }
        for _, addr := range address {
            if addr > 0 {
                B += addr
            }
        }
        B = len - A - B
        // 字符串拼接
        return fmt.Sprint(A, "A", B, "B")
    }
    
    Go语言执行结果
    执行用时 : 0 ms , 在所有 Go 提交中击败了 100.00% 的用户
    内存消耗 : 2.3 MB , 在所有 Go 提交中击败了 75.00% 的用户
    

    Java提交之后,执行结果有点惨不忍睹,一度怀疑这算法不是题目标签中更优秀的算法,可能前面提交的人太多,相同的执行用时已经赶不上前面的了。

    为了Java和Golang有明显的对比,Java就贴上执行用时和内存消耗。

    执行用时 : 6 ms
    内存消耗 : 38.4 MB
    

    通过执行结果,Golang的执行用时差别不是很大,而消耗内存切实很省空间。

    关注「五分钟学算法」,一起领悟算法的魅力,大家加油 (●'◡'●)

    END


    ● LeetCode图解|237.删除链表中的节点

    ● LeetCode图解|206.反转链表

    ● LeetCode图解|37.解数独

    LeetCode图解|191.位1的个数

    点“在看”你懂得 

  • 相关阅读:
    机器学习实践笔记3(树和随机森林)
    Cocos2d-x3.1回调函数具体解释
    base 64 编解码器
    HDU 4915 Parenthese sequence _(:зゝ∠)_ 哈哈
    跟我extjs5(03--在项目过程中加载文件)
    备份和还原数据库
    Android学习–Android app 语言切换功能
    Android app内语言环境切换
    Android学习–Android app 语言切换功能
    swift:自定义UICollectionViewFlowLayout
  • 原文地址:https://www.cnblogs.com/csnd/p/16674967.html
Copyright © 2020-2023  润新知