返回题库算法与数据结构刷题复杂度、排序与解题方法 · 第 3 / 3 篇

算法编程题:复杂度、排序与解题方法(6 题)

001 实现插入排序并保持相等元素的原始相对顺序。

难度: 基础

方法签名: int[] numbers

输入约束: numbers 非 null,允许返回新数组

示例: [4, 2, 2, 1] → [1, 2, 2, 4]

目标复杂度: 最坏时间 O(n²),额外空间 O(n)

查看参考答案

解题思路: 复制输入,从左到右把当前元素插入已经有序的前缀。

不变量: 每轮开始时 [0, i) 已按升序稳定排列。

public static class AlgCode001Solution
{
    public static int[] Solve(int[] numbers)
    {
        int[] result = (int[])numbers.Clone();
        for (int index = 1; index < result.Length; index++)
        {
            int value = result[index];
            int position = index - 1;
            while (position >= 0 && result[position] > value)
            {
                result[position + 1] = result[position];
                position--;
            }
            result[position + 1] = value;
        }
        return result;
    }
}

复杂度: 最坏时间 O(n²),最好时间 O(n),额外空间 O(n)。

边界用例: 空数组;已经有序;全部相等。

面试追问:

  • 如果允许原地修改输入,空间复杂度如何变化?

002 实现稳定的归并排序。

难度: 进阶

方法签名: int[] numbers

输入约束: numbers 非 null,长度不超过 200000

示例: [5, -1, 3, 3] → [-1, 3, 3, 5]

目标复杂度: 时间 O(n log n),额外空间 O(n)

查看参考答案

解题思路: 递归排序左右区间,再把两个有序区间稳定合并。

不变量: 合并时临时数组始终包含两个输入前缀中最小的若干元素。

public static class AlgCode002Solution
{
    public static int[] Solve(int[] numbers)
    {
        int[] result = (int[])numbers.Clone();
        int[] buffer = new int[result.Length];
        Sort(result, buffer, 0, result.Length);
        return result;
    }
    
    private static void Sort(int[] values, int[] buffer, int start, int end)
    {
        if (end - start <= 1) return;
        int middle = start + (end - start) / 2;
        Sort(values, buffer, start, middle);
        Sort(values, buffer, middle, end);
        int left = start, right = middle, write = start;
        while (left < middle || right < end)
        {
            if (right >= end || (left < middle && values[left] <= values[right]))
                buffer[write++] = values[left++];
            else
                buffer[write++] = values[right++];
        }
        Array.Copy(buffer, start, values, start, end - start);
    }
}

复杂度: 时间 O(n log n),额外空间 O(n),递归栈 O(log n)。

边界用例: 空数组;单元素;重复值;极端有序输入。

面试追问:

  • 链表归并排序为什么可以把额外数组空间降下来?

003 实现能有效处理大量重复值的三向切分快速排序。

难度: 进阶

方法签名: int[] numbers

输入约束: numbers 非 null,允许原地排序

示例: [3, 1, 3, 2, 3] → [1, 2, 3, 3, 3]

目标复杂度: 平均时间 O(n log n),递归栈平均 O(log n)

查看参考答案

解题思路: 把区间划分为小于、等于和大于基准值的三段。

不变量: 扫描过程中左段小于基准,中段等于基准,右段大于基准。

public static class AlgCode003Solution
{
    public static int[] Solve(int[] numbers)
    {
        int[] result = (int[])numbers.Clone();
        Sort(result, 0, result.Length - 1);
        return result;
    }
    
    private static void Sort(int[] values, int left, int right)
    {
        if (left >= right) return;
        int pivot = values[left + (right - left) / 2];
        int lower = left, index = left, upper = right;
        while (index <= upper)
        {
            if (values[index] < pivot)
                (values[lower++], values[index++]) = (values[index], values[lower]);
            else if (values[index] > pivot)
                (values[index], values[upper--]) = (values[upper], values[index]);
            else
                index++;
        }
        Sort(values, left, lower - 1);
        Sort(values, upper + 1, right);
    }
}

复杂度: 平均时间 O(n log n),最坏时间 O(n²),递归栈平均 O(log n)。

边界用例: 空数组;全部相等;已经有序;大量重复值。

面试追问:

  • 如何随机选择基准以降低持续遇到坏划分的概率?

004 合并所有互相重叠的闭区间。

难度: 进阶

方法签名: int[][] intervals

输入约束: 每个区间恰有两个端点且 start <= end

示例: [[1,3],[2,6],[8,10]] → [[1,6],[8,10]]

目标复杂度: 时间 O(n log n),额外空间 O(n)

查看参考答案

解题思路: 按起点排序,当前区间与结果末尾重叠时扩展终点。

不变量: 结果中的区间互不重叠,且覆盖所有已扫描输入。

public static class AlgCode004Solution
{
    public static int[][] Solve(int[][] intervals)
    {
        if (intervals.Length == 0) return [];
        int[][] sorted = intervals
            .Select(interval => new[] { interval[0], interval[1] })
            .OrderBy(interval => interval[0])
            .ThenBy(interval => interval[1])
            .ToArray();
        var merged = new List<int[]> { sorted[0] };
        for (int index = 1; index < sorted.Length; index++)
        {
            int[] last = merged[^1];
            int[] current = sorted[index];
            if (current[0] <= last[1])
                last[1] = Math.Max(last[1], current[1]);
            else
                merged.Add(current);
        }
        return merged.ToArray();
    }
}

复杂度: 排序时间 O(n log n),扫描 O(n),额外空间 O(n)。

边界用例: 空输入;相邻端点;完全包含;负数端点。

面试追问:

  • 如果输入已经按起点排序,可以省掉哪部分成本?

005 按主键排序记录,并让主键相同时保持原始输入顺序。

难度: 基础

方法签名: (string Name, int Priority)[] items

输入约束: items 非 null,Name 非 null

示例: [(A,2),(B,1),(C,2)] → [(B,1),(A,2),(C,2)]

目标复杂度: 时间 O(n log n),额外空间 O(n)

查看参考答案

解题思路: 记录原始下标,以主键和原始下标组成排序键。

不变量: 相同 Priority 的记录始终按原始下标递增。

public static class AlgCode005Solution
{
    public static (string Name, int Priority)[] Solve(
        (string Name, int Priority)[] items)
    {
        return items
            .Select((item, index) => (item, index))
            .OrderBy(entry => entry.item.Priority)
            .ThenBy(entry => entry.index)
            .Select(entry => entry.item)
            .ToArray();
    }
}

复杂度: 时间 O(n log n),额外空间 O(n)。

边界用例: 空输入;全部主键相同;名称重复。

面试追问:

  • 如果排序 API 已承诺稳定,还需要显式保存原始下标吗?

006 使用归并过程统计数组中的逆序对数量。

难度: 综合

方法签名: int[] numbers

输入约束: numbers 非 null,长度不超过 200000

示例: [2, 4, 1, 3, 5] → 3

目标复杂度: 时间 O(n log n),额外空间 O(n)

查看参考答案

解题思路: 归并左右有序段;右侧元素先写入时,一次贡献左侧剩余数量。

不变量: 递归返回前区间已经有序,计数包含区间内部全部逆序对。

public static class AlgCode006Solution
{
    public static long Solve(int[] numbers)
    {
        int[] values = (int[])numbers.Clone();
        int[] buffer = new int[values.Length];
        return Count(values, buffer, 0, values.Length);
    }
    
    private static long Count(int[] values, int[] buffer, int start, int end)
    {
        if (end - start <= 1) return 0;
        int middle = start + (end - start) / 2;
        long count = Count(values, buffer, start, middle) +
            Count(values, buffer, middle, end);
        int left = start, right = middle, write = start;
        while (left < middle || right < end)
        {
            if (right >= end || (left < middle && values[left] <= values[right]))
                buffer[write++] = values[left++];
            else
            {
                buffer[write++] = values[right++];
                count += middle - left;
            }
        }
        Array.Copy(buffer, start, values, start, end - start);
        return count;
    }
}

复杂度: 时间 O(n log n),额外空间 O(n),结果使用 long 防止计数溢出。

边界用例: 空数组;重复值不构成逆序;完全逆序;计数超过 int。

面试追问:

  • 为什么相等值不能计入逆序对?
当前分类

复杂度、排序与解题方法

查看全部分类 →
  1. 01算法专题:复杂度、排序与解题方法
  2. 02算法选择题:复杂度、排序与解题方法(5 题)5 题
  3. 03算法编程题:复杂度、排序与解题方法(6 题)6 题
ESC

输入关键词开始搜索