【青训营笔记1】AI练中学的3道题和AI思路提示功能的笔记

121 阅读6分钟
  1. 套路题:异或找独特数字
  2. 一维dp:找出最长的神奇数列
  3. 贪心:字典序最小的01字符串

1. 套路题:异或找独特数字

问题描述

在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小C快速找到那个拿了独特数字卡片的同学手上的数字是什么。

要求:

  1. 设计一个算法,使其时间复杂度为 O(n),其中 n 是班级的人数。
  2. 尽量减少额外空间的使用,以体现你的算法优化能力。

测试样例

样例1:

输入:cards = [1, 1, 2, 2, 3, 3, 4, 5, 5]
输出:4

套路

aXORa=0a \quad XOR\quad a=0

遍历一遍所有数,用一个变量记录所有值的异或结果即可。

def solution(inp)-> int :
    res=0
    for i in inp: res=res^i
    return res

AI练中学评价

测试Prompt:默认的思路提示

部分结果:

解题思路

  1. 异或运算的特性

  • 异或运算有一个非常有用的特性:a ^ a = 0 和 a ^ 0 = a。这意味着,如果我们将数组中所有数字进行异或运算,出现两次的数字会相互抵消,最终剩下的就是唯一出现一次的数字。

个人评价:效果很好,答出异或就行。

2. 一维dp:找出最长的神奇数列

问题描述

小F是一个好学的中学生,今天他学习了数列的概念。他在纸上写下了一个由 0 和 1 组成的正整数序列,长度为 n。这个序列中的 1 和 0 交替出现,且至少由 3 个连续的 0 和 1 组成的部分数列称为「神奇数列」。例如,10101 是一个神奇数列,而 1011 不是。现在,小F想知道在这个序列中,最长的「神奇数列」是哪一个。你能帮他找到吗?

如果有多个神奇数列,那么输出最先出现的一个。


测试样例

样例1:

输入:inp = "0101011101"
输出:'010101'

思路

状态转移方程: f(seq)=max(f(seq[1:]),seq[0]+f(seq[1:]))f(seq)=max(f(seq[1:]), seq[0]+f(seq[1:]))

对于任意一位数字(索引i),我们可以从这个角度考虑:神奇数列是不是从i这里开始(是不是神奇数列的首元素),只有2种值:是或不是(接着前面的数列)。基于此,我们可以拆解出sequence的每一位元素与剩余sequence的关系,列出上面的状态转移方程,从而解决问题。

实现上,我们遍历一次输入的字符串的每一位数,假定在循环中,每个epoch的索引对应数都是会被选中的,那么要么它是神奇数列的首元素,要么它接着前面的元素。

这种一维的动态规划问题是比较基础的dp,在遍历过程中用一个变量记录需要的最大值即可。

def solution(inp:str)->str:
    if not inp or len(inp)<3: return ""
    seq=inp[0]
    maxs=seq
    for i in range(1,len(inp)):
        # 01元素交替出现,那么该元素i接在前面
        if inp[i] != inp[i-1]:
            seq+=inp[i]
            if len(seq)>len(maxs):
                maxs=seq
        # 否则,神奇数列从i 这里重新开始
        else:
            seq=inp[i]

    return maxs if len(maxs)>=3 else ""

AI练中学评价

  • 测试Prompt:
请你从动态规划的角度,考虑给出思路提示
  • 部分结果:

好的,让我们从动态规划的角度来考虑这个问题。

问题理解 我们需要在一个由 0 和 1 组成的字符串中找到最长的「神奇数列」。神奇数列的定义是:

由 0 和 1 交替出现。 长度至少为 3。 动态规划思路 动态规划的核心思想是将问题分解为子问题,并通过存储子问题的解来避免重复计算。

状态定义 我们可以定义一个二维数组 dp,其中 dp[i][0] 表示以第 i 个字符结尾的最长神奇数列的长度,且该数列以 0 结尾;dp[i][1] 表示以第 i 个字符结尾的最长神奇数列的长度,且该数列以 1 结尾。

状态转移方程 如果 inp[i] == '0': dp[i][0] = dp[i-1][1] + 1,因为当前字符是 0,所以它只能接在以 1 结尾的数列后面。 dp[i][1] = 0,因为当前字符是 0,不能接在以 1 结尾的数列后面。 如果 inp[i] == '1': dp[i][1] = dp[i-1][0] + 1,因为当前字符是 1,所以它只能接在以 0 结尾的数列后面。 dp[i][0] = 0,因为当前字符是 1,不能接在以 0 结尾的数列后面。 初始条件 dp[0][0] = 1 如果 inp[0] == '0',否则 dp[0][0] = 0。 dp[0][1] = 1 如果 inp[0] == '1',否则 dp[0][1] = 0。

  • 个人评价:效果不太行。似乎它会把dp默认为需要用二维数组的形式,难道是训练语料里背包问题之类的太多了?

3. 贪心:字典序最小的01字符串

问题描述

小U拥有一个由0和1组成的字符串,她可以进行最多k次操作,每次操作可以交换相邻的两个字符。目标是通过这些操作,使得最终得到的字符串字典序最小。

例如,小U当前有一个字符串 01010,她最多可以进行 2 次相邻字符交换操作。通过这些操作,她可以将字符串调整为 00101,这是可以通过不超过2次操作得到的字典序最小的字符串。

现在,小U想知道,经过最多k次操作后,能够得到的字典序最小的字符串是什么。


测试样例

样例1:

输入:n = 5, k = 2, s = "01010"
输出:'00101'

思路

因为没有时间限制,所以我感觉暴力做就行。 k次操作就是一个k轮的for 循环,每轮的操作就是先定位,然后再交换。 定位:找到字符串最左边的、右边有0的那个1 交换:将这个1和右边的0交换

def locate(s:str) -> int:
    for i in range(len(s)):
        if s[i]=='1' and i+1<len(s) and s[i+1]=='0':
            break
    return i

def solution(n: int, k: int, s: str) -> str:
    res=s
    for epoch in range(k):
        i= locate(res)
        if i<len(s)-1:
            res=res[:i]+"01"+(res[i+2:] if i+2 <len(res) else "")
        else: break
    return res

AI练中学

  • 测试Prompt: 默认的选项,给出思路提示

  • 部分结果:

要解决这个问题,我们需要通过最多 k 次相邻字符交换操作,使得字符串的字典序最小。以下是解题思路:

解题思路

  1. 理解字典序最小

    • 字典序最小意味着我们需要尽可能地将 0 放在前面,1 放在后面。
  2. 贪心策略

    • 我们可以从左到右遍历字符串,尝试将当前位置的 0 尽可能地向左移动。
    • 每次移动一个 0 到当前位置,需要计算移动的步数,并确保不超过 k 次操作。
  3. 具体步骤

    • 初始化一个空的结果字符串。

    • 遍历原始字符串,对于每个字符:

      • 如果当前字符是 0,尝试将其移动到结果字符串的当前位置。
      • 计算移动所需的步数,并更新剩余的操作次数 k
    • 如果当前字符是 1,直接将其添加到结果字符串中。

  4. 边界情况

    • 如果 k 为 0,则不能再进行任何操作,直接返回当前结果字符串。
    • 如果字符串中没有 0,则直接返回原始字符串。
  • 个人评价:不错,我觉得能答出贪心和边界情况还可以了