Skip to content

feat: 每日一题 - 2019-09-16 and 2019-08-21 #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions daily/2019-08-21.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 毎日一题 - 桶中取黑白球

## 信息卡片

* 时间:2019-08-21
* tag:`Math` `位运算`
## 题目描述
```
有一个桶,里面有白球,黑球各100个,你必须用以下规则将球取出来:
- 每次从桶里取两个球
- 如果两个球是相同的颜色,那么再放一个黑球
- 如果两个球是不同的颜色,那么再放一个白球。
问:最后一个球是黑球的概率是多少?
```

## 参考答案

### 1. 数学分析原问题

首先我们来仔细读题看看我们有哪些知道的信息:

- 不管什么情况,每次球的总数减1;
- 两黑:黑球-1,白球0;
- 两白:黑球+1,白球-2;
- 一黑一白:黑球-1,白球0;
- 最后两球只要不是一黑一白,最后一球都是黑;

初始状态是100个黑球和100个白球,从上面三个状态可知道,黑球要么+1要么-1,而白球要么不变要么-2;在198次取球后,我们可知剩余两个球,现在假设剩余的两球为一黑一白,可以证明这是不存在的。

因为白球下降是以2的倍数下降,不可能从100下降至1,;故剩余两球肯定不是一黑一白的情况,那么最后一球的情况必然为黑。


### 2. 原问题拓展(n个黑球和m个白球)

在n+m-2次取球后,剩余两个球。

由于我们知道白球数下降是以2的倍数下降,如果m为偶数的话,是不可能下降至1;即同上1,最后一球必为黑球。如果m为奇数的话,最后必然是k黑1白(k>=1),显然对于任意的k,要么剩余全是黑球,要么黑球不断减1,最后变为1黑1白。全黑和1黑1白最后的结果都是剩余一个白球。

得出结论,最后一球结果无关黑球数量(n>=0),仅与白球数量m有关。

- 如果白球m为奇数,最后一球必然白;
- 如果白球m为偶数,最后一球必然黑;

### 3. 抽象为数学模型,严格证明

不妨设黑球为0,白球为1;

- 两黑:F(0,0) = 0;表示两个黑球生一黑;
- 两白:F(1,1) = 0;表示两个白球生一黑;
- 一黑一白:F(0,1) = 0;表示一个黑球一个白球生一白;

仔细观察就会发现这个函数F就是XOR(异或);

那么m个黑球和n个白球,就抽象为m个0和n个1作异或的结果;而且我们可知异或满足结合律和交换律(证明略,最简单的证明方法枚举)。

那么问题就很简单,对于任意多0,异或结果依然是0,所以对于任意多1,只需要考虑1个数的奇偶性就可判断最后剩余1个1还是0个1;

结论同2:

- 1(白球)的个数奇数,最后异或结果为1;
- 1(白球)的个数偶数,最后异或结果为0;


## 优秀解答

>暂缺
162 changes: 162 additions & 0 deletions daily/2019-09-15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# 毎日一题 - 水壶问题

## 信息卡片

* 时间:2019-09-15
* 题目链接:https://leetcode-cn.com/problems/water-and-jug-problem/
* tag:`Math`
## 题目描述
```
给你一个装满水的 8 升满壶和两个分别是 5 升、3 升的空壶,请想个优雅的办法,使得其中一个水壶恰好装 4 升水,每一步的操作只能是倒空或倒满。
```

## 参考答案

1.数学分析解答

上面的问题是一个特例,我们可以抽象为[leetcode-365-水壶问题](https://leetcode-cn.com/problems/water-and-jug-problem/)。
```
有两个容量分别为 x升 和 y升 的水壶(壶1,壶2)以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?
```

解题核心思路(x < y,即壶1容量小于壶2,x == y的情况后面讨论):

1. 将壶2倒满,往壶1倒入至满。
2. 若壶1满,记录当前壶2中新水量。壶1倒出,将壶2中剩余的继续往壶1中倒入;(当壶1满,继续此操作,并记录当前壶2中新水量nw, 若此新水量已被记录,则)。
3. 若出现壶1不满时(即此时壶2必空),重复操作1。

开辟一个新数组nws记录所有新水量,对任意nws[i],可构造的水量为nws[i],nws[i]+x,nws[i]+y。

(其实不需要新数组,因为数学上可以证明新水量的值循环周期呈现,故可以使用一个临时变量cur,当cur==x为终止条件)

数学证明新水量nw值是循环周期的:
![1111](<https://raw.githubusercontent.com/lvguofeng1303/markdownimage/master/daily/%E6%B0%B4%E5%A3%B6%E9%97%AE%E9%A2%98/math%20prove.jpg>)

个别特殊情况考虑:

- x == z || y == z; **true**
- x == 0 || x+y < z; **false**
- x+y == z || z == 0; **true**

```c++
class Solution {
public:
bool canMeasureWater(int x, int y, int z) {
if(x > y) return canMeasureWater(y, x, z);
if(x == z || y == z) return true;
if(x == 0 || x+y < z) return false;
if(x+y == z || z == 0) return true;
int cur = y - x;
while(cur != x){
if(cur == z) return true;
if(cur > x){
if(cur + x == z) return true;
cur = cur - x;
}
else{
if(cur + y == z || cur + x == z) return true;
cur = y - x + cur;
}
}
return false;
}
};
```

2.BFS

不仅可以计算是否能获取 z 升水,而且可以获得最少多少操作可获取 z 升水。(缺点,无法通过,因为需要太大的空间,需要申请一个三维数组记录状态)

核心思想就是状态转移问题:

壶0(x+y),壶1(x),壶2(y),壶0是本是无限大水池,同理于定义为大小为x+y的壶。用bfs的思想,使用一个队列记录所有新的状态。

对于任意状态(c,a,b),状态转移就是:

- 若c不为0,将壶0倒水入壶1或壶2;若a不为0,将壶1倒水入壶0或壶2;若b不为0,将壶2倒水入壶0或壶1;
- 记录每个新状态,并入队,若此状态访问过则不入队。

特殊情况考虑同1。

```c++
class Solution {
public:
struct state{
int nums[3];
state(int xy, int x, int y){
nums[0] = xy;
nums[1] = x;
nums[2] = y;
}
};

state pour_water(state cur, int src, int det, int size[]){
state ans = cur;
int need_w = size[det] - cur.nums[det];
if(need_w <= cur.nums[src]){
ans.nums[det] += need_w;
ans.nums[src] -= need_w;
}
else{
ans.nums[det] += ans.nums[src];
ans.nums[src] = 0;
}
return ans;
}

bool canMeasureWater(int x, int y, int z) {
if(x > y) return canMeasureWater(y, x, z); //
if(x == z || y == z) return true;
if(x == 0 || x+y < z) return false;
if(x+y == z || z == 0) return true;
int visited[x+y+1][x+1][y+1];
int water_size[3] = {x+y, x, y};
memset(visited, 0, sizeof(visited));
state cur(x+y, 0, 0);
queue<state> q;
q.push(cur);
int step = 0;
while(!q.empty()){
int size = q.size();
while(size){
state temp(0, 0, 0);
cur = q.front();
if(cur.nums[1] + cur.nums[2] == z) return true;
visited[cur.nums[0]][cur.nums[1]][cur.nums[2]] = 1;
q.pop();
if(cur.nums[0] != 0){
temp = pour_water(cur, 0, 1, water_size);
if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1)
q.push(temp);
temp = pour_water(cur, 0, 2, water_size);
if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1)
q.push(temp);
}
if(cur.nums[1] != 0){
temp = pour_water(cur, 1, 2, water_size);
if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1)
q.push(temp);
temp = pour_water(cur, 1, 0, water_size);
if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1)
q.push(temp);
}
if(cur.nums[2] != 0){
temp = pour_water(cur, 2, 1, water_size);
if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1)
q.push(temp);
temp = pour_water(cur, 2, 0, water_size);
if(visited[temp.nums[0]][temp.nums[1]][temp.nums[2]] != 1)
q.push(temp);
}
size--;
}
step++;
}
return false;
}
};
```

## 优秀解答

>暂缺
158 changes: 158 additions & 0 deletions daily/2019-09-16.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# 毎日一题 - 版本号比较

## 信息卡片

* 时间:2019-09-16
* 题目链接:<https://leetcode-cn.com/problems/compare-version-numbers/>
* tag:`String`
## 题目描述
```
比较两个版本号 version1 和 version2。
如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。

你可以假设版本字符串非空,并且只包含数字和 . 字符。

. 字符不代表小数点,而是用于分隔数字序列。

例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。

你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。

示例 1:

输入: version1 = "0.1", version2 = "1.1"
输出: -1
示例 2:

输入: version1 = "1.0.1", version2 = "1"
输出: 1
示例 3:

输入: version1 = "7.5.2.4", version2 = "7.5.3"
输出: -1
示例 4:

输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。
示例 5:

输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。

提示:

版本字符串由以点 (.) 分隔的数字字符串组成。这个数字字符串可能有前导零。
版本字符串不以点开始或结束,并且其中不会有两个连续的点。
```

## 参考答案

### 1. 递归解决

其实这个问题其实简化后就是依次比较每一个修订版本大小,所以问题有以下几点:

1. 获取每个修订版本号大小;
2. 处理每个修订版本号前导零问题;
3. 处理不同版本有不同次数修订版本;

问题1:这个如果对字符串处理比较熟悉的会比较简单,直接遍历循环找到第一个逗号first_dot(找不到的情况设为-1),str.substr(0, first_dot)即可。针对第二,第三个逗号,我们用递归的方案回避,这样每次我们都相当于找第一个逗号前的数字。

问题2:前导零问题更容易解决,在遍历过程中找到第一个非零数first_no_zero,str.substr(first_no_zero, first_dot - first_no_zero)。当然更简单的方案是定义初值v1 = 0,每次计算v1 = v1*10 + str[i] - 'a'

问题3:针对不同次数的修订版本,我们可以在字符串末尾填0表示。即有一个版本号first_dot = -1。

代码如下:

```c++
class Solution {
public:
int first_num(string str, int& first_dot){
int v1 = 0;
first_dot = -1;
for(int i = 0; i < str.size(); i++){
if(str[i] == '.'){
first_dot = i;
break;
}
else
v1 = v1 * 10 + (str[i] - '0');
}
return v1;
}

int compareVersion(string version1, string version2) {
int v1 = 0, v2 = 0;
int v1_first_dot, v2_first_dot;
v1 = first_num(version1, v1_first_dot);
v2 = first_num(version2, v2_first_dot);
if(v1 > v2)
return 1;
else if(v1 < v2)
return -1;
else{
if(v1_first_dot == -1 && v2_first_dot == -1)
return 0;
if(v1_first_dot == -1)
version1 = "0";
else
version1 = version1.substr(v1_first_dot+1);
if(v2_first_dot == -1)
version2 = "0";
else
version2 = version2.substr(v2_first_dot+1);
return compareVersion(version1, version2);
}
}
};

```

### 2. 数组

解析每个版本号,放入数组,依次比较大小。

```c
int compareVersion(char * version1, char * version2){
if (version1 == NULL || version2 == NULL) return -1;
int *val1 = (int *)calloc(1024, sizeof(int));
int *val2 = (int *)calloc(1024, sizeof(int));
int len1 = strlen(version1), top1 = 0;
int len2 = strlen(version2), top2 = 0;
int i, n;
for (i = 0, n = 0; i < len1; ++i) { //解析版本1
if (version1[i] == '.') {
val1[top1++] = n;
n = 0;
}else n = n*10 + (version1[i] & 0x0f);
}
val1[top1++] = n;
for (i = 0, n = 0; i < len2; ++i) { //解析版本1
if (version2[i] == '.') {
val2[top2++] = n;
n = 0;
}else n = n*10 + (version2[i] & 0x0f);
}
val2[top2++] = n;
for (i = 0; i < top1 && i < top2; ++i) { //比较版本大小
if (val1[i] > val2[i]) return 1;
else if (val1[i] < val2[i]) return -1;
}
if (i < top1) { //由于可能有的版本还没遍历完
while (i < top1) if (val1[i++]) return 1; //只要版本后面的数字出现的不是0,就意味着两个版本不一样
}else{
while (i < top2) if (val2[i++]) return -1;
}
return 0;
}
//作者:ljj666
//链接:https://leetcode-cn.com/problems/compare-version-numbers/solution/cyu-yan-jian-jian-dan-dan-de-ji-xing-dai-ma-jie-37/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
```


## 优秀解答

>暂缺