Skip to content

其他网站 ​

1115. 取石子游戏 ​

dfs, https://www.acwing.com/problem/content/description/1117/

有两堆石子,两个人轮流去取,每次取的时候,只能从较多的那堆石子里取,并且取的数目必须是较少的那堆石子数目的整数倍(不能不取)。

最后谁能够把一堆石子取空谁就算赢。

比如初始的时候两堆石子的数目是25和7

25 7→11 7→4 7→4 3→1 3→1 0
选手1取选手2取选手1取选手2取选手1取

最后选手1(先取的)获胜,在取的过程中选手2都只有唯一的一种取法。

给定初始时石子的数目,如果两个人都采取最优策略,请问先手能否获胜。

输入格式

输入包含多数数据。每组数据一行,包含两个正整数 a 和 b,表示初始时石子的数目。

输入以两个0表示结束。

输出格式

如果先手胜,输出”win”,否则输出”lose”。

数据范围:1≤a,b≤10^9

输入样例:

34 12
15 24
0 0

输出样例:

win
lose

提示

假设石子数目为 (a,b) 且 a≥b,如果 [a/b]≥2 则先手必胜,如果 [a/b]<2,那么先手只有唯一的一种取法。

[a/b] 表示 a 除以 b 取整后的值。

按题目提示递归,函数返回值为布尔变量,取一次石子先后手交换,返回相反的结果。注意边界条件,如果有两堆一样的则“当前先手”胜。

python
# 徐至晟 24光华管院
def f(x,y):
    if x<y:
        return f(y,x)
    if x>=y*2:
        return True
    elif x==y:
        return True
    else:
        return not f(x-y,y)

a,b=map(int,input().split())
while a:
    if f(a,b):
        print("win")
    else:
        print("lose")
    a,b=map(int,input().split())

思路:遍历先手取完后所有可能的结果,如果后手有必赢策略则lose,反之win

python
# 彭凌越 24光华管理学院
dic = {}
def dfs(a,b):
    if (a,b) in dic:
        return dic[(a,b)]
    if a==0 or b==0:
        dic[(a,b)]=True
        return True
    if a<b:
        a,b = b,a
    if a%b==0:
        dic[(a,b)]=True
        return True
    for i in range(1,a//b+1):
            if not dfs(a-i*b,b):
                dic[(a,b)]=True
                return True
    dic[(a,b)]=False
    return False
while True:
    a, b = map(int, input().split())
    if a == 0 and b == 0:
        break
    if dfs(a,b):
        print('win')
    else:
        print('lose')
python
from functools import lru_cache
import sys
sys.setrecursionlimit(1<<30)

@lru_cache(maxsize=None)
def can_win(a, b):
    if a == 0 or b == 0:
        return False  # 如果有一堆石子为空,则先手输
    if a > b:
        for i in range(1, a // b + 1):
            if not can_win(a - b * i, b):
                return True
    else:
        for i in range(1, b // a + 1):
            if not can_win(a, b - a * i):
                return True
    return False

def main():
    while True:
        a, b = map(int, input().split())
        if a == 0 and b == 0:
            break
        if can_win(max(a, b), min(a, b)):
            print("win")
        else:
            print("lose")

if __name__ == "__main__":
    main()

NC208830找到搜索二叉树中两个错误的节点 ​

https://ac.nowcoder.com/acm/problem/208830

一棵二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这棵二叉树不再是搜索二叉树,请按升序输出这两个错误节点的值。(每个节点的值各不相同)

搜索二叉树:满足每个节点的左子节点小于当前节点,右子节点大于当前节点。

样例1图

img

样例2图

img

数据范围:3≤n≤1000003≤n≤100000,节点上的值满足 1≤val≤n1≤val≤n ,保证每个value各不相同

进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

示例1

输入

{1,2,3}

返回值

[1,2]

说明

如题面图

示例2

输入

{4,2,5,3,1}

返回值

[1,3]

我们可以通过对二叉树进行中序遍历(In-order Traversal)来解决这个问题。

解题思路

在一个正常的二叉搜索树(BST)中,中序遍历得到的值序列应该是严格递增的。如果其中有两个节点的值被交换了,那么中序遍历的序列中就会出现逆序对(即前一个节点的值大于后一个节点的值)。

设中序遍历序列为 A。

  1. 如果交换的两个节点不相邻,序列中会出现两处逆序对。
    • 例如:正常序列为 [1, 2, 3, 4, 5, 6],交换 2 和 5 后变为 [1, 5, 3, 4, 2, 6]。
    • 逆序对为:5 > 3 和 4 > 2。
    • 第一个错误节点是第一处逆序对中较大的那个值(5),第二个错误节点是第二处逆序对中较小的那个值(2)。
  2. 如果交换的两个节点相邻,序列中只会存在一处逆序对。
    • 例如:正常序列为 [1, 2, 3, 4],交换 2 和 3 后变为 [1, 3, 2, 4]。
    • 逆序对为:3 > 2。
    • 第一个错误节点是这处逆序对中较大的值(3),第二个错误节点是较小的值(2)。

根据题目进阶要求,我们需要实现 O(1) 的额外空间复杂度。普通的递归或迭代中序遍历需要 O(n) 或 O(h) 的栈空间。为了达到 O(1) 空间复杂度,我们可以采用 Morris 中序遍历 算法。

Python 代码实现

python
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 
# @param root TreeNode类 the root
# @return int整型一维数组
#
class Solution:
    def findError(self , root: TreeNode) -> List[int]:
        # 用于记录两个错误的节点值
        first_err = None
        second_err = None
        # 记录中序遍历中的前一个节点值
        prev_val = float('-inf')
        
        curr = root
        # 使用 Morris 中序遍历
        while curr is not None:
            if curr.left is None:
                # 访问当前节点
                if prev_val > curr.val:
                    if first_err is None:
                        first_err = prev_val
                        second_err = curr.val
                    else:
                        second_err = curr.val
                prev_val = curr.val
                curr = curr.right
            else:
                # 寻找当前节点的前驱节点
                pred = curr.left
                while pred.right is not None and pred.right is not curr:
                    pred = pred.right
                
                if pred.right is None:
                    # 建立临时线索指针
                    pred.right = curr
                    curr = curr.left
                else:
                    # 恢复树的结构
                    pred.right = None
                    # 访问当前节点
                    if prev_val > curr.val:
                        if first_err is None:
                            first_err = prev_val
                            second_err = curr.val
                        else:
                            second_err = curr.val
                    prev_val = curr.val
                    curr = curr.right
        
        # 题目要求按升序输出这两个节点的值
        # first_err 记录的是较大的错误值,second_err 记录的是较小的错误值
        return [second_err, first_err]

复杂度分析

  • 时间复杂度:O(n)。在 Morris 遍历中,虽然存在内部循环寻找前驱节点,但整棵树的每条边最多被访问 3 次,因此总体时间复杂度为线性。
  • 空间复杂度:O(1)。我们只使用了几个辅助指针和变量,没有使用递归栈或额外的队列/栈结构,符合进阶的空间复杂度要求。