LeetCode.23 Merge k Sorted Lists (对数组链表进行合并,归并排序 && 或者使用PriorityQueue实现)

本文介绍了一种有效的算法来合并多个已排序的链表,并返回一个单一的排序链表。该方法采用分治策略,类似于归并排序,并通过递归实现。此外,还提供了一种使用优先队列的方法,该方法适用于Java 8及更高版本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

分析:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        //对多个有序的链表进行合并
        //思路:类似归并排序,先合将队列拆成两部分,两部分继续拆分。之后合并两个小的队列几个
        if(lists==null||lists.length==0) return null;
        if(lists.length==1) return lists[0];
        
        return mergeKListsCore(lists,0,lists.length-1);
        
    }
    public ListNode mergeKListsCore(ListNode [] list,int start,int end){
        if(start<end){
            int mid=(start+end)/2;
            ListNode left=mergeKListsCore(list,start,mid);
            ListNode right=mergeKListsCore(list,mid+1,end);
            return mergeTwoList(left,right);
        }else{
            return list[start];
        }
    }
    
    public ListNode mergeTwoList(ListNode head1,ListNode head2){
        
        //两个链表合并
        ListNode A=head1;
        ListNode B=head2;
        
        ListNode temp=new ListNode(0);
        ListNode cur=temp;
        while(A!=null&&B!=null){
            if(A.val<B.val){
                cur.next=A;
                A=A.next;
            }else{
                cur.next=B;
                B=B.next;
            }
            cur=cur.next;
        }
        
        //防止还有剩下的
        while(A!=null){
            cur.next=A;
            cur=cur.next;
            A=A.next;
        }
        while(B!=null){
            cur.next=B;
            cur=cur.next;
            B=B.next;
        }
        
        return temp.next;        
    }
    
}

PriorityQueue实现:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        //使用优先队列PrirorityQueue实现,修改其中的对比内部类compare
        //保证每次插入都是按从小插入
        
        if(lists==null||lists.length==0) return null;
        
//         //JAVA之前,使用匿名内部类。记得初始化队列长度
//         PriorityQueue<ListNode> pq=new PriorityQueue<>(lists.length,new Comparator<ListNode>(){
//             @Override
//             public int compare(ListNode l1,ListNode l2){
//                 //在添加元素的时候就进行规则排序。升序
//                 if(l1.bal>l2.val){
//                     return 1;
//                 }else if(l1.val==l2.val){
//                     return 0;
//                 }else{
//                     return -1;
//                 }
//             }
//         });
        
        //Java8后使用lambda表达式更加高效、简捷
        PriorityQueue <ListNode> pq=new PriorityQueue<ListNode>(lists.length,(a,b)->a.val-b.val);
        
        ListNode res=new ListNode(0);
        ListNode cur=res;
        for(ListNode node:lists){
            //进行非空判断,防止里面存在空的子数组
            if(node!=null){
                //插入的时候已经进行了排序,最小的元素放在最前面
                pq.add(node);
            }
        }
        
        while(!pq.isEmpty()){
            cur.next=pq.poll();
            cur=cur.next;
            if(cur.next!=null){
                //将后面的元素继续插入队列
                pq.add(cur.next);
            }
        }
        return res.next;
    }
}

<think>好的,我现在需要帮用户寻找高难度的Java算法题,用于面试和练习。首先,我得回忆一下用户之前的请求,他提到过想要高难度的题目,并且可能参考了之前的引用内容,比如引用[2]中的素数判断,引用[3]中的小红书面试题。不过用户现在需要更高难度的,所以得想想哪些算法题在Java中比较有挑战性。首先,高难度算法题通常涉及复杂的数据结构、动态规划、图论或者需要优化的算法。比如动态规划中的背包问题变种,图论中的最短路径或网络流问题,或者树结构的高级操作。另外,一些需要巧妙思维的问题,比如滑动窗口、贪心算法的高级应用,也可能是高难度的。然后,我需要考虑用户可能的水平。用户已经刷过一些题,但需要更难的,所以应该推荐LeetCode Hard级别的题目。同时,结合Java语言特性,比如多线程、集合框架的高效使用,可能也是考察点之一。接下来,我得整理一些典型的高难度题目。比如:1.**最长有效括号**:这个问题需要动态规划或栈结构来解决,LeetCode第32题,属于Hard级别。 2. **合并K个升序链表**:分治或优先队列的应用,LeetCode23题,Hard。3.**编辑距离**:动态规划经典问题,计算两个字符串的最小编辑次数,LeetCode第72题,Hard。4.**滑动窗口最大值**:使用双向队列优化,LeetCode239题,Hard。 5. **正则表达式匹配**:递归或动态规划处理复杂模式匹配,LeetCode第10题,Hard。6.**LFU缓存**:结合哈希表和双向链表实现,要求O(1)时间复杂度,LeetCode第460题,Hard。7.**寻找两个正序数的中位数**:二分查找的巧妙应用,LeetCode第4题,Hard。 8. **接雨水**:数处理的双指针或动态规划方法,LeetCode第42题,Hard。9.**数据流的中位数**:维护两个堆(优先队列)来动态计算中位数,LeetCode第295题,Hard。 10. **二叉树中的最大路径和**:递归结合全局变量处理路径问题,LeetCode第124题,Hard。 然后,我需要为每个问题提供简要分析和代码示例,确保代码符合Java语法,并展示算法的高效性。例如,合并K个链表可以使用优先队列,时间复杂度为O(Nlog K),代码结构要清晰。同时,要确保回答结构清晰,分点列出题目,每个题目有分析、解决思路和代码示例。用户可能需要理解解题思路而不仅仅是代码,所以需要简要解释算法原理。 最后,生成相关问题,帮助用户进一步深入,比如动态规划的优化方法,或者如何测试这些高难度算法题。还要注意引用之前的参考资料,比如引用[3]提到的在线白板编码的注意事项,提醒用户练习时注意调试和思路表达。</think>### 高难度 Java 算法题推荐(附解决思路与代码) #### 1. **最长有效括号(Longest Valid Parentheses)** **问题**:给定一个只包含 `'('` 和 `')'` 的字符串,找出最长有效(格式正确且连续)括号子串的长度。 **解决思路**: - 动态规划:定义 `dp[i]` 表示以第 `i` 个字符结尾的最长有效括号长度。若 `s[i]=')'` 且 `s[i-1]='('`,则 `dp[i] = dp[i-2] + 2`;若 `s[i-dp[i-1]-1]='('`,则 `dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2]`[^1]。 - 栈:维护栈记录括号索引,遇到 `')'` 时弹出栈顶并更新最大长度。 **代码示例**: ```java public int longestValidParentheses(String s) { Stack<Integer> stack = new Stack<>(); stack.push(-1); int maxLen = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { stack.push(i); } else { stack.pop(); if (stack.isEmpty()) { stack.push(i); } else { maxLen = Math.max(maxLen, i - stack.peek()); } } } return maxLen; } ``` --- #### 2. **合并K个升序链表Merge k Sorted Lists)** **问题**:合并 `k` 个有序链表为一个整体有序链表。 **解决思路**: - **优先队列**:将每个链表头节点加入最小堆,每次取出堆顶节点并追加到结果链表,时间复杂度 $O(n \log k)$[^2]。 **代码示例**: ```java public ListNode mergeKLists(ListNode[] lists) { PriorityQueue<ListNode> heap = new PriorityQueue<>((a, b) -> a.val - b.val); for (ListNode node : lists) { if (node != null) heap.offer(node); } ListNode dummy = new ListNode(0); ListNode curr = dummy; while (!heap.isEmpty()) { ListNode minNode = heap.poll(); curr.next = minNode; curr = curr.next; if (minNode.next != null) heap.offer(minNode.next); } return dummy.next; } ``` --- #### 3. **编辑距离(Edit Distance)** **问题**:计算将字符串 `word1` 转换为 `word2` 所需的最小操作次数(插入、删除、替换)。 **解决思路**: - **动态规划**:定义 `dp[i][j]` 为 `word1` 前 `i` 个字符转换为 `word2` 前 `j` 个字符的最小操作数。状态转移方程: $$ dp[i][j] = \begin{cases} dp[i-1][j-1] & \text{if } word1[i-1] = word2[j-1] \\ 1 + \min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) & \text{otherwise} \end{cases} $$ **代码示例**: ```java public int minDistance(String word1, String word2) { int m = word1.length(), n = word2.length(); int[][] dp = new int[m+1][n+1]; 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] = 1 + Math.min(dp[i-1][j], Math.min(dp[i][j-1], dp[i-1][j-1])); } } } return dp[m][n]; } ``` --- #### 4. **LFU缓存(Least Frequently Used Cache)** **问题**:设计一个数据结构,支持 `get` 和 `put` 操作,当容量满时淘汰最不经常使用的项(使用频率最低,同频率则淘汰最久未使用的)。 **解决思路**: - **双哈希表 + 双向链表**:一个哈希表记录键到频率的映射,另一个按频率分层存储双向链表。每次访问更新频率并调整链表位置[^3]。 **代码示例**(简化版): ```java class LFUCache { private int capacity; private Map<Integer, Integer> keyToVal; private Map<Integer, Integer> keyToFreq; private Map<Integer, LinkedHashSet<Integer>> freqToKeys; private int minFreq; public LFUCache(int capacity) { this.capacity = capacity; keyToVal = new HashMap<>(); keyToFreq = new HashMap<>(); freqToKeys = new HashMap<>(); } public int get(int key) { if (!keyToVal.containsKey(key)) return -1; increaseFreq(key); return keyToVal.get(key); } public void put(int key, int value) { if (capacity == 0) return; if (keyToVal.containsKey(key)) { keyToVal.put(key, value); increaseFreq(key); return; } if (keyToVal.size() >= capacity) { removeMinFreqKey(); } keyToVal.put(key, value); keyToFreq.put(key, 1); freqToKeys.putIfAbsent(1, new LinkedHashSet<>()); freqToKeys.get(1).add(key); minFreq = 1; } private void increaseFreq(int key) { int freq = keyToFreq.get(key); keyToFreq.put(key, freq + 1); freqToKeys.get(freq).remove(key); if (freqToKeys.get(freq).isEmpty()) { freqToKeys.remove(freq); if (freq == minFreq) minFreq++; } freqToKeys.putIfAbsent(freq + 1, new LinkedHashSet<>()); freqToKeys.get(freq + 1).add(key); } private void removeMinFreqKey() { LinkedHashSet<Integer> keys = freqToKeys.get(minFreq); int oldestKey = keys.iterator().next(); keys.remove(oldestKey); if (keys.isEmpty()) freqToKeys.remove(minFreq); keyToVal.remove(oldestKey); keyToFreq.remove(oldestKey); } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值