前言
本次测试按照力扣给出的初级算法面试清单来逐步进行题目测试(https://leetcode.cn/leetbook/detail/top-interview-questions-easy/)。
按照力扣的划分,初级算法面试清单中包含以下几类:
为确保一致性,本文所有测试算法均采用python来编写。
数组篇
数组是在程序设计中,把具有相同类型的若干元素按有序的形式组织起来的一种形式。
作为线性表的实现方式之一,数组中的元素在内存中是 连续 存储的,且每个元素占相同大小的内存。
对于一个数组 ['oranges', 'apples', 'bananas', 'pears', 'tomatoes'],通过索引和数组第 1 个元素的内存地址,可以计算出其它元素的内存地址,进而访问内存地址里存储的内容。索引与内存地址的关系如下图所示。
数组通过 索引 快速访问每个元素的值。在大多数编程语言中,索引从0算起。
在不同的编程语言中,数组的实现方式具有一定差别。比如 C++ 和 Java 中,数组中的元素类型必须保持一致,而 Python 中则可以不同。相比之下,Python 中的数组(称为 list)具有更多的高级功能。
删除排序数组中的重复项
提问输入如下:
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。
判题标准:
系统会用下面的代码来测试你的题解:
int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案
int k = removeDuplicates(nums); // 调用
assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}
如果所有断言都通过,那么您的题解将被 通过。
请根据以下代码完成:class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
给出代码及对照结果, 正确 !
买卖股票的最佳时机 II
提问输入如下:
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在同一天出售。
返回 你能获得的最大利润 。当输入:prices = [7,1,5,3,6,4]
结果输出:7
这是因为在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
所以总利润为 4 + 3 = 7 。同理,当我们输入:prices = [1,2,3,4,5]
结果输出:4
这是因为在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
总利润为 4 。提示:1 <= prices.length <= 3 * 104
0 <= prices[i] <= 104请你结合下述代码优化完善并能满足上述的输入输出案例:class Solution:
def maxProfit(self, prices: List[int]) -> int:
给出代码及对照结果, 很遗憾,并未通过全部测试用例!
经过排查,我在提问时举出的解释示例(在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。)被判定的权重较大,故代码生成时以满足该输入输出为优先选择。但是即便如此,通义灵码在分析该输入输出时仍然出现了明显问题,根据通义灵码生成的代码逻辑如下:
检查价格列表的长度是否小于2。如果是这样,表示没有交易的天数,最大利润为0。
我们将最小价格初始化为列表中的第一个价格。
接下来,我们从第二个价格开始遍历价格列表。
如果当前价格小于最小价格,我们更新最小价格。
当我修改提问方式先问主体问题,再单个将示例喂给他让他自己不断优化时:
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。1 <= prices.length <= 3 * 104
0 <= prices[i] <= 104
请优化你的代码以实现下列输入输出对应结果及逻辑。当输入:prices = [7,1,5,3,6,4]
结果输出:7
这是因为在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
所以总利润为 4 + 3 = 7 。
同理,当我们输入:prices = [1,2,3,4,5]
结果输出:4
这是因为在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
总利润为 4 。当输入:prices = [7,6,4,3,1]
结果输出:0
这是因为在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
他给出的优化后答案虽较之前有进步,但仍然无法完美通过实例测试:
为此,我将正确答案复制过去询问通义灵码,看它是如何分析力扣上的正确答案的。
紧接着,我让它分析自己代码和正确答案的区别。
从这里可以看出,它的回答局限于示例 1 细致的描述之中,自从我提问涉及到示例给出的逻辑后它就基本原封不动的按照其细化后的逻辑来编写代码(然而对于示例中给出的逻辑理解也是有误的!)
此处表明其对于复杂问题的理解能力还是有所欠缺。
旋转数组
提问输入如下:
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。请根据以下代码优化完善:class Solution:
def rotate(self, nums: List[int], k: int)
假如我输入: nums = [1,2,3,4,5,6,7], k = 3
此时输出结果应该为 [5,6,7,1,2,3,4]
这是因为k为3,此时需要向右轮转 1 步: 得到[7,1,2,3,4,5,6],得到向右轮转 2 步: [6,7,1,2,3,4,5]
,得到向右轮转 3 步: [5,6,7,1,2,3,4]。同理,假如我输入:nums = [-1,-100,3,99], k = 2时,此时应该输出为[3,99,-1,-100]
很遗憾,给出的代码并未通过全部测试用例!
观察输出结果可以看到,它只是实现了数字的轮转,但是仍保留了轮转前的字符顺序。
意外的是,我在通义灵码中按照输入输出询问它时,它竟给出了一个惊人的答复:
它坚定的认为在自己的代码逻辑下生成的答案就是正确的。
为了打醒它,我又开始了比较大法。
比较代码1和代码2的区别。
代码1:class Solution: //标准答案
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
tmp = [0] * n
for i in range(n):
tmp[(i+k)%n] = nums[i]
nums[:] = tmp[:]。代码2:class Solution: //通义灵码给出
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k = k % n
nums[:k] = nums[n-k:n] + nums[:k]
此时,它也是给出了相关分析,分析结论是正确的。
但是当我再次询问,它仍然给出的是一个错误的答案:
此时,明显为逻辑幻觉错误 ,于是我试着去告知它:
此时他也做出了相应的改变,但是其逻辑仍然存在问题。
除此之外,我认为此处也仍表明其面对复杂问题的理解能力不足。
存在重复元素
提问输入如下:
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。请根据以下代码完成:class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
给出代码及对照结果, 正确 !
只出现一次的数字
提问输入如下:
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。请根据以下代码完成:class Solution:
def singleNumber(self, nums: List[int]) -> int:
给出代码及对照结果, 正确 !
两个数组的交集 II
提问输入如下:
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。请根据以下代码完成:class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
给出代码及对照结果, 正确 !
加一
提问输入如下:
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
请根据以下代码完成:class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
给出的代码并未通过全部测试用例!
老办法,给出数值让它自己校验。
此处同样出现了代码逻辑错误,即使我将它的代码复制后告知它此时输出为【1,1,2,4】,在经过它的修改后代码仍是保持之前的错误逻辑。
将标准答案和通义灵码生成的答案进行比较,它给出了如下解释:
但是实际上,输入【1,2,3】时输出【1,2,4】并未进位,通义灵码给出的代码却直接在数组最前面添加了 1,此处的考虑是相对周全的,增加了一个carry变量,其初始值为1,当某个元素加一后还是9时,carry应该进位,但是却没有对carry进行正确的处理,所以导致了错误的输出。
这时,采用问题描述结合疑问解答的方式来对通义灵码进行发问:
但是当我输入[1,2,3]时并没有存在进位,为什么会输出成[1,1,2,4]呢?
此时,它似乎才意识到自己的问题所在,经过此次修改后,它给出的代码也是成功的通过了验证。
移动零
提问输入如下:
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请根据以下代码完成:class Solution:
def moveZeroes(self, nums: List[int]) -> None:
给出代码及对照结果, 正确 !
两数之和
提问输入如下:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。请根据以下代码完成:class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
给出代码及对照结果, 正确 !
有效的数独
提问输入如下:
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
请根据以下代码完成:class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
果不其然,对于这种问题比较晦涩难懂的,其实靠人脑都一时无法理解,对于发展中的AI大模型来言同样也是如此。
此时,结合示例去通义灵码中测试,此时能明确知道输出的值就是错误的。
故此时不是代码逻辑问题,而是复杂语义理解问题。
所以,此时我给出明确的指示:
不对,此时应该输出为true,可能是你给出的代码存在问题,请你修改完善
最终也是成功通过了, 正确 !
旋转图像
提问输入如下:
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。
请根据以下代码完成:class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
上述代码需要满足以下通用示例:如输入matrix = [[1,2,3],[4,5,6],[7,8,9]],正确输出为[[7,4,1],[8,5,2],[9,6,3]];如输入matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]],正确输出为[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]。请根据这两个示例完善代码
果不其然,还是错误的。
经过验证此时代码也出现了逻辑幻觉错误。
于是乎果断开始拿事实说话,直接告诉它答案有误。
幸运的是这小子这次还好承认了,然后我接着说:
最终也是成功通过了, 正确 !
结论
总体来看,通义灵码在实现数组算法生成方面的正确率和效率还是非常可观的,但是同时也是会面临两种困境:
代码逻辑幻觉:根据给定表述,它所生成的代码的逻辑存在幻觉,即给定输入时会输出错误的结果,但是它始终认为自身的输出是给定出的正确结果。(比如上述题目:加一,按照通义灵码生成的代码逻辑输出结果应该为【1,1,2,4】,但是它始终认为自己输出的是【1,2,4】(即正确结果))。
复杂语言理解问题:对于题设过于抽象时其实通义灵码很难能直观的理解,不过好在可以采用引导式的方式来逐步调优,这也侧面反映出其对于复杂语义的理解能力还可以进一步优化。
题目 | 最初答案 | 最终答案 | 执行用时 | 内存消耗 |
---|---|---|---|---|
删除排序数组中的重复项 | √ | √ | 超过48.70% | 超过94.03% |
买卖股票的最佳时机 II | × | × | 通过测试用例93/200 | 通过测试用例93/200 |
旋转数组 | × | × | 通过测试用例9/38 | 通过测试用例9/38 |
存在重复元素 | √ | √ | 超过87.37% | 超过72.72% |
只出现一次的数字 | √ | √ | 超过74.85% | 超过77.07% |
两个数组的交集 II | √ | √ | 超过54.37% | 超过83.94% |
加一 | × | √ | 超过57.66% | 超过92.76% |
移动零 | √ | √ | 超过81.00% | 超过98.20% |
两数之和 | √ | √ | 超过58.17% | 超过43.17% |
有效的数独 | × | √ | 超过7.17% | 超过36.15% |
旋转图像 | × | √ | 超过92.57% | 超过68.93% |
【注意】最初答案指第一次生成的答案是否正确;最终答案指多次调教之后生成的答案是否正确;执行用时越短,超过的比例就越多;内存消耗越少,超过的比例就越多;测试用例是经过多次调教后仍未完全通过的。