張曉雅 投稿自 凹非寺
量子位 | 公眾號 QbitAI
還在自己吭哧吭哧打算法平臺Leetcode的周賽?為什么不試試神奇的ChatGPT類AI呢!
用AI助手Claude參加第103場周賽,共四道題,均完成了AC,能達到參與者前10%的成績。
事情的起因是知乎上一位叫蕭雅的用戶嘗試使用AI進行編程,但在測試過程中,她發(fā)現(xiàn)直接給出題目讓AI進行編程并輸出結(jié)果的方法,效果并不理想。
于是一種大膽的想法誕生了:使用自然語言配合逐步的思路描述,通過與AI的交互來完成編程。
具體操作中,她選擇了Anthropic公司出品的ChatGPT最強競品Claude這個AI助手,并選取了Leetcode第103場雙周賽來完成實驗。
整體的解答過程遵循以下幾點要求:
全程使用自然語言進行描述,通過一次溝通完成輸出,因此下文所有發(fā)給Claude的指令均可以復(fù)現(xiàn)。
采用問題描述、解答思路、以及開頭書寫要求作為提問固定格式。
編程采用python代碼輸出。
最終測試結(jié)果是,可以認為通過自然語言+解答思路發(fā)送給AI進行編程是可行的。
以下是測試全過程:
用自然語言讓AI打leetcode周賽
Leetcode第103場周賽題目1如下,k個元素的最大和:
發(fā)給Claude指令如下:
問題描述如下:
'''
給你一個下標從 0 開始的整數(shù)數(shù)組 nums 和一個整數(shù) k 。你需要執(zhí)行以下操作 恰好 k 次,最大化你的得分:
1.從 nums 中選擇一個元素 m 。
2.將選中的元素 m 從數(shù)組中刪除。
3.將新元素 m + 1 添加到數(shù)組中。
4.你的得分增加 m 。
請你返回執(zhí)行以上操作恰好 k 次后的最大得分。
'''
解答思路如下:
'''
由于最大值加一后還是最大值,那么反復(fù)利用最大值即可。
設(shè)數(shù)組的最大值為m,答案就是m+(m+1)+(m+2)++(m+k1)= [(2m+k1)k]/2
'''
請用python3書寫,并以下面這行作為開頭。
class Solution:
def maximizeSum(self, nums: List[int], k: int) -> int:
Claude回復(fù)內(nèi)容如下:
這里是python代碼實現(xiàn):
python
class Solution:
def maximizeSum(self, nums: List[int], k: int) -> int:
m = max(nums)
return (2*m + k - 1) * k // 2
解答思路:
1. 首先找到數(shù)組中的最大值m。
2. 然后最大的得分公式為:m + (m+1) + (m+2) + ... + (m+k-1)
= (m + (m+k-1)) * k / 2
= (2*m + k - 1) * k / 2
3. 直接返回計算結(jié)果。
時間復(fù)雜度:O(n)。需要遍歷數(shù)組一次得到最大值。
空間復(fù)雜度:O(1)。
這個解法直接利用了最大值與k的關(guān)系來得到最終結(jié)果,沒有實際操作數(shù)組。所以時間和空間復(fù)雜度都比較低。
請 let me know 如果您有任何疑問或需要解釋的地方。我很樂意提供幫助。
代碼提交結(jié)果:
周賽題目2如下,找到兩個數(shù)組的前綴公共數(shù)組:
發(fā)給Claude指令如下:
問題描述如下:
'''
給你兩個下標從 0 開始長度為 n 的整數(shù)排列 A 和 B 。
A 和 B 的 前綴公共數(shù)組 定義為數(shù)組 C ,其中 C[i] 是數(shù)組 A 和 B 到下標為 i 之前公共元素的數(shù)目。
請你返回 A 和 B 的 前綴公共數(shù)組 。
如果一個長度為 n 的數(shù)組包含 1 到 n 的元素恰好一次,我們稱這個數(shù)組是一個長度為 n 的 排列 。
'''
解答思路如下:
'''
1.存儲公共前綴的大小
2.存儲 a 和 b 中已經(jīng)匹配過的元素
3.同時遍歷 a 和 b 中的元素
4.將 a 中的當前元素 x 對應(yīng)的二進制位置為 1,標記為已匹配
5.將 b 中的當前元素 y 對應(yīng)的二進制位置為 1,標記為已匹配
6.計算當前公共前綴的大小,即 p 和 q 的二進制數(shù)中 1 的個數(shù)
7.返回公共前綴大小的列表
'''
請用python3書寫,并以下面這行作為開頭。
class Solution:
def findThePrefixCommonArray(self, A: List[int], B: List[int]) -> List[int]:
Claude回復(fù)內(nèi)容如下:
這里是優(yōu)化后的python代碼實現(xiàn):
python
class Solution:
def findThePrefixCommonArray(self, A: List[int], B: List[int]) -> List[int]:
p, q = 0, 0
res = []
for i in range(min(len(A), len(B))):
p |= 1
q |= 1
res.append(bin(p&q).count('1'))
return res
解答思路:
1. p和q初始化為0,用來記錄A和B數(shù)組中出現(xiàn)過的數(shù)字。
2. 同時遍歷A和B數(shù)組的元素。
3. 通過p |= 1
4. 通過q |= 1
5. p & q得到兩個數(shù)組公共元素,然后bin(p & q).count('1')計算1的個數(shù),得到公共元素個數(shù)c。
6. 添加c到res。
7. 重復(fù)3-6直到A或B數(shù)組遍歷結(jié)束。
8. 返回res。
時間復(fù)雜度:O(m + n)。需要遍歷兩個數(shù)組。
空間復(fù)雜度:O(m + n)。需要使用res存儲結(jié)果。
這種方法無需提前記錄每個數(shù)字在A和B數(shù)組中出現(xiàn)的次數(shù),而是在遍歷的同時更新p和q,簡潔高效。
請 let me know 如果您有任何疑問或需要解釋的地方。我很樂意提供幫助。
代碼提交結(jié)果:
周賽題目3如下,網(wǎng)格中魚的最大數(shù)目:
發(fā)給Claude指令如下:
問題描述如下:
'''
給你一個下標從 0 開始大小為 m x n 的二維整數(shù)數(shù)組 grid ,其中下標在 (r, c) 處的整數(shù)表示:
如果 grid[r][c] = 0 ,那么它是一塊 陸地 。
如果 grid[r][c] > 0 ,那么它是一塊 水域 ,且包含 grid[r][c] 條魚。
一位漁夫可以從任意 水域 格子 (r, c) 出發(fā),然后執(zhí)行以下操作任意次:
捕撈格子 (r, c) 處所有的魚,或者
移動到相鄰的 水域 格子。
請你返回漁夫最優(yōu)策略下, 最多 可以捕撈多少條魚。如果沒有水域格子,請你返回 0 。
格子 (r, c) 相鄰 的格子為 (r, c + 1) ,(r, c - 1) ,(r + 1, c) 和 (r - 1, c) ,前提是相鄰格子在網(wǎng)格圖內(nèi)。
'''
解答思路如下:
'''
1.獲取網(wǎng)格的行數(shù)和列數(shù)
2.定義深度優(yōu)先搜索函數(shù)
3. 如果下標越界或網(wǎng)格中沒有魚,則返回 0
4.統(tǒng)計當前點的價值為s
5.將當前點標記成訪問過,不需要重置其值
6. 四個方向進行深度優(yōu)先搜索
7.將搜索到的魚的價值累加到 s 中
8.返回能夠獲得的最大價值s
9.遍歷所有的格子,計算其能夠獲得的最大價值并返回
'''
請用python3書寫,并以下面這行作為開頭,按照思路不要添加任何額外細節(jié)。
class Solution:
def findMaxFish(self, grid: List[List[int]]) -> int:
Claude回復(fù)內(nèi)容如下:
這里是精簡的python代碼實現(xiàn):
python
class Solution:
def findMaxFish(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
def dfs(i, j):
if not (0 0): return 0
s = grid[i][j]
grid[i][j] = 0
s += dfs(i+1, j) + dfs(i-1, j) + dfs(i, j+1) + dfs(i, j-1)
return s
res = 0
for i in range(m):
for j in range(n):
res = max(res, dfs(i, j))
return res
解答思路:
1. 獲取網(wǎng)格的行數(shù)m和列數(shù)n。
2. 定義dfs函數(shù)進行深度優(yōu)先搜索。
3. 如果下標越界或網(wǎng)格中沒有魚,則返回 0。
4. 統(tǒng)計當前點grid[i][j]的魚的數(shù)量s。
5. 將grid[i][j]標記為已訪問,不需要重置其值 。
6. dfs搜索四個方向,并將搜索到的魚的數(shù)量累加到s。
7. 返回s。
8. 遍歷所有的格子,通過dfs計算其能夠獲得的最大價值,并更新res。
9. 返回res。
時間復(fù)雜度:O(m*n)。需要遍歷整個網(wǎng)格。
空間復(fù)雜度:O(m*n)。遞歸深度可能達到m*n。
這是一道典型的深度優(yōu)先搜索問題,需要注意狀態(tài)重置。
通過不重置grid[i][j]的值,避免了狀態(tài)重置,簡化了代碼邏輯。
請 let me know 如果您有任何疑問或需要解釋的地方。我很樂意提供幫助。
代碼提交結(jié)果:
周賽題目4如下,將數(shù)組清空:
發(fā)給Claude指令如下:
問題描述如下:
'''
給你一個包含若干 互不相同 整數(shù)的數(shù)組 nums ,你需要執(zhí)行以下操作 直到數(shù)組為空 :
如果數(shù)組中第一個元素是當前數(shù)組中的 最小值 ,則刪除它。
否則,將第一個元素移動到數(shù)組的 末尾 。
請你返回需要多少個操作使 nums 為空。
'''
解答思路如下:
'''
數(shù)組 nums 的長度為n
創(chuàng)建一個空的堆列表heap
對nums進行i,nums這個格式的遍歷,再將當前元素值和對應(yīng)索引組成后面格式的元組 (num, i) 加入到堆列表 heap 中,不要對這兩個格式順序做修改。
將 nums 中的所有元素及其下標加入最小堆 heap 中
用sortedcontainers里面的SortedList創(chuàng)建空的有序集合pos,用于存儲已經(jīng)刪除的元素的下標
res用于存儲清空數(shù)組所需的最少操作數(shù),初始化為 0
哨兵li,上一個彈出的數(shù)的位置,初始化為-1
獲取堆中的最小值,并計算清除該元素所需的步數(shù),重復(fù)直到堆為空
如果 i 在上一個彈出元素li的后面,對于每個被彈出的元素,計算從i到li在pos中的有效元素數(shù)量d,即計算值在 li的右邊且值小于 i 的元素數(shù)量。再將從當前位置到i之間的長度減去d,加入總步數(shù) res 中
如果當前元素的位置在上一個被彈出元素 li 的前面,則計算從 li 到數(shù)組結(jié)尾的有效元素數(shù)量d,即集合中小于等于i的元素數(shù)量與集合中小于n的元素數(shù)量相加,再減去集合中小于li的元素數(shù)量為有效元素數(shù)量d。再將從當前位置 li 到數(shù)組末尾的元素數(shù)量加上i,再減去d,加入總步數(shù)res中
將元素 i 的下標加入已刪除元素的有序集合pos中
更新上一個彈出的數(shù)位置
返回清空數(shù)組所需的最少操作數(shù)res
'''
請用python3書寫,并以下面這行作為開頭。
class Solution:
def countOperationsToEmptyArray(self, nums: List[int]) -> int:
Claude回復(fù)內(nèi)容如下:
這里是python代碼實現(xiàn):
python
from sortedcontainers import SortedList
class Solution:
def countOperationsToEmptyArray(self, nums: List[int]) -> int:
n = len(nums)
heap = []
for i, num in enumerate(nums):
heappush(heap, (num, i))
pos = SortedList()
res = 0
li = -1
while heap:
num, i = heappop(heap)
if i > li:
d = pos.bisect_left(i) - pos.bisect_right(li)
res += i - li - d
else:
d = pos.bisect_right(n) - pos.bisect_right(li) + pos.bisect_left(i)
res += n - li + i - d
pos.add(i)
li = i
return res
解答思路:
1. 數(shù)組 nums 的長度為n。
2. 創(chuàng)建一個空的堆列表heap。
3. 對nums進行i,nums這個格式的遍歷,再將當前元素值和對應(yīng)索引組成后面格式的元組 (num, i) 加入到堆列表 heap 中,不要對這兩個格式順序做修改。
4. 用sortedcontainers里面的SortedList創(chuàng)建空的有序集合pos,用于存儲已經(jīng)刪除的元素的下標。
5. res用于存儲清空數(shù)組所需的最少操作數(shù),初始化為0。
6. 哨兵li,上一個彈出的數(shù)的位置,初始化為-1。
7. 獲取堆中的最小值,并計算清除該元素所需的步數(shù),重復(fù)直到堆為空。
8. 如果i在上一個彈出元素li的后面,對于每個被彈出的元素,計算從i到li在pos中的有效元素數(shù)量d,即計算值在 li的右邊且值小于 i 的元素數(shù)量。再將從當前位置到i之間的長度減去d,加入總步數(shù)res中。
9. 如果當前元素的位置在上一個被彈出元素li的前面,則計算從li到數(shù)組結(jié)尾的有效元素數(shù)量d,即集合中小于等于i的元素數(shù)量與集合中小于n的元素數(shù)量相加,再減去集合中小于li的元素數(shù)量為有效元素數(shù)量d。再將從當前位置li到數(shù)組末尾的元素數(shù)量加上i,再減去d,加入總步數(shù)res中。
10. 將元素i的下標加入已刪除元素的有序集合pos中。
11. 更新上一個彈出的數(shù)位置li = i。
12. 返回清空數(shù)組所需的最少操作數(shù)res。
時間復(fù)雜度:O(nlogn)。需要排序。
空間復(fù)雜度:O(n)。需要堆和集合。
這是一道運用堆與有序集合的題目。通過維護哨兵位置和已刪除元素,得到最優(yōu)解。
請 let me know 如果您有任何疑問或需要解釋的地方。我很樂意提供幫助。
代碼提交結(jié)果:
實踐后的思考
對于這個問題最初的想法就來自于在AI助手有了突破之后,嘗試能否通過自然語言編程來代替程序語言編程。
自然語言編程指的是使用我們?nèi)粘UZ言來表達計算機程序的概念和算法,而非傳統(tǒng)的程序語言如C++和Python等。
自然語言具有良好的表達形式,更易于理解和表述,更加親和易懂,人們可以用最自然的語言來表達自己的思想和創(chuàng)意,而無需陷入復(fù)雜的程序語言語法。
自然語言編程可以讓人們更為容易得表達自己的需求,并快速實現(xiàn)理想中的功能。
然而,自然語言編程也存在明顯劣勢。
首先,自然語言表達是模糊的和不精確的。我們所講的語言中,存在很多概念和意思是相對的和依賴語境的,這些難以在計算機程序中得到明確和一致的表達,這都給程序的理解使用和調(diào)試帶來了很大困難。
其次,自然語言難以表達計算機程序需要的精確和嚴謹?shù)母拍睿陨淼膰乐斝圆粔颉?/p>
程序語言提供了形式化的語法和豐富的數(shù)據(jù)類型來定義程序邏輯,給出程序的統(tǒng)一性標準,有著共同的語言約定和對應(yīng)規(guī)則,這都是自然語言很難達到的。
最后,要實現(xiàn)人與機器之間完全自然的語言交互和理解,這需要機器具備廣泛而深入的知識水平和推理能力,而人工智能能否準確理解這些,需要標準來進行評估。目前的自然語言處理技術(shù)能否完全理解人類語言的深層次含義,實現(xiàn)人與機器完全自然的語言交互仍然需要商榷。
通過使用Claude打leetcode周賽,對于如何使用自然語言描述程序多了一點自己的認識。
自然語言可以表達所有的程序語言,具有更寬泛的表達能力。事實上,在學(xué)習(xí)和解釋任何一門程序語言時,所依賴的都是自然語言。
程序語言本質(zhì)上只是人與機器間的一種中間溝通工具,而其實這些年它的發(fā)展也在不斷地從機器可解析的形式向更加貼近人類理解的自然語言靠攏,更偏人類正常溝通的方向靠近。
所以,程序語言并不比自然語言本身擁有更強的表達能力。相反,自然語言可以承載更豐富、更廣泛的概念,實現(xiàn)更加抽象和深層次的思維傳達。
程序語言之所以存在,其目的就在于讓機器可以理解人類用自然語言表達的思想和邏輯。因此,程序語言并不是一個更優(yōu)于自然語言的表現(xiàn)形式。
實踐發(fā)現(xiàn),在書寫自然語言編程描述時,需要著重考慮到以下幾個參考點:
語言表述:用盡可能精準自然的語言來進行溝通回復(fù),避免重復(fù)性詞語,盡可能不使用可能/或許這種不確定性的描述。
語義理解:過于復(fù)雜或者專業(yè)的表達會增加理解難度,需要補充更為清晰易懂的技術(shù)背景,會得到更好的輸出。
交互模式:用更為直接明了的命令形式進行表達,雖然不及聊天形式自然,但更為清晰簡明。
規(guī)范結(jié)構(gòu):需要在表達方式和交互規(guī)則上增加一定框架,對輸入輸出都會有更為直觀的展示。
以上這些只是一些想法,自然語言編程背后涉及的知識和技術(shù)遠不止此。
能超過50%的周賽參與者
用AI編寫程序,為什么不選擇熱度最高的ChatGPT,或其他生成代碼專用AI?就這個問題背后的原因,量子位詢問了蕭雅本人。
她表示,沒有選擇生成代碼專用AI是因為想用最根本的模型進行解答,再考慮到所有輸入是中文+代碼,有10萬token的Claude對中文的理解和處理優(yōu)于ChatGPT等產(chǎn)品。
實際體驗后她得出結(jié)論,通過與AI的交互來完成編程這種方法在某些步驟和環(huán)節(jié)比手寫代碼節(jié)省時間,尤其是改bug和學(xué)習(xí)新語言的時間。
“比如說本身對c++非常熟悉,而可能這個代碼需要python書寫才能更匹配,哪怕對語言不是很了解,但是整體的思路和想法有很大共通的。”她補充解釋道,“這個語言做的好的地方,相對的那個語言應(yīng)該也會有對應(yīng)的庫,但是可以在不那么精通另一個語言的情況下依然很好的書寫代碼。”
雖然能加快變成速度,但新方法還是沒有辦法超過90%-100%的作答者,不過至少能超過50%的周賽參與者。
理由是這種自然語言編程的方法和近些年的編程語言發(fā)展模式很接近,讓語言設(shè)計都更偏向于人類,而遠離計算機硬件。
此外,這個方法和人的學(xué)習(xí)過程是一致的,人在學(xué)習(xí)編程語言的時候都是用自然語言進行解釋的,這是最好的描述工具。
她舉例說,團隊合作書寫代碼的時候,所有代碼的描述也是通過自然語言說清楚再進行思維碰撞,這種方法可以很大程度提高工作效率。
最終目標肯定是直接給出題目就可以讓AI進行解答,現(xiàn)在是做不到這點的,這種方法只是AI書寫代碼的其中一步。