算法

算法

目录


华为OD常考算法题

1. 字符串处理

1.1 统计相同字符串个数

原题:给定一个字符串和一个子串,统计子串在字符串中出现的次数(不区分大小写)。

示例

1
2
3
4
输入:
Hello World Hello
hello
输出:2

解题思路:将原字符串和子串都转为小写,通过替换子串为空,计算长度差来得到出现次数。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Scanner;

public class StrNumbers {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String str = scanner.nextLine();
String target = scanner.nextLine();
System.out.println(countOccurrences(str, target));
}

static int countOccurrences(String str, String target) {
String s = str.toLowerCase();
String t = target.toLowerCase();
if (t.isEmpty()) return 0;
return (s.length() - s.replace(t, "").length()) / t.length();
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
def count_occurrences(s: str, target: str) -> int:
s_lower = s.lower()
t_lower = target.lower()
if not t_lower:
return 0
return s_lower.count(t_lower)

if __name__ == "__main__":
s = input()
target = input()
print(count_occurrences(s, target))

1.2 字符串反转

原题:输入一个字符串,反转输出该字符串。

示例

1
2
输入:hello world
输出:dlrow olleh

解题思路:双指针法,左右交换字符。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Scanner;

public class StringReverse {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(reverse(s));
}

static String reverse(String s) {
char[] chars = s.toCharArray();
int left = 0, right = chars.length - 1;
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
return new String(chars);
}
}

Python 源码

1
2
3
4
5
6
def reverse_string(s: str) -> str:
return s[::-1]

if __name__ == "__main__":
s = input()
print(reverse_string(s))

2. 数组与链表

2.1 明明的随机数

原题:明明生成了N个1到500之间的随机整数,请你删去其中重复的数字,然后从小到大排序输出。

示例

1
2
3
4
5
6
7
8
9
10
11
12
输入:
5
3
2
2
1
4
输出:
1
2
3
4

解题思路:使用TreeSet(Java)或sorted(set)(Python)进行去重和排序。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Iterator;
import java.util.Scanner;
import java.util.TreeSet;

public class RandomNumbers {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
TreeSet<Integer> set = new TreeSet<>();
for (int i = 0; i < num; i++) {
set.add(sc.nextInt());
}
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

Python 源码

1
2
3
4
5
6
7
if __name__ == "__main__":
n = int(input())
nums = set()
for _ in range(n):
nums.add(int(input()))
for num in sorted(nums):
print(num)

2.2 两数之和

原题:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。

示例

1
2
3
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] = 2 + 7 = 9

解题思路:使用哈希表存储已遍历的数字及其索引,时间复杂度O(n)。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.HashMap;
import java.util.Map;

public class TwoSum {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
map.put(nums[i], i);
}
return new int[]{-1, -1};
}
}

Python 源码

1
2
3
4
5
6
7
8
def two_sum(nums: list[int], target: int) -> list[int]:
num_map = {}
for i, num in enumerate(nums):
complement = target - num
if complement in num_map:
return [num_map[complement], i]
num_map[num] = i
return [-1, -1]

3. 排序与搜索

3.1 快速排序

原题:实现快速排序算法。

示例

1
2
输入:[3,6,8,10,1,2,1]
输出:[1,1,2,3,6,8,10]

解题思路:选择一个基准元素,将小于基准的放左边,大于基准的放右边,递归排序左右子数组。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class QuickSort {
public void quickSort(int[] arr, int left, int right) {
if (left < right) {
int pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
}

private int partition(int[] arr, int left, int right) {
int pivot = arr[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, right);
return i + 1;
}

private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def quick_sort(arr: list[int], left: int, right: int):
if left < right:
pivot_index = partition(arr, left, right)
quick_sort(arr, left, pivot_index - 1)
quick_sort(arr, pivot_index + 1, right)

def partition(arr: list[int], left: int, right: int) -> int:
pivot = arr[right]
i = left - 1
for j in range(left, right):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[right] = arr[right], arr[i + 1]
return i + 1

4. 动态规划

4.1 三角形最小路径和

原题:给定一个三角形,找到从顶部到底部的最小路径和。每一步只能移动到下一行中相邻的数字。

示例

1
2
3
4
5
6
7
8
9
输入:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
输出:11
解释:自顶向下的最小路径和为 2 + 3 + 5 + 1 = 11

解题思路:动态规划,从下往上递推更简洁。dp[i][j]表示从(i,j)到底部的最小路径和。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.List;

public class TriangleMinimumTotal {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[] dp = new int[n + 1];
for (int i = n - 1; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
}
}
return dp[0];
}
}

Python 源码

1
2
3
4
5
6
7
def minimum_total(triangle: list[list[int]]) -> int:
n = len(triangle)
dp = [0] * (n + 1)
for i in range(n - 1, -1, -1):
for j in range(i + 1):
dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j]
return dp[0]

4.2 最长上升子序列

原题:给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例

1
2
3
输入:[10,9,2,5,3,7,101,18]
输出:4
解释:最长的上升子序列是 [2,3,7,101],它的长度是 4

解题思路:动态规划,dp[i]表示以i结尾的最长上升子序列长度。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LongestIncreasingSubsequence {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int[] dp = new int[nums.length];
int max = 1;
for (int i = 0; i < nums.length; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
def length_of_lis(nums: list[int]) -> int:
if not nums:
return 0
dp = [1] * len(nums)
max_len = 1
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
max_len = max(max_len, dp[i])
return max_len

5. 树与图

5.1 二叉树的层序遍历

原题:给你一个二叉树,请你返回其按层序遍历得到的节点值。(即逐层地,从左到右访问所有节点)。

示例

1
2
3
4
5
6
7
输入:[3,9,20,null,null,15,7]
输出:
[
[3],
[9,20],
[15,7]
]

解题思路:使用队列进行广度优先搜索(BFS)。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}

public class BinaryTreeLevelOrder {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(level);
}
return result;
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from collections import deque
from typing import List, Optional

class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right

def level_order(root: Optional[TreeNode]) -> List[List[int]]:
result = []
if not root:
return result
queue = deque([root])
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result

5.2 二叉树的最大深度

原题:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

示例

1
2
输入:[3,9,20,null,null,15,7]
输出:3

解题思路:递归或BFS。

Java 源码

1
2
3
4
5
6
7
8
public class MaxDepth {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
}

Python 源码

1
2
3
4
5
6
def max_depth(root: Optional[TreeNode]) -> int:
if not root:
return 0
left = max_depth(root.left)
right = max_depth(root.right)
return max(left, right) + 1

6. 双指针与滑动窗口

6.1 无重复字符的最长子串

原题:给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。

示例

1
2
3
输入:s = "abcabcbb"
输出:3
解释:因为无重复字符的最长子串是 "abc",所以其长度为 3

解题思路:滑动窗口,用哈希表记录字符最后出现的位置。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.HashMap;
import java.util.Map;

public class LongestSubstringWithoutRepeating {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int max = 0;
int left = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (map.containsKey(c)) {
left = Math.max(left, map.get(c) + 1);
}
map.put(c, right);
max = Math.max(max, right - left + 1);
}
return max;
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
def length_of_longest_substring(s: str) -> int:
char_map = {}
max_len = 0
left = 0
for right in range(len(s)):
c = s[right]
if c in char_map:
left = max(left, char_map[c] + 1)
char_map[c] = right
max_len = max(max_len, right - left + 1)
return max_len

6.2 盛最多水的容器

原题:给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

示例

1
2
3
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水的最大值为 49。

解题思路:双指针,每次移动较短的那一边。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ContainerWithMostWater {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int max = 0;
while (left < right) {
int area = Math.min(height[left], height[right]) * (right - left);
max = Math.max(max, area);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return max;
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
12
def max_area(height: list[int]) -> int:
left = 0
right = len(height) - 1
max_area = 0
while left < right:
area = min(height[left], height[right]) * (right - left)
max_area = max(max_area, area)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area

7. 回溯与DFS

7.1 全排列

原题:给定一个不含重复数字的数组 nums ,返回其所有可能的全排列。

示例

1
2
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

解题思路:回溯算法,交换法或visited数组标记。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.ArrayList;
import java.util.List;

public class Permutations {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, 0, result);
return result;
}

private void backtrack(int[] nums, int start, List<List<Integer>> result) {
if (start == nums.length) {
List<Integer> list = new ArrayList<>();
for (int num : nums) list.add(num);
result.add(list);
return;
}
for (int i = start; i < nums.length; i++) {
swap(nums, start, i);
backtrack(nums, start + 1, result);
swap(nums, start, i);
}
}

private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
12
def permute(nums: list[int]) -> list[list[int]]:
result = []
def backtrack(start: int):
if start == len(nums):
result.append(nums.copy())
return
for i in range(start, len(nums)):
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start]
backtrack(0)
return result

7.2 组合总和

原题:给你一个无重复元素的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的所有不同组合。

示例

1
2
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]

解题思路:回溯,剪枝优化。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CombinationSum {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(candidates);
backtrack(candidates, target, 0, new ArrayList<>(), result);
return result;
}

private void backtrack(int[] candidates, int target, int start, List<Integer> path, List<List<Integer>> result) {
if (target == 0) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
if (candidates[i] > target) break;
path.add(candidates[i]);
backtrack(candidates, target - candidates[i], i, path, result);
path.remove(path.size() - 1);
}
}
}

Python 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def combination_sum(candidates: list[int], target: int) -> list[list[int]]:
result = []
candidates.sort()
def backtrack(remain: int, start: int, path: list[int]):
if remain == 0:
result.append(path.copy())
return
for i in range(start, len(candidates)):
if candidates[i] > remain:
break
path.append(candidates[i])
backtrack(remain - candidates[i], i, path)
path.pop()
backtrack(target, 0, [])
return result

8. 其他经典题目

8.1 进制转换

原题:输入一个十六进制的字符串,输出其十进制的表示。

示例

1
2
输入:0xA
输出:10

解题思路:直接使用语言内置函数,或手动计算每一位。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Scanner;

public class HexToDecimal {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNextLine()) {
String s = sc.nextLine().trim();
if (s.startsWith("0x") || s.startsWith("0X")) {
s = s.substring(2);
}
System.out.println(Integer.parseInt(s, 16));
}
}
}

Python 源码

1
2
3
4
5
6
7
if __name__ == "__main__":
import sys
for line in sys.stdin:
s = line.strip()
if s.startswith(('0x', '0X')):
s = s[2:]
print(int(s, 16))

8.2 斐波那契数列

原题:写一个函数,输入 n ,求斐波那契数列的第 n 项。

示例

1
2
3
输入:n = 5
输出:5
解释:F(5) = F(4) + F(3) = 3 + 2 = 5

解题思路:动态规划,避免递归的重复计算。

Java 源码

1
2
3
4
5
6
7
8
9
10
11
12
public class Fibonacci {
public int fib(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; i++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
}

Python 源码

1
2
3
4
5
6
7
def fib(n: int) -> int:
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b

基础算法思想

深度优先遍历(DFS)

思路:递归,访问顶点v后,继续以深度优先的方式访问其邻节点。

模板

1
2
3
4
5
6
7
8
void dfs(Node node, Set<Node> visited) {
if (node == null || visited.contains(node)) return;
visited.add(node);
// 处理当前节点
for (Node neighbor : node.neighbors) {
dfs(neighbor, visited);
}
}

广度优先遍历(BFS)

思路:画弧,访问顶点v后,继续访问顶点v的所有邻节点,待其访问最后最后一邻节点,按顺序访问首次访问的邻节点的所有邻节点。依次进行。

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void bfs(Node start) {
Queue<Node> queue = new LinkedList<>();
Set<Node> visited = new HashSet<>();
queue.offer(start);
visited.add(start);
while (!queue.isEmpty()) {
Node node = queue.poll();
// 处理当前节点
for (Node neighbor : node.neighbors) {
if (!visited.contains(neighbor)) {
visited.add(neighbor);
queue.offer(neighbor);
}
}
}
}

面试技巧

  1. 先问清楚:确认输入输出格式、边界条件、数据范围
  2. 思路先行:先讲思路,再写代码
  3. 复杂度分析:时间、空间复杂度分析
  4. 测试用例:正常情况、边界情况、异常情况
  5. 代码规范:变量命名清晰,适当注释