1. 问题描述:
你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下:
你写出一个秘密数字,并请朋友猜这个数字是多少。
朋友每猜测一次,你就会给他一个提示,告诉他的猜测数字中有多少位属于数字和确切位置都猜对了(称为“Bulls”, 公牛),有多少位属于数字猜对了但是位置不对(称为“Cows”, 奶牛)。
朋友根据提示继续猜,直到猜出秘密数字。
请写出一个根据秘密数字和朋友的猜测数返回提示的函数,返回字符串的格式为 xAyB ,x 和 y 都是数字,A 表示公牛,用 B 表示奶牛。
xA 表示有 x 位数字出现在秘密数字中,且位置都与秘密数字一致。
yB 表示有 y 位数字出现在秘密数字中,但位置与秘密数字不一致。
请注意秘密数字和朋友的猜测数都可能含有重复数字,每位数字只能统计一次。
示例 1:
输入: secret = "1807", guess = "7810"
输出: "1A3B"
解释: 1 公牛和 3 奶牛。公牛是 8,奶牛是 0, 1 和 7。
示例 2:
输入: secret = "1123", guess = "0111"
输出: "1A1B"
解释: 朋友猜测数中的第一个 1 是公牛,第二个或第三个 1 可被视为奶牛。
说明: 你可以假设秘密数字和朋友的猜测数都只包含数字,并且它们的长度永远相等。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/bulls-and-cows
2. 思路分析:
① 分析题目可以知道我们需要记录secret与guess字符串中字符出现的次数,这样可以根据记录的字符的次数来计算B情况的数目(字符相同但是位置不用的情况),一开始想到的是先遍历一遍secret与guess字符串,首先是匹配当前的位置i对应的字符是否相等,secret[i] == guess[i],如果相等那么属于A情况,这个时候对A情况计数加1即可,当发现不匹配的时候我们需要将不匹配的字符分别记录在两个字典中,这一点非常关键,当我们记录两个字符串中字符出现的次数之后我们就可以遍历记录secret字符串的字典,检查当前字符是否存在字典2中,如果存在说明secret中的字符与guess中的字符位置不相同但是出现的字符是相同的(字典中记录的都是两个字符串在i位置不匹配的情况),我们只需要取出其中当前字符中两个字典的最小值即可(最小值表示的意思是当前相同的字符相互进行匹配),这就属于B情况的,最后拼接一下字符串即可
例如 secret = "1123", guess = "0111",可以发现第二个位置中的数字1是一一对应的,剩下的dic1中记录的为:1:1, 2:1,3:1,dic2:0:1, 1:2所以最终情况B的匹配是:min(dic1[1], dic2[1]) = 1
② 第二种思路是使用哈希表记录一下secret中字符出现的次数,遍历一下guess字符串判断当前遍历的字符是否在secret中,如果存在那么计数加1并且哈希表中对应的字符次数减1,这里主要是统计一下猜对的字符的数目total,最后遍历一下两个字符串判断对应位置相等的字符数目bull,total减去bull的结果就是猜对但是位置不对的字符数目,最后拼接一下结果即可。
③ 第三种思路是在力扣的题解中发现的,思路非常ok,下面是我自己的一些理解:
首先需要声明一个int类型的cache数组,用来匹配在当前位置i中两个字符串中字符不相等的情况(当两个字符串在i位置相同的时候非常简单而且是属于A情况的,我们主要是讨论当两个字符串中对应位置不相同情况的匹配),其中使用到的策略是当前遍历的secret[i]字符对应cache数组中是小于0的,那么这个时候应该是属于B情况的,计数加1,当前遍历的guess[i]字符对应cache数组中是小于0的,那么这个时候应该也是属于B情况的,计数也应该是加1的,其中我们需要结合这两个判断一起理解,cache[secret.chatAt(i)] < 0说明之前使在guess字符串是出现过这个字符的这样才有可能导致当前位置i对应的数组值是小于0的,所以属于B情况的,当cache[guess.chatAt(i)] > 0的时候说明secret字符串是存在这个字符与之进行匹配的,判断之后我们需要对当前的secret[i],guess[i]对应的cache数组位置加上1与减去1,之后才可以对之后的字符进行匹配,感觉整个思路还是比较巧妙的,值得学习学习,我在作者java代码的基础上使用python重写了一遍
3. 代码如下:
字典:
import collections
class Solution:
def getHint(self, secret: str, guess: str) -> str:
A, B = 0, 0
dic1, dic2 = collections.defaultdict(int), collections.defaultdict(int)
for i in range(len(secret)):
if secret[i] == guess[i]:
A += 1
# 不匹配的时候记录两个字符出现的次数
else:
dic1[secret[i]] += 1
dic2[guess[i]] += 1
# 检查字典1记录的字符串1的匹配情况, 当字符存在于两个字典中时取出两者中的最小猪
for key, value in dic1.items():
if key in dic2: B += min(value, dic2[key])
return str(A) + "A" + str(B) + "B"
import collections
class Solution:
def getHint(self, secret: str, guess: str) -> str:
dic = collections.defaultdict(int)
for c in secret:
dic[c] += 1
total = 0
for c in guess:
if dic[c]:
total += 1
dic[c] -= 1
bull = 0
for i in range(len(secret)):
if secret[i] == guess[i]: bull += 1
return str(bull) + "A" + str(total - bull) + "B"
cache数组对出现的字符进行自增与自减进行匹配:
import collections
class Solution:
def getHint(self, secret: str, guess: str) -> str:
# 初始化长度为10能够记录字母进行匹配的数组
cache = [0] * 10
A, B = 0, 0
for i in range(len(secret)):
cs, cg = secret[i], guess[i]
# 当对应位置字符相等说明是属于A情况的
if cs == cg: A += 1
else:
# 小于0说明之前guess字符串中是出现过当前这个字符并且还没有匹配
if cache[int(cs)] < 0: B += 1
# 大于0说明之前secret是出现过当前这个字符并且还还没有匹配
if cache[int(cg)] > 0: B += 1
cache[int(cs)] += 1
cache[int(cg)] -= 1
return str(A) + "A" + str(B) + "B"