题目描述
题目链接
给定一个从1 到 n 排序的整数列表。
首先,从左到右,从第一个数字开始,每隔一个数字进行删除,直到列表的末尾。
第二步,在剩下的数字中,从右到左,从倒数第一个数字开始,每隔一个数字进行删除,直到列表开头。
我们不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。
返回长度为 n 的列表中,最后剩下的数字。
示例:
输入:
n = 9,
1 2 3 4 5 6 7 8 9
2 4 6 8
2 6
6
输出:
6
解题思路
此题是约瑟夫问题的变体,可以总结递推公式求解。
下面通过举例来总结此问题的规律。当n=9时,子问题分解如下:
当前列表 | 删除方向 |
---|---|
1 2 3 4 5 6 7 8 9 | 从左到右删除 |
2 4 6 8 | 将当前列表看成1 2 3 4,结果乘以2再返回 |
1 2 3 4 | 从右到左删除,结果返回 |
1 3 | 将当前列表看成2 4,结果减1再返回 |
2 4 | 将当前列表看成1 2,结果乘以2再返回 |
1 2 | 从左到右删除,结果返回 |
2 | 剩余1个数,结果返回 |
当n=10时,子问题分解如下:
当前列表 | 删除方向 |
---|---|
1 2 3 4 5 6 7 8 9 10 | 从左到右删除 |
2 4 6 8 10 | 将当前列表看成1 2 3 4 5,结果乘以2再返回 |
1 2 3 4 5 | 从右到左删除,结果返回 |
2 4 | 将当前列表看成1 2,结果乘以2再返回 |
1 2 | 从左到右删除,结果返回 |
2 | 剩余一个数,结果返回 |
从上面可以看出,递归时需要判断的条件有两个。一个是删除方向,一个是列表的长度是否被2整除。
如果列表长度是奇数,并且是从左到右删除,例如是 1 2 3 4 5 6 7 8 9,那么结果和 1 2 3 4 5 6 7 8 相同。
如果列表长度是奇数,并且是从右到左删除,例如是 1 2 3 4 5 6 7 8 9,那么结果和 2 4 6 8 相同,也就是 1 2 3 4 乘以2。
如果列表长度是偶数,并且是从左到右删除,例如是 1 2 3 4 5 6 7 8,那么结果和 2 4 6 8 相同,也就是 1 2 3 4 的结果乘以2。
如果列表长度是偶数,并且是从右到左删除,例如是 1 2 3 4 5 6 7 8,那么结果是 1 3 5 7 ,也就是 2 4 6 8 的结果减1,也就是 1 2 3 4 的结果乘以2再减1。
递推公式如下(其中n代表列表长度,direc代表删除方向,0代表从左到右删除,1代表从右到左删除),并且f(1)=1:
f
(
n
,
d
i
r
e
c
)
=
{
2
∗
f
(
n
/
2
,
1
)
,
d
i
r
e
c
=
0
&
&
n
%
2
=
0
f
(
n
−
1
,
0
)
,
d
i
r
e
c
=
0
&
&
n
%
2
≠
0
2
∗
f
(
n
/
2
,
0
)
−
1
,
d
i
r
e
c
=
1
&
&
n
%
2
=
0
2
∗
f
(
n
/
2
,
0
)
,
d
i
r
e
c
=
1
&
&
n
%
2
≠
0
f(n, direc) = \left\{ \begin{aligned} 2*f(n/2, 1),direc=0\And\And n\%2=0 \\ f(n-1, 0),direc=0\And\And n\%2\not=0 \\ 2*f(n/2, 0)-1,direc=1\And\And n\%2=0 \\ 2*f(n/2, 0),direc=1\And\And n\%2\not=0 \\ \end{aligned} \right.
f(n,direc)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧2∗f(n/2,1),direc=0&&n%2=0f(n−1,0),direc=0&&n%2=02∗f(n/2,0)−1,direc=1&&n%2=02∗f(n/2,0),direc=1&&n%2=0
代码如下:
class Solution {
public:
int lastRemainingWithDirec(int n, int direction) { // direction等于0代表从左到右,等于1代表从右到左
if (n == 1) return 1;
else if (direction == 0) {
if (n % 2 == 0) return 2 * lastRemainingWithDirec(n / 2, 1);
else return lastRemainingWithDirec(n - 1, 0);
}
else {
if (n % 2 == 0) return 2 * lastRemainingWithDirec(n / 2, 0) - 1;
else return 2 * lastRemainingWithDirec(n / 2, 0);
}
}
int lastRemaining(int n) {
return lastRemainingWithDirec(n, 0);
}
};