前言:算法题在测试面试中的价值
在软件测试面试中,算法题并非为了考察你能否写出一个完美的搜索引擎,而是为了评估你的逻辑思维、代码质量意识以及问题拆解能力。一个优秀的测试工程师,应该能写出健壮、可测试的代码,并能从测试角度思考边界条件。本文精选7道高频算法面试题,从测试视角进行拆解,每道题都包含问题分析、代码实现、测试用例设计三个核心部分。
问题:实现一个函数,将输入的字符串反转。例如输入"hello",返回"olleh"。
测试思维拆解:这道题看似简单,但测试人员需要思考的边界条件包括:空字符串、单字符、包含空格、包含特殊字符、Unicode字符等。
代码实现(Python):
def reverse_string(s: str) -> str:
if not isinstance(s, str):
raise TypeError("Input must be a string")
return s[::-1]
测试用例设计:
- ►正常输入:
assert reverse_string("hello") == "olleh" - ►空字符串:
assert reverse_string("") == "" - ►单字符:
assert reverse_string("a") == "a" - ►包含空格:
assert reverse_string("ab cd") == "dc ba" - ►包含数字:
assert reverse_string("123") == "321" - ►包含特殊字符:
assert reverse_string("!@#") == "#@!" - ►Unicode字符:
assert reverse_string("你好") == "好你" - ►异常输入:
try: reverse_string(123); except TypeError: pass
问题:给定一个整数数组和一个目标值,找出数组中和为目标值的两个数的下标。假设每种输入只会对应一个答案。
测试思维拆解:需要覆盖数组长度、重复元素、负数、目标值不存在等情况。同时要思考,如果存在多个解,函数应该如何处理?题目假设唯一解,但测试人员需要验证这个假设。
代码实现(Python):
def two_sum(nums: list, target: int) -> list:
if not isinstance(nums, list) or not isinstance(target, int):
raise TypeError("Invalid input types")
seen = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
测试用例设计:
- ►正常解:
assert two_sum([2, 7, 11, 15], 9) == [0, 1] - ►负数参与:
assert two_sum([-3, 4, 3, 90], 0) == [0, 2] - ►重复元素:
assert two_sum([3, 3], 6) == [0, 1] - ►无解:
assert two_sum([1, 2, 3], 10) == [] - ►空数组:
assert two_sum([], 5) == [] - ►单元素数组:
assert two_sum([1], 1) == [] - ►异常输入:
try: two_sum("abc", 5); except TypeError: pass
问题:判断一个整数是否是回文数。回文数是指正序和倒序读都是一样的整数。
测试思维拆解:面试官可能期望你写出不将整数转为字符串的解法。测试人员需要关注负数、0、以0结尾的数、大整数等边界。
代码实现(Python):
def is_palindrome(x: int) -> bool:
if not isinstance(x, int):
raise TypeError("Input must be an integer")
if x < 0 or (x % 10 == 0 and x != 0):
return False
reversed_half = 0
while x > reversed_half:
reversed_half = reversed_half * 10 + x % 10
x //= 10
return x == reversed_half or x == reversed_half // 10
测试用例设计:
- ►正回文:
assert is_palindrome(121) == True - ►非回文:
assert is_palindrome(123) == False - ►负数:
assert is_palindrome(-121) == False - ►以0结尾:
assert is_palindrome(10) == False - ►0本身:
assert is_palindrome(0) == True - ►一位数:
assert is_palindrome(7) == True - ►大整数:
assert is_palindrome(1234567890987654321) == True - ►异常输入:
try: is_palindrome(1.5); except TypeError: pass
问题:给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。有效条件:左括号必须用相同类型的右括号闭合,且顺序正确。
测试思维拆解:这道题考察栈数据结构的理解。测试人员需要覆盖嵌套、不匹配、顺序错误、空字符串等情况。
代码实现(Python):
def is_valid_brackets(s: str) -> bool:
if not isinstance(s, str):
raise TypeError("Input must be a string")
bracket_map = {')': '(', '}': '{', ']': '['}
stack = []
for char in s:
if char in bracket_map.values():
stack.append(char)
elif char in bracket_map:
if not stack or stack.pop() != bracket_map[char]:
return False
else:
return False # 非法字符
return not stack
测试用例设计:
- ►有效嵌套:
assert is_valid_brackets("({[]})") == True - ►无效顺序:
assert is_valid_brackets("([)]") == False - ►左括号多余:
assert is_valid_brackets("(()") == False - ►右括号多余:
assert is_valid_brackets("())") == False - ►空字符串:
assert is_valid_brackets("") == True - ►单字符:
assert is_valid_brackets("(") == False - ►包含非法字符:
assert is_valid_brackets("(a)") == False - ►长字符串:
assert is_valid_brackets("()" * 1000) == True
问题:给你两个按非递减顺序排列的整数数组 nums1 和 nums2,合并 nums2 到 nums1 中,使合并后的数组同样按非递减顺序排列。假设 nums1 有足够的空间。
测试思维拆解:面试中常见的是原地合并,从后往前遍历可以避免覆盖。测试人员需要关注空数组、全重复、一个数组远大于另一个等情况。
代码实现(Python):
如果 nums2 还有剩余
nums1[:p2 + 1] = nums2[:p2 + 1]
**测试用例设计**:正常情况
nums1 = [1, 2, 3, 0, 0, 0]
merge_sorted(nums1, 3, [2, 5, 6], 3)
assert nums1 == [1, 2, 2, 3, 5, 6]
nums2为空
nums1 = [1]
merge_sorted(nums1, 1, [], 0)
assert nums1 == [1]
nums1为空
nums1 = [0, 0, 0]
merge_sorted(nums1, 0, [1, 2, 3], 3)
assert nums1 == [1, 2, 3]
全重复
nums1 = [1, 1, 1, 0, 0, 0]
merge_sorted(nums1, 3, [1, 1, 1], 3)
assert nums1 == [1, 1, 1, 1, 1, 1]
一个元素
nums1 = [5, 0]
merge_sorted(nums1, 1, [3], 1)
assert nums1 == [3, 5]
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">6. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">最长公共前缀:字符串数组的共性</span></div><div style="padding:16px 20px;"><p><strong>问题</strong>:编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。</p>
<p><strong>测试思维拆解</strong>:这道题考察字符串处理。测试人员需要覆盖空数组、单字符串、无公共前缀、所有字符串相同、大小写敏感等情况。</p>
<p><strong>代码实现(Python)</strong>:</p>
<div style="margin:16px 0;border-radius:8px;overflow:hidden;border:1px solid #E0E0E0;"><div style="background:#263238;color:#B0BEC5;padding:6px 16px;font-size:12px;font-family:Consolas,monospace;"><span style="text-transform:uppercase;letter-spacing:1px;">python</span></div><pre style="background:#1E272E;color:#E0E0E0;padding:16px 20px;margin:0;overflow-x:auto;font-size:14px;line-height:1.7;font-family:Consolas,"Courier New",monospace;"><code>def longest_common_prefix(strs: list) -> str:
if not isinstance(strs, list):
raise TypeError("Input must be a list")
if not strs:
return ""
prefix = strs[0]
for s in strs[1:]:
while not s.startswith(prefix):
prefix = prefix[:-1]
if not prefix:
return ""
return prefix</code></pre></div>
<p><strong>测试用例设计</strong>:</p>
<ul style="list-style:none;padding-left:0;margin:12px 0;">
<li style="position:relative;padding:4px 0 4px 24px;line-height:1.8;"><span style="color:#2196F3;font-weight:bold;position:absolute;left:4px;">►</span>正常情况:<code style="background:#F3E5F5;color:#6A1B9A;padding:1px 6px;border-radius:3px;font-size:0.9em;font-family:Consolas,monospace;">assert longest_common_prefix(["flower", "flow", "flight"]) == "fl"</code></li>
<li style="position:relative;padding:4px 0 4px 24px;line-height:1.8;"><span style="color:#2196F3;font-weight:bold;position:absolute;left:4px;">►</span>无公共前缀:<code style="background:#F3E5F5;color:#6A1B9A;padding:1px 6px;border-radius:3px;font-size:0.9em;font-family:Consolas,monospace;">assert longest_common_prefix(["dog", "racecar", "car"]) == ""</code></li>
<li style="position:relative;padding:4px 0 4px 24px;line-height:1.8;"><span style="color:#2196F3;font-weight:bold;position:absolute;left:4px;">►</span>空数组:<code style="background:#F3E5F5;color:#6A1B9A;padding:1px 6px;border-radius:3px;font-size:0.9em;font-family:Consolas,monospace;">assert longest_common_prefix([]) == ""</code></li>
<li style="position:relative;padding:4px 0 4px 24px;line-height:1.8;"><span style="color:#2196F3;font-weight:bold;position:absolute;left:4px;">►</span>单字符串:<code style="background:#F3E5F5;color:#6A1B9A;padding:1px 6px;border-radius:3px;font-size:0.9em;font-family:Consolas,monospace;">assert longest_common_prefix(["hello"]) == "hello"</code></li>
<li style="position:relative;padding:4px 0 4px 24px;line-height:1.8;"><span style="color:#2196F3;font-weight:bold;position:absolute;left:4px;">►</span>所有相同:<code style="background:#F3E5F5;color:#6A1B9A;padding:1px 6px;border-radius:3px;font-size:0.9em;font-family:Consolas,monospace;">assert longest_common_prefix(["test", "test", "test"]) == "test"</code></li>
<li style="position:relative;padding:4px 0 4px 24px;line-height:1.8;"><span style="color:#2196F3;font-weight:bold;position:absolute;left:4px;">►</span>大小写敏感:<code style="background:#F3E5F5;color:#6A1B9A;padding:1px 6px;border-radius:3px;font-size:0.9em;font-family:Consolas,monospace;">assert longest_common_prefix(["abc", "ABC"]) == ""</code></li>
<li style="position:relative;padding:4px 0 4px 24px;line-height:1.8;"><span style="color:#2196F3;font-weight:bold;position:absolute;left:4px;">►</span>包含空字符串:<code style="background:#F3E5F5;color:#6A1B9A;padding:1px 6px;border-radius:3px;font-size:0.9em;font-family:Consolas,monospace;">assert longest_common_prefix(["", "b"]) == ""</code></li>
</ul></div></div>
<div style="margin:24px 0;border:1px solid #E3F2FD;border-radius:8px;background:#FAFAFA;overflow:hidden;"><div style="display:flex;align-items:center;gap:12px;padding:12px 20px;background:linear-gradient(135deg,#E3F2FD,#BBDEFB);"><span style="width:36px;height:36px;background:#2196F3;color:#fff;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-weight:bold;font-size:16px;flex-shrink:0;">7. </span><span style="font-size:17px;font-weight:700;color:#1565C0;">数组中的多数元素:摩尔投票算法</span></div><div style="padding:16px 20px;"><p><strong>问题</strong>:给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。假设数组非空,且多数元素总是存在。</p>
<p><strong>测试思维拆解</strong>:这道题可以用哈希表或排序解决,但最优解是摩尔投票算法(空间O(1))。测试人员需要覆盖数组长度为1、2、所有元素相同、不同元素数量相等(但题目保证多数元素存在)等情况。</p>
<p><strong>代码实现(Python)</strong>:</p></div></div>
# 验证阶段(虽然题目保证存在,但测试中应验证)
if nums.count(candidate) > len(nums) // 2:
return candidate
raise ValueError("No majority element found")测试用例设计:
# 正常情况
assert majority_element([3, 2, 3]) == 3
assert majority_element([2, 2, 1, 1, 1, 2, 2]) == 2
# 单元素
assert majority_element([1]) == 1
# 两元素相同
assert majority_element([5, 5]) == 5
# 所有相同
assert majority_element([7, 7, 7, 7]) == 7
# 大数组
nums = [1] * 1000 + [2] * 999
assert majority_element(nums) == 1总结:测试视角下的算法面试
这7道题覆盖了字符串、数组、栈、双指针、哈希表等核心数据结构与算法。在面试中,除了写出正确代码,测试人员应额外展示以下能力:
1. 边界思考:主动提出并测试空输入、单元素、越界等场景
2. 异常处理:对非法输入给出明确的错误处理
3. 代码可测性:函数设计应便于单元测试,避免全局状态
4. 性能意识:能分析时间/空间复杂度,并解释为什么选择某种解法
算法面试不是终点,而是展示你作为测试工程师逻辑严谨性的起点。掌握这些基础题,结合测试思维,你将在面试中脱颖而出。
京公网安备 11010802030320号