高阶算法技巧分享 一

96 阅读3分钟

1. 动态规划 + 状态压缩

核心思想:通过滚动数组或变量覆盖,将二维/三维DP压缩到一维,大幅降低空间复杂度。
应用场景:背包问题、路径问题等需要状态转移的场景。
示例(0-1背包问题,空间优化到O(n)):

public int knapsack(int[] weights, int[] values, int capacity) {
    int[] dp = new int[capacity + 1];
    for (int i = 0; i < weights.length; i++) {
        for (int j = capacity; j >= weights[i]; j--) { // 逆向遍历避免覆盖
            dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
        }
    }
    return dp[capacity];
}

2. 位运算技巧

核心思想:利用位操作的特性(如异或、掩码)实现高效计算。
应用场景:状态压缩、快速判断奇偶、交换变量、找唯一数等。
示例(找数组中唯一出现一次的数,其他出现两次):

public int singleNumber(int[] nums) {
    int result = 0;
    for (int num : nums) {
        result ^= num; // 异或性质:a ^ a = 0, a ^ 0 = a
    }
    return result;
}

3. 单调栈

核心思想:维护一个栈内元素单调递增或递减,快速找到下一个更大/更小元素。
应用场景:柱状图最大面积、每日温度等。
示例(下一个更大元素):

public int[] nextGreaterElement(int[] nums) {
    int[] res = new int[nums.length];
    Arrays.fill(res, -1);
    Deque<Integer> stack = new ArrayDeque<>();
    for (int i = 0; i < nums.length; i++) {
        while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
            int idx = stack.pop();
            res[idx] = nums[i];
        }
        stack.push(i);
    }
    return res;
}

4. 并查集(Union-Find)

核心思想:通过路径压缩和按秩合并,高效处理集合的合并与查询。
应用场景:连通性问题(岛屿数量、朋友圈等)。
示例(基础并查集模板):

class UnionFind {
    private int[] parent;
    private int[] rank;

    public UnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i;
        }
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]); // 路径压缩
        }
        return parent[x];
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else {
                parent[rootX] = rootY;
                if (rank[rootX] == rank[rootY]) {
                    rank[rootY]++;
                }
            }
        }
    }
}

5. 线段树(Segment Tree)

核心思想:将区间分解为树形结构,支持区间查询与更新操作。
应用场景:动态区间最值、区间和等。
示例(区间和查询):

class SegmentTree {
    private int[] tree;
    private int n;

    public SegmentTree(int[] nums) {
        n = nums.length;
        tree = new int[4 * n];
        build(nums, 0, 0, n - 1);
    }

    private void build(int[] nums, int node, int start, int end) {
        if (start == end) {
            tree[node] = nums[start];
        } else {
            int mid = (start + end) / 2;
            build(nums, 2 * node + 1, start, mid);
            build(nums, 2 * node + 2, mid + 1, end);
            tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
        }
    }

    public void update(int idx, int val) {
        update(0, 0, n - 1, idx, val);
    }

    private void update(int node, int start, int end, int idx, int val) {
        if (start == end) {
            tree[node] = val;
        } else {
            int mid = (start + end) / 2;
            if (idx <= mid) {
                update(2 * node + 1, start, mid, idx, val);
            } else {
                update(2 * node + 2, mid + 1, end, idx, val);
            }
            tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
        }
    }

    public int query(int l, int r) {
        return query(0, 0, n - 1, l, r);
    }

    private int query(int node, int start, int end, int l, int r) {
        if (r < start || end < l) return 0;
        if (l <= start && end <= r) return tree[node];
        int mid = (start + end) / 2;
        return query(2 * node + 1, start, mid, l, r) +
               query(2 * node + 2, mid + 1, end, l, r);
    }
}

6. 快速选择(QuickSelect)

核心思想:基于快速排序的分区思想,在O(n)时间内找到第K小/大的元素。
示例(找数组中第K大的元素):

public int findKthLargest(int[] nums, int k) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int pivotIndex = partition(nums, left, right);
        if (pivotIndex == k - 1) {
            return nums[pivotIndex];
        } else if (pivotIndex < k - 1) {
            left = pivotIndex + 1;
        } else {
            right = pivotIndex - 1;
        }
    }
    return -1;
}

private int partition(int[] nums, int left, int right) {
    int pivot = nums[right];
    int i = left;
    for (int j = left; j < right; j++) {
        if (nums[j] >= pivot) { // 降序排列找第K大
            swap(nums, i, j);
            i++;
        }
    }
    swap(nums, i, right);
    return i;
}

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

选择技巧的时机

  • 动态规划优化:空间限制严格时(如内存敏感场景)。
  • 位运算:需要极致性能或处理二进制状态时。
  • 单调栈/队列:需要维护区间极值时。
  • 线段树/并查集:频繁区间查询或合并操作时。