算法与数据结构进阶

概述

算法与数据结构是Java高级开发工程师必须掌握的基础知识,不仅在面试中经常考察,在实际工作中也是解决复杂问题和系统优化的重要工具。本文深入讲解常用算法、数据结构的实现和应用场景。

核心面试问题

1. 高级数据结构实现

面试问题:如何实现一个线程安全的LRU缓存?手写红黑树的插入操作?

LRU缓存实现

// 线程安全的LRU缓存实现
public class ThreadSafeLRUCache<K, V> {

    private final int capacity;
    private final Map<K, Node<K, V>> cache;
    private final Node<K, V> head;
    private final Node<K, V> tail;
    private final ReentrantReadWriteLock lock;
    private final Lock readLock;
    private final Lock writeLock;

    private static class Node<K, V> {
        K key;
        V value;
        Node<K, V> prev;
        Node<K, V> next;

        Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    public ThreadSafeLRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>();
        this.lock = new ReentrantReadWriteLock();
        this.readLock = lock.readLock();
        this.writeLock = lock.writeLock();

        // 创建虚拟头尾节点
        this.head = new Node<>(null, null);
        this.tail = new Node<>(null, null);
        head.next = tail;
        tail.prev = head;
    }

    public V get(K key) {
        readLock.lock();
        try {
            Node<K, V> node = cache.get(key);
            if (node == null) {
                return null;
            }

            // 需要升级为写锁来移动节点
            readLock.unlock();
            writeLock.lock();
            try {
                // 双重检查
                node = cache.get(key);
                if (node != null) {
                    moveToHead(node);
                    return node.value;
                }
                return null;
            } finally {
                readLock.lock(); // 降级为读锁
                writeLock.unlock();
            }
        } finally {
            readLock.unlock();
        }
    }

    public void put(K key, V value) {
        writeLock.lock();
        try {
            Node<K, V> node = cache.get(key);

            if (node != null) {
                // 更新现有节点
                node.value = value;
                moveToHead(node);
            } else {
                // 插入新节点
                Node<K, V> newNode = new Node<>(key, value);

                if (cache.size() >= capacity) {
                    // 移除最少使用的节点
                    Node<K, V> lastNode = tail.prev;
                    removeNode(lastNode);
                    cache.remove(lastNode.key);
                }

                addToHead(newNode);
                cache.put(key, newNode);
            }
        } finally {
            writeLock.unlock();
        }
    }

    private void moveToHead(Node<K, V> node) {
        removeNode(node);
        addToHead(node);
    }

    private void removeNode(Node<K, V> node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void addToHead(Node<K, V> node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    public int size() {
        readLock.lock();
        try {
            return cache.size();
        } finally {
            readLock.unlock();
        }
    }

    public void clear() {
        writeLock.lock();
        try {
            cache.clear();
            head.next = tail;
            tail.prev = head;
        } finally {
            writeLock.unlock();
        }
    }
}

// 使用ConcurrentHashMap的简化版本
public class SimpleLRUCache<K, V> extends LinkedHashMap<K, V> {

    private final int capacity;
    private final Object lock = new Object();

    public SimpleLRUCache(int capacity) {
        super(capacity + 1, 1.0f, true); // accessOrder = true
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    @Override
    public V get(Object key) {
        synchronized (lock) {
            return super.get(key);
        }
    }

    @Override
    public V put(K key, V value) {
        synchronized (lock) {
            return super.put(key, value);
        }
    }
}

红黑树实现

// 红黑树实现
public class RedBlackTree<T extends Comparable<T>> {

    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private Node<T> root;

    private static class Node<T> {
        T data;
        Node<T> left;
        Node<T> right;
        Node<T> parent;
        boolean color;

        Node(T data, boolean color, Node<T> parent) {
            this.data = data;
            this.color = color;
            this.parent = parent;
        }
    }

    public void insert(T data) {
        Node<T> newNode = new Node<>(data, RED, null);

        if (root == null) {
            root = newNode;
            root.color = BLACK;
            return;
        }

        // 标准BST插入
        Node<T> current = root;
        Node<T> parent = null;

        while (current != null) {
            parent = current;
            int cmp = data.compareTo(current.data);
            if (cmp < 0) {
                current = current.left;
            } else if (cmp > 0) {
                current = current.right;
            } else {
                // 数据已存在,更新值
                current.data = data;
                return;
            }
        }

        newNode.parent = parent;
        int cmp = data.compareTo(parent.data);
        if (cmp < 0) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }

        // 修复红黑树性质
        fixInsert(newNode);
    }

    private void fixInsert(Node<T> node) {
        // 案例1:节点是根节点
        if (node.parent == null) {
            node.color = BLACK;
            return;
        }

        // 案例2:父节点是黑色
        if (node.parent.color == BLACK) {
            return;
        }

        Node<T> parent = node.parent;
        Node<T> grandparent = parent.parent;
        Node<T> uncle = getUncle(node);

        // 案例3:父节点和叔叔节点都是红色
        if (uncle != null && uncle.color == RED) {
            parent.color = BLACK;
            uncle.color = BLACK;
            grandparent.color = RED;
            fixInsert(grandparent);
            return;
        }

        // 案例4:父节点是红色,叔叔节点是黑色(或不存在)
        // 需要旋转
        if (parent == grandparent.left) {
            if (node == parent.right) {
                // 左右情况:先左旋父节点
                rotateLeft(parent);
                node = parent;
                parent = node.parent;
            }
            // 左左情况:右旋祖父节点
            rotateRight(grandparent);
            parent.color = BLACK;
            grandparent.color = RED;
        } else {
            if (node == parent.left) {
                // 右左情况:先右旋父节点
                rotateRight(parent);
                node = parent;
                parent = node.parent;
            }
            // 右右情况:左旋祖父节点
            rotateLeft(grandparent);
            parent.color = BLACK;
            grandparent.color = RED;
        }
    }

    private Node<T> getUncle(Node<T> node) {
        Node<T> grandparent = node.parent.parent;
        if (grandparent == null) {
            return null;
        }

        if (node.parent == grandparent.left) {
            return grandparent.right;
        } else {
            return grandparent.left;
        }
    }

    private void rotateLeft(Node<T> node) {
        Node<T> rightChild = node.right;
        node.right = rightChild.left;

        if (rightChild.left != null) {
            rightChild.left.parent = node;
        }

        rightChild.parent = node.parent;

        if (node.parent == null) {
            root = rightChild;
        } else if (node == node.parent.left) {
            node.parent.left = rightChild;
        } else {
            node.parent.right = rightChild;
        }

        rightChild.left = node;
        node.parent = rightChild;
    }

    private void rotateRight(Node<T> node) {
        Node<T> leftChild = node.left;
        node.left = leftChild.right;

        if (leftChild.right != null) {
            leftChild.right.parent = node;
        }

        leftChild.parent = node.parent;

        if (node.parent == null) {
            root = leftChild;
        } else if (node == node.parent.right) {
            node.parent.right = leftChild;
        } else {
            node.parent.left = leftChild;
        }

        leftChild.right = node;
        node.parent = leftChild;
    }

    public boolean search(T data) {
        return search(root, data) != null;
    }

    private Node<T> search(Node<T> node, T data) {
        if (node == null) {
            return null;
        }

        int cmp = data.compareTo(node.data);
        if (cmp == 0) {
            return node;
        } else if (cmp < 0) {
            return search(node.left, data);
        } else {
            return search(node.right, data);
        }
    }

    public void inorderTraversal() {
        inorderTraversal(root);
        System.out.println();
    }

    private void inorderTraversal(Node<T> node) {
        if (node != null) {
            inorderTraversal(node.left);
            System.out.print(node.data + "(" + (node.color ? "R" : "B") + ") ");
            inorderTraversal(node.right);
        }
    }
}

并发安全的跳表实现

// 跳表实现(类似ConcurrentSkipListMap)
public class ConcurrentSkipList<T extends Comparable<T>> {

    private static final int MAX_LEVEL = 32;
    private static final double P = 0.5;

    private final Node<T> header;
    private volatile int level;
    private final Random random;

    private static class Node<T> {
        final T value;
        final Node<T>[] forward;
        volatile boolean marked;

        @SuppressWarnings("unchecked")
        Node(T value, int level) {
            this.value = value;
            this.forward = new Node[level + 1];
            this.marked = false;
        }
    }

    @SuppressWarnings("unchecked")
    public ConcurrentSkipList() {
        this.header = new Node<>(null, MAX_LEVEL);
        this.level = 0;
        this.random = new Random();
    }

    public boolean contains(T value) {
        return findNode(value) != null;
    }

    private Node<T> findNode(T value) {
        Node<T> current = header;

        for (int i = level; i >= 0; i--) {
            while (current.forward[i] != null && 
                   !current.forward[i].marked &&
                   current.forward[i].value.compareTo(value) < 0) {
                current = current.forward[i];
            }
        }

        current = current.forward[0];
        if (current != null && !current.marked && current.value.equals(value)) {
            return current;
        }

        return null;
    }

    public boolean add(T value) {
        Node<T>[] update = new Node[MAX_LEVEL + 1];
        Node<T> current = header;

        // 找到插入位置
        for (int i = level; i >= 0; i--) {
            while (current.forward[i] != null && 
                   !current.forward[i].marked &&
                   current.forward[i].value.compareTo(value) < 0) {
                current = current.forward[i];
            }
            update[i] = current;
        }

        current = current.forward[0];

        // 检查是否已存在
        if (current != null && !current.marked && current.value.equals(value)) {
            return false;
        }

        // 生成新节点的层级
        int newLevel = randomLevel();
        if (newLevel > level) {
            for (int i = level + 1; i <= newLevel; i++) {
                update[i] = header;
            }
            level = newLevel;
        }

        Node<T> newNode = new Node<>(value, newLevel);

        // 插入新节点
        for (int i = 0; i <= newLevel; i++) {
            newNode.forward[i] = update[i].forward[i];
            update[i].forward[i] = newNode;
        }

        return true;
    }

    public boolean remove(T value) {
        Node<T>[] update = new Node[MAX_LEVEL + 1];
        Node<T> current = header;

        // 找到要删除的节点
        for (int i = level; i >= 0; i--) {
            while (current.forward[i] != null && 
                   !current.forward[i].marked &&
                   current.forward[i].value.compareTo(value) < 0) {
                current = current.forward[i];
            }
            update[i] = current;
        }

        current = current.forward[0];

        if (current == null || current.marked || !current.value.equals(value)) {
            return false;
        }

        // 标记为删除
        current.marked = true;

        // 更新指针
        for (int i = 0; i <= level; i++) {
            if (update[i].forward[i] != current) {
                break;
            }
            update[i].forward[i] = current.forward[i];
        }

        // 更新层级
        while (level > 0 && header.forward[level] == null) {
            level--;
        }

        return true;
    }

    private int randomLevel() {
        int level = 0;
        while (random.nextDouble() < P && level < MAX_LEVEL) {
            level++;
        }
        return level;
    }

    public void printList() {
        for (int i = level; i >= 0; i--) {
            System.out.print("Level " + i + ": ");
            Node<T> current = header.forward[i];
            while (current != null) {
                if (!current.marked) {
                    System.out.print(current.value + " ");
                }
                current = current.forward[i];
            }
            System.out.println();
        }
    }
}

2. 高级算法实现

面试问题:实现KMP字符串匹配算法,设计一个高效的topK算法?

字符串匹配算法

public class StringMatchingAlgorithms {

    // KMP算法实现
    public static class KMP {

        public static int search(String text, String pattern) {
            if (pattern.isEmpty()) return 0;
            if (text.length() < pattern.length()) return -1;

            int[] lps = computeLPS(pattern);
            int i = 0; // text的索引
            int j = 0; // pattern的索引

            while (i < text.length()) {
                if (text.charAt(i) == pattern.charAt(j)) {
                    i++;
                    j++;
                }

                if (j == pattern.length()) {
                    return i - j; // 找到匹配
                } else if (i < text.length() && text.charAt(i) != pattern.charAt(j)) {
                    if (j != 0) {
                        j = lps[j - 1];
                    } else {
                        i++;
                    }
                }
            }

            return -1; // 未找到匹配
        }

        private static int[] computeLPS(String pattern) {
            int[] lps = new int[pattern.length()];
            int len = 0; // 最长相等前后缀的长度
            int i = 1;

            while (i < pattern.length()) {
                if (pattern.charAt(i) == pattern.charAt(len)) {
                    len++;
                    lps[i] = len;
                    i++;
                } else {
                    if (len != 0) {
                        len = lps[len - 1];
                    } else {
                        lps[i] = 0;
                        i++;
                    }
                }
            }

            return lps;
        }

        // 查找所有匹配位置
        public static List<Integer> searchAll(String text, String pattern) {
            List<Integer> results = new ArrayList<>();
            if (pattern.isEmpty()) return results;

            int[] lps = computeLPS(pattern);
            int i = 0, j = 0;

            while (i < text.length()) {
                if (text.charAt(i) == pattern.charAt(j)) {
                    i++;
                    j++;
                }

                if (j == pattern.length()) {
                    results.add(i - j);
                    j = lps[j - 1];
                } else if (i < text.length() && text.charAt(i) != pattern.charAt(j)) {
                    if (j != 0) {
                        j = lps[j - 1];
                    } else {
                        i++;
                    }
                }
            }

            return results;
        }
    }

    // Rabin-Karp算法(滚动哈希)
    public static class RabinKarp {
        private static final int PRIME = 101;

        public static List<Integer> search(String text, String pattern) {
            List<Integer> results = new ArrayList<>();
            int m = pattern.length();
            int n = text.length();

            if (m > n) return results;

            // 计算模式字符串的哈希值
            long patternHash = hash(pattern, m);
            long textHash = hash(text, m);

            // 滑动窗口
            for (int i = 0; i <= n - m; i++) {
                // 检查哈希值
                if (patternHash == textHash) {
                    // 哈希值相等,需要逐字符比较确认
                    if (text.substring(i, i + m).equals(pattern)) {
                        results.add(i);
                    }
                }

                // 计算下一个窗口的哈希值
                if (i < n - m) {
                    textHash = rollingHash(textHash, text.charAt(i), 
                                         text.charAt(i + m), m);
                }
            }

            return results;
        }

        private static long hash(String str, int length) {
            long hash = 0;
            for (int i = 0; i < length; i++) {
                hash += str.charAt(i) * Math.pow(PRIME, i);
            }
            return hash;
        }

        private static long rollingHash(long oldHash, char oldChar, char newChar, int length) {
            long newHash = (oldHash - oldChar) / PRIME;
            newHash += newChar * Math.pow(PRIME, length - 1);
            return newHash;
        }
    }

    // AC自动机(多模式匹配)
    public static class AhoCorasick {

        private static class TrieNode {
            Map<Character, TrieNode> children = new HashMap<>();
            TrieNode failure = null;
            List<String> output = new ArrayList<>();
        }

        private TrieNode root;

        public AhoCorasick(List<String> patterns) {
            buildTrie(patterns);
            buildFailureFunction();
        }

        private void buildTrie(List<String> patterns) {
            root = new TrieNode();

            for (String pattern : patterns) {
                TrieNode current = root;
                for (char c : pattern.toCharArray()) {
                    current.children.putIfAbsent(c, new TrieNode());
                    current = current.children.get(c);
                }
                current.output.add(pattern);
            }
        }

        private void buildFailureFunction() {
            Queue<TrieNode> queue = new LinkedList<>();

            // 第一层的失效指针都指向根节点
            for (TrieNode child : root.children.values()) {
                child.failure = root;
                queue.offer(child);
            }

            while (!queue.isEmpty()) {
                TrieNode current = queue.poll();

                for (Map.Entry<Character, TrieNode> entry : current.children.entrySet()) {
                    char c = entry.getKey();
                    TrieNode child = entry.getValue();

                    // 构建失效指针
                    TrieNode failure = current.failure;
                    while (failure != null && !failure.children.containsKey(c)) {
                        failure = failure.failure;
                    }

                    if (failure != null) {
                        child.failure = failure.children.get(c);
                    } else {
                        child.failure = root;
                    }

                    // 合并输出
                    child.output.addAll(child.failure.output);

                    queue.offer(child);
                }
            }
        }

        public List<MatchResult> search(String text) {
            List<MatchResult> results = new ArrayList<>();
            TrieNode current = root;

            for (int i = 0; i < text.length(); i++) {
                char c = text.charAt(i);

                // 沿着失效指针找到匹配的转移
                while (current != null && !current.children.containsKey(c)) {
                    current = current.failure;
                }

                if (current != null) {
                    current = current.children.get(c);
                } else {
                    current = root;
                }

                // 输出所有匹配的模式
                for (String pattern : current.output) {
                    results.add(new MatchResult(pattern, i - pattern.length() + 1));
                }
            }

            return results;
        }

        public static class MatchResult {
            public final String pattern;
            public final int position;

            public MatchResult(String pattern, int position) {
                this.pattern = pattern;
                this.position = position;
            }

            @Override
            public String toString() {
                return "Pattern: " + pattern + ", Position: " + position;
            }
        }
    }
}

TopK算法实现

public class TopKAlgorithms {

    // 使用小顶堆的TopK实现
    public static class HeapBasedTopK<T extends Comparable<T>> {
        private final int k;
        private final PriorityQueue<T> minHeap;

        public HeapBasedTopK(int k) {
            this.k = k;
            this.minHeap = new PriorityQueue<>(k);
        }

        public void add(T element) {
            if (minHeap.size() < k) {
                minHeap.offer(element);
            } else if (element.compareTo(minHeap.peek()) > 0) {
                minHeap.poll();
                minHeap.offer(element);
            }
        }

        public List<T> getTopK() {
            List<T> result = new ArrayList<>(minHeap);
            result.sort(Collections.reverseOrder()); // 降序排列
            return result;
        }
    }

    // 快速选择算法(Quick Select)
    public static class QuickSelect {

        public static int findKthLargest(int[] nums, int k) {
            return quickSelect(nums, 0, nums.length - 1, nums.length - k);
        }

        private static int quickSelect(int[] nums, int left, int right, int kIndex) {
            if (left == right) {
                return nums[left];
            }

            // 随机选择基准点,避免最坏情况
            Random random = new Random();
            int pivotIndex = left + random.nextInt(right - left + 1);

            pivotIndex = partition(nums, left, right, pivotIndex);

            if (kIndex == pivotIndex) {
                return nums[kIndex];
            } else if (kIndex < pivotIndex) {
                return quickSelect(nums, left, pivotIndex - 1, kIndex);
            } else {
                return quickSelect(nums, pivotIndex + 1, right, kIndex);
            }
        }

        private static int partition(int[] nums, int left, int right, int pivotIndex) {
            int pivotValue = nums[pivotIndex];

            // 将基准移到末尾
            swap(nums, pivotIndex, right);

            int storeIndex = left;
            for (int i = left; i < right; i++) {
                if (nums[i] < pivotValue) {
                    swap(nums, storeIndex, i);
                    storeIndex++;
                }
            }

            // 将基准移到正确位置
            swap(nums, storeIndex, right);
            return storeIndex;
        }

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

        // 获取TopK元素
        public static List<Integer> getTopK(int[] nums, int k) {
            if (k <= 0 || k > nums.length) {
                return new ArrayList<>();
            }

            int[] copy = nums.clone();
            int kthLargest = findKthLargest(copy, k);

            List<Integer> result = new ArrayList<>();
            int count = 0;

            for (int num : nums) {
                if (num > kthLargest || (num == kthLargest && count < k)) {
                    result.add(num);
                    if (num == kthLargest) {
                        count++;
                    }
                }
            }

            return result;
        }
    }

    // 分布式TopK算法
    public static class DistributedTopK {

        // 模拟分布式环境下的TopK计算
        public static List<Integer> distributedTopK(List<List<Integer>> partitions, int k) {
            // 第一阶段:每个分区计算本地TopK
            List<List<Integer>> localTopKs = partitions.parallelStream()
                    .map(partition -> getLocalTopK(partition, k))
                    .collect(Collectors.toList());

            // 第二阶段:合并所有本地TopK结果
            List<Integer> mergedList = localTopKs.stream()
                    .flatMap(List::stream)
                    .collect(Collectors.toList());

            // 第三阶段:从合并结果中选择全局TopK
            return QuickSelect.getTopK(mergedList.stream().mapToInt(i -> i).toArray(), k);
        }

        private static List<Integer> getLocalTopK(List<Integer> partition, int k) {
            HeapBasedTopK<Integer> topK = new HeapBasedTopK<>(k);
            for (Integer num : partition) {
                topK.add(num);
            }
            return topK.getTopK();
        }
    }

    // 流式TopK算法(适用于数据流场景)
    public static class StreamingTopK<T extends Comparable<T>> {
        private final int k;
        private final PriorityQueue<T> minHeap;
        private final Map<T, Integer> counts;

        public StreamingTopK(int k) {
            this.k = k;
            this.minHeap = new PriorityQueue<>(k);
            this.counts = new HashMap<>();
        }

        public void process(T element) {
            counts.put(element, counts.getOrDefault(element, 0) + 1);

            if (!minHeap.contains(element)) {
                if (minHeap.size() < k) {
                    minHeap.offer(element);
                } else if (element.compareTo(minHeap.peek()) > 0) {
                    minHeap.poll();
                    minHeap.offer(element);
                }
            }
        }

        public List<TopKItem<T>> getCurrentTopK() {
            return minHeap.stream()
                    .map(item -> new TopKItem<>(item, counts.get(item)))
                    .sorted((a, b) -> b.count - a.count)
                    .collect(Collectors.toList());
        }

        public static class TopKItem<T> {
            public final T item;
            public final int count;

            public TopKItem(T item, int count) {
                this.item = item;
                this.count = count;
            }

            @Override
            public String toString() {
                return item + "(" + count + ")";
            }
        }
    }
}

3. 动态规划经典问题

面试问题:最长公共子序列、背包问题、股票买卖问题的DP解法?

经典DP问题实现

public class DynamicProgramming {

    // 最长公共子序列(LCS)
    public static class LCS {

        public static int longestCommonSubsequence(String text1, String text2) {
            int m = text1.length();
            int n = text2.length();

            // dp[i][j] 表示 text1[0..i-1] 和 text2[0..j-1] 的LCS长度
            int[][] dp = new int[m + 1][n + 1];

            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                    } else {
                        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                    }
                }
            }

            return dp[m][n];
        }

        // 返回具体的LCS字符串
        public static String getLCSString(String text1, String text2) {
            int m = text1.length();
            int n = text2.length();
            int[][] dp = new int[m + 1][n + 1];

            // 构建DP表
            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                    } else {
                        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                    }
                }
            }

            // 回溯构建LCS字符串
            StringBuilder lcs = new StringBuilder();
            int i = m, j = n;

            while (i > 0 && j > 0) {
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    lcs.append(text1.charAt(i - 1));
                    i--;
                    j--;
                } else if (dp[i - 1][j] > dp[i][j - 1]) {
                    i--;
                } else {
                    j--;
                }
            }

            return lcs.reverse().toString();
        }

        // 空间优化版本
        public static int longestCommonSubsequenceOptimized(String text1, String text2) {
            int m = text1.length();
            int n = text2.length();

            // 只需要两行
            int[] prev = new int[n + 1];
            int[] curr = new int[n + 1];

            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                        curr[j] = prev[j - 1] + 1;
                    } else {
                        curr[j] = Math.max(prev[j], curr[j - 1]);
                    }
                }
                // 交换数组
                int[] temp = prev;
                prev = curr;
                curr = temp;
            }

            return prev[n];
        }
    }

    // 0-1背包问题
    public static class Knapsack {

        public static int knapsack01(int[] weights, int[] values, int capacity) {
            int n = weights.length;
            // dp[i][w] 表示前i个物品在容量为w时的最大价值
            int[][] dp = new int[n + 1][capacity + 1];

            for (int i = 1; i <= n; i++) {
                for (int w = 1; w <= capacity; w++) {
                    // 不选择第i个物品
                    dp[i][w] = dp[i - 1][w];

                    // 选择第i个物品(如果能放下)
                    if (weights[i - 1] <= w) {
                        dp[i][w] = Math.max(dp[i][w], 
                                          dp[i - 1][w - weights[i - 1]] + values[i - 1]);
                    }
                }
            }

            return dp[n][capacity];
        }

        // 空间优化版本
        public static int knapsack01Optimized(int[] weights, int[] values, int capacity) {
            int[] dp = new int[capacity + 1];

            for (int i = 0; i < weights.length; i++) {
                // 逆序遍历,避免重复使用
                for (int w = capacity; w >= weights[i]; w--) {
                    dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
                }
            }

            return dp[capacity];
        }

        // 返回选择的物品
        public static List<Integer> getKnapsackItems(int[] weights, int[] values, int capacity) {
            int n = weights.length;
            int[][] dp = new int[n + 1][capacity + 1];

            // 构建DP表
            for (int i = 1; i <= n; i++) {
                for (int w = 1; w <= capacity; w++) {
                    dp[i][w] = dp[i - 1][w];
                    if (weights[i - 1] <= w) {
                        dp[i][w] = Math.max(dp[i][w], 
                                          dp[i - 1][w - weights[i - 1]] + values[i - 1]);
                    }
                }
            }

            // 回溯找出选择的物品
            List<Integer> selectedItems = new ArrayList<>();
            int w = capacity;

            for (int i = n; i > 0 && w > 0; i--) {
                if (dp[i][w] != dp[i - 1][w]) {
                    selectedItems.add(i - 1); // 物品索引
                    w -= weights[i - 1];
                }
            }

            Collections.reverse(selectedItems);
            return selectedItems;
        }

        // 完全背包问题
        public static int unboundedKnapsack(int[] weights, int[] values, int capacity) {
            int[] dp = new int[capacity + 1];

            for (int i = 0; i < weights.length; i++) {
                for (int w = weights[i]; w <= capacity; w++) {
                    dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
                }
            }

            return dp[capacity];
        }
    }

    // 股票买卖问题
    public static class StockTrading {

        // 最多一次交易
        public static int maxProfitOneTransaction(int[] prices) {
            if (prices.length <= 1) return 0;

            int minPrice = prices[0];
            int maxProfit = 0;

            for (int i = 1; i < prices.length; i++) {
                maxProfit = Math.max(maxProfit, prices[i] - minPrice);
                minPrice = Math.min(minPrice, prices[i]);
            }

            return maxProfit;
        }

        // 无限次交易
        public static int maxProfitUnlimited(int[] prices) {
            int profit = 0;

            for (int i = 1; i < prices.length; i++) {
                if (prices[i] > prices[i - 1]) {
                    profit += prices[i] - prices[i - 1];
                }
            }

            return profit;
        }

        // 最多k次交易
        public static int maxProfitKTransactions(int k, int[] prices) {
            if (prices.length <= 1 || k == 0) return 0;

            // 如果k很大,等同于无限次交易
            if (k >= prices.length / 2) {
                return maxProfitUnlimited(prices);
            }

            // dp[i][j][0] 表示第i天,最多j次交易,当前不持有股票的最大利润
            // dp[i][j][1] 表示第i天,最多j次交易,当前持有股票的最大利润
            int[][][] dp = new int[prices.length][k + 1][2];

            for (int i = 0; i < prices.length; i++) {
                for (int j = 0; j <= k; j++) {
                    if (i == 0) {
                        dp[i][j][0] = 0;
                        dp[i][j][1] = -prices[i];
                        continue;
                    }

                    // 不持有股票:昨天不持有或今天卖出
                    dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);

                    // 持有股票:昨天持有或今天买入
                    if (j > 0) {
                        dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
                    } else {
                        dp[i][j][1] = dp[i - 1][j][1];
                    }
                }
            }

            return dp[prices.length - 1][k][0];
        }

        // 含冷冻期的股票交易
        public static int maxProfitWithCooldown(int[] prices) {
            if (prices.length <= 1) return 0;

            // hold: 持有股票, sold: 刚卖出股票(冷冻期), rest: 不持有股票(非冷冻期)
            int hold = -prices[0];
            int sold = 0;
            int rest = 0;

            for (int i = 1; i < prices.length; i++) {
                int prevHold = hold;
                int prevSold = sold;
                int prevRest = rest;

                hold = Math.max(prevHold, prevRest - prices[i]);
                sold = prevHold + prices[i];
                rest = Math.max(prevRest, prevSold);
            }

            return Math.max(sold, rest);
        }

        // 含手续费的股票交易
        public static int maxProfitWithFee(int[] prices, int fee) {
            int hold = -prices[0];
            int sold = 0;

            for (int i = 1; i < prices.length; i++) {
                hold = Math.max(hold, sold - prices[i]);
                sold = Math.max(sold, hold + prices[i] - fee);
            }

            return sold;
        }
    }

    // 编辑距离
    public static class EditDistance {

        public static int minDistance(String word1, String word2) {
            int m = word1.length();
            int n = word2.length();

            // dp[i][j] 表示 word1[0..i-1] 转换为 word2[0..j-1] 的最小操作数
            int[][] dp = new int[m + 1][n + 1];

            // 初始化
            for (int i = 0; i <= m; i++) {
                dp[i][0] = i; // 删除i个字符
            }
            for (int j = 0; j <= n; j++) {
                dp[0][j] = j; // 插入j个字符
            }

            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1]; // 不需要操作
                    } else {
                        dp[i][j] = Math.min(
                            Math.min(
                                dp[i - 1][j] + 1,     // 删除
                                dp[i][j - 1] + 1      // 插入
                            ),
                            dp[i - 1][j - 1] + 1     // 替换
                        );
                    }
                }
            }

            return dp[m][n];
        }

        // 返回具体的操作序列
        public static List<String> getEditOperations(String word1, String word2) {
            int m = word1.length();
            int n = word2.length();
            int[][] dp = new int[m + 1][n + 1];

            // 构建DP表
            for (int i = 0; i <= m; i++) dp[i][0] = i;
            for (int j = 0; j <= n; j++) dp[0][j] = j;

            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    } else {
                        dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), 
                                          dp[i - 1][j - 1] + 1);
                    }
                }
            }

            // 回溯构建操作序列
            List<String> operations = new ArrayList<>();
            int i = m, j = n;

            while (i > 0 || j > 0) {
                if (i > 0 && j > 0 && word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    i--;
                    j--;
                } else if (i > 0 && j > 0 && dp[i][j] == dp[i - 1][j - 1] + 1) {
                    operations.add("Replace " + word1.charAt(i - 1) + " with " + word2.charAt(j - 1));
                    i--;
                    j--;
                } else if (i > 0 && dp[i][j] == dp[i - 1][j] + 1) {
                    operations.add("Delete " + word1.charAt(i - 1));
                    i--;
                } else {
                    operations.add("Insert " + word2.charAt(j - 1));
                    j--;
                }
            }

            Collections.reverse(operations);
            return operations;
        }
    }
}

4. 图算法高级应用

面试问题:实现Dijkstra算法、检测图中的环、最小生成树算法?

图算法实现

public class GraphAlgorithms {

    // 图的表示
    public static class Graph {
        private final int vertices;
        private final List<List<Edge>> adjList;

        public Graph(int vertices) {
            this.vertices = vertices;
            this.adjList = new ArrayList<>();
            for (int i = 0; i < vertices; i++) {
                adjList.add(new ArrayList<>());
            }
        }

        public void addEdge(int from, int to, int weight) {
            adjList.get(from).add(new Edge(to, weight));
        }

        public void addUndirectedEdge(int u, int v, int weight) {
            adjList.get(u).add(new Edge(v, weight));
            adjList.get(v).add(new Edge(u, weight));
        }

        public List<Edge> getNeighbors(int vertex) {
            return adjList.get(vertex);
        }

        public int getVertices() {
            return vertices;
        }

        public static class Edge {
            public final int to;
            public final int weight;

            public Edge(int to, int weight) {
                this.to = to;
                this.weight = weight;
            }
        }
    }

    // Dijkstra最短路径算法
    public static class Dijkstra {

        public static class Result {
            public final int[] distances;
            public final int[] predecessors;

            public Result(int[] distances, int[] predecessors) {
                this.distances = distances;
                this.predecessors = predecessors;
            }

            public List<Integer> getPath(int target) {
                List<Integer> path = new ArrayList<>();
                int current = target;

                while (current != -1) {
                    path.add(current);
                    current = predecessors[current];
                }

                Collections.reverse(path);
                return path;
            }
        }

        public static Result shortestPath(Graph graph, int source) {
            int n = graph.getVertices();
            int[] distances = new int[n];
            int[] predecessors = new int[n];
            boolean[] visited = new boolean[n];

            Arrays.fill(distances, Integer.MAX_VALUE);
            Arrays.fill(predecessors, -1);
            distances[source] = 0;

            // 使用优先队列优化
            PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a.distance));
            pq.offer(new Node(source, 0));

            while (!pq.isEmpty()) {
                Node current = pq.poll();
                int u = current.vertex;

                if (visited[u]) continue;
                visited[u] = true;

                for (Graph.Edge edge : graph.getNeighbors(u)) {
                    int v = edge.to;
                    int weight = edge.weight;

                    if (!visited[v] && distances[u] + weight < distances[v]) {
                        distances[v] = distances[u] + weight;
                        predecessors[v] = u;
                        pq.offer(new Node(v, distances[v]));
                    }
                }
            }

            return new Result(distances, predecessors);
        }

        private static class Node {
            int vertex;
            int distance;

            Node(int vertex, int distance) {
                this.vertex = vertex;
                this.distance = distance;
            }
        }
    }

    // 环检测算法
    public static class CycleDetection {

        // 有向图环检测(DFS)
        public static boolean hasCycleDirected(Graph graph) {
            int n = graph.getVertices();
            int[] color = new int[n]; // 0: white, 1: gray, 2: black

            for (int i = 0; i < n; i++) {
                if (color[i] == 0 && dfsHasCycle(graph, i, color)) {
                    return true;
                }
            }

            return false;
        }

        private static boolean dfsHasCycle(Graph graph, int vertex, int[] color) {
            color[vertex] = 1; // 标记为灰色

            for (Graph.Edge edge : graph.getNeighbors(vertex)) {
                int neighbor = edge.to;

                if (color[neighbor] == 1) {
                    return true; // 找到后向边,存在环
                }

                if (color[neighbor] == 0 && dfsHasCycle(graph, neighbor, color)) {
                    return true;
                }
            }

            color[vertex] = 2; // 标记为黑色
            return false;
        }

        // 无向图环检测(并查集)
        public static boolean hasCycleUndirected(int n, int[][] edges) {
            UnionFind uf = new UnionFind(n);

            for (int[] edge : edges) {
                int u = edge[0];
                int v = edge[1];

                if (uf.find(u) == uf.find(v)) {
                    return true; // 两个顶点已在同一连通分量中
                }

                uf.union(u, v);
            }

            return false;
        }

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

            public UnionFind(int n) {
                parent = new int[n];
                rank = new int[n];
                for (int i = 0; i < n; 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[rootX] = rootY;
                    } else if (rank[rootX] > rank[rootY]) {
                        parent[rootY] = rootX;
                    } else {
                        parent[rootY] = rootX;
                        rank[rootX]++;
                    }
                }
            }
        }
    }

    // 最小生成树算法
    public static class MinimumSpanningTree {

        // Kruskal算法
        public static List<GraphEdge> kruskal(int n, List<GraphEdge> edges) {
            List<GraphEdge> mst = new ArrayList<>();

            // 按权重排序
            edges.sort(Comparator.comparingInt(a -> a.weight));

            CycleDetection.UnionFind uf = new CycleDetection.UnionFind(n);

            for (GraphEdge edge : edges) {
                if (uf.find(edge.from) != uf.find(edge.to)) {
                    uf.union(edge.from, edge.to);
                    mst.add(edge);

                    if (mst.size() == n - 1) {
                        break; // MST完成
                    }
                }
            }

            return mst;
        }

        // Prim算法
        public static List<GraphEdge> prim(Graph graph) {
            int n = graph.getVertices();
            List<GraphEdge> mst = new ArrayList<>();
            boolean[] inMST = new boolean[n];

            // 从顶点0开始
            inMST[0] = true;

            PriorityQueue<GraphEdge> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a.weight));

            // 添加顶点0的所有边
            for (Graph.Edge edge : graph.getNeighbors(0)) {
                pq.offer(new GraphEdge(0, edge.to, edge.weight));
            }

            while (!pq.isEmpty() && mst.size() < n - 1) {
                GraphEdge edge = pq.poll();

                if (inMST[edge.to]) {
                    continue; // 跳过已在MST中的顶点
                }

                mst.add(edge);
                inMST[edge.to] = true;

                // 添加新顶点的所有边
                for (Graph.Edge neighbor : graph.getNeighbors(edge.to)) {
                    if (!inMST[neighbor.to]) {
                        pq.offer(new GraphEdge(edge.to, neighbor.to, neighbor.weight));
                    }
                }
            }

            return mst;
        }

        public static class GraphEdge {
            public final int from;
            public final int to;
            public final int weight;

            public GraphEdge(int from, int to, int weight) {
                this.from = from;
                this.to = to;
                this.weight = weight;
            }

            @Override
            public String toString() {
                return "(" + from + "->" + to + ", " + weight + ")";
            }
        }
    }

    // 拓扑排序
    public static class TopologicalSort {

        public static List<Integer> topologicalSort(Graph graph) {
            int n = graph.getVertices();
            int[] indegree = new int[n];

            // 计算入度
            for (int i = 0; i < n; i++) {
                for (Graph.Edge edge : graph.getNeighbors(i)) {
                    indegree[edge.to]++;
                }
            }

            Queue<Integer> queue = new LinkedList<>();
            for (int i = 0; i < n; i++) {
                if (indegree[i] == 0) {
                    queue.offer(i);
                }
            }

            List<Integer> result = new ArrayList<>();

            while (!queue.isEmpty()) {
                int current = queue.poll();
                result.add(current);

                for (Graph.Edge edge : graph.getNeighbors(current)) {
                    indegree[edge.to]--;
                    if (indegree[edge.to] == 0) {
                        queue.offer(edge.to);
                    }
                }
            }

            // 检查是否存在环
            if (result.size() != n) {
                return null; // 存在环,无法进行拓扑排序
            }

            return result;
        }

        // DFS版本的拓扑排序
        public static List<Integer> topologicalSortDFS(Graph graph) {
            int n = graph.getVertices();
            boolean[] visited = new boolean[n];
            Stack<Integer> stack = new Stack<>();

            for (int i = 0; i < n; i++) {
                if (!visited[i]) {
                    dfsTopological(graph, i, visited, stack);
                }
            }

            List<Integer> result = new ArrayList<>();
            while (!stack.isEmpty()) {
                result.add(stack.pop());
            }

            return result;
        }

        private static void dfsTopological(Graph graph, int vertex, boolean[] visited, Stack<Integer> stack) {
            visited[vertex] = true;

            for (Graph.Edge edge : graph.getNeighbors(vertex)) {
                if (!visited[edge.to]) {
                    dfsTopological(graph, edge.to, visited, stack);
                }
            }

            stack.push(vertex);
        }
    }
}

高频面试题目

1. 理论深度题目

Q: 红黑树和AVL树的区别?什么时候选择哪种?

A: 主要区别:

  • 平衡性:AVL树严格平衡,红黑树近似平衡
  • 旋转次数:AVL树插入时最多2次旋转,红黑树最多3次
  • 查找性能:AVL树查找稍快,红黑树插入删除更快
  • 应用场景:AVL树适合查找密集,红黑树适合插入删除频繁

Q: 时间复杂度和空间复杂度如何分析?

A: 复杂度分析方法:

  • 时间复杂度:分析算法执行时间与输入规模的关系
  • 空间复杂度:分析算法使用空间与输入规模的关系
  • 最好/平均/最坏情况:全面分析算法性能
  • 摊还分析:分析一系列操作的平均代价

2. 实战应用题目

Q: 如何设计一个支持高并发的排行榜系统?

答题要点:

  1. 数据结构选择:跳表、红黑树、堆的组合使用
  2. 分片策略:按分数区间或用户ID分片
  3. 缓存设计:多级缓存,定时更新
  4. 一致性保证:最终一致性,异步更新
  5. 性能优化:批量操作,预计算

Q: 海量数据下如何实现TopK?

答题要点:

  1. 分治策略:Map-Reduce模式处理大数据
  2. 内存优化:外部排序,分块处理
  3. 近似算法:Count-Min Sketch,HyperLogLog
  4. 流式处理:滑动窗口,增量更新
  5. 分布式计算:多机协作,结果合并

总结

算法与数据结构面试重点:

  1. 基础数据结构:数组、链表、栈、队列、树、图的实现和应用
  2. 高级数据结构:红黑树、跳表、并发数据结构的原理
  3. 算法设计:分治、动态规划、贪心、回溯等算法思想
  4. 复杂度分析:时间空间复杂度的准确分析
  5. 实际应用:能够将算法应用到具体业务场景中

建议通过大量练习巩固基础,重点理解算法思想和应用场景,能够快速选择合适的算法解决实际问题。

powered by Gitbook© 2025 编外计划 | 最后修改: 2025-07-28 18:05:38

results matching ""

    No results matching ""