nowcoder-oj【面试高频TOP榜单-中等难度(2)5道】
1、NC88 寻找第K大
import java.util.*; public class Solution { public int findKth(int[] a, int n, int K) { // write code here } }
实现
import java.util.*; public class Solution { public int findKth(int[] a, int n, int K) { //set去重 HashSet<Integer> set = new HashSet<>(); for(int i=0; i<n; i++){ set.add(a[i]); } //去重后存入新的数组 int[] arr = new int[set.size()]; int j = 0; for(int num: set){ arr[j] = num; j++; } //对新数组进行快速排序(降序) quickSort(arr, 0, arr.length-1); //返回排序结果的第K大元素 return arr[K-1]; } public void quickSort(int[] array, int start, int end) { if (start < end) { int key = array[start];//用待排数组的第一个作为中枢 int i = start; for (int j=start+1; j<=end; j++) { if (key < array[j]) { // <降序,>升序 swap(array, j, ++i); } } array[start] = array[i];//先挪,然后再把中枢放到指定位置 array[i] = key; quickSort(array, start, i-1); quickSort(array, i+1, end); } } public void swap(int[] A, int i, int j) { if (i != j) { int temp = A[i]; A[i] = A[j]; A[j] = temp; // A[i] ^= A[j]; // A[j] ^= A[i]; // A[i] ^= A[j]; //这三行效果=上三行 } } }
参考
// 参考1:全局排序,时间复杂度取决于排序算法,一般是 O(n*lgn) import java.util.*; public class Solution { public int findKth(int[] a, int n, int K) { Arrays.sort(a); return a[n-K]; } }
// 参考2:局部排序,只排序TopK个数,O(n*k),冒泡、直接选择、直接插入都可以,但k的取值趋近n时,时间复杂度又趋近与O(n^2) import java.util.*; public class Solution { public int findKth(int[] a, int n, int K) { // 冒泡k次 for (int i = 0; i < K; i++) { for (int j = 0; j < n - 1 - i; j++) { if (a[j] > a[j + 1]) { int temp = a[j]; a[j] = a[j + 1]; a[j + 1] = temp; } } } return a[n - K]; } }
// 参考3:堆,TopK个数也不排序了,O(n*lg(k)),想练手的可以模仿堆排序手动维护小根堆。也可以使用最小优先队列 import java.util.*; public class Solution { public int findKth(int[] a, int n, int K) { // 占存 第k大的值 PriorityQueue<Integer> queue = new PriorityQueue<>(K); // n * 调整 lgk for (int num : a) { if (queue.isEmpty() || num > queue.peek()) { if (queue.size() >= K) { queue.poll(); } queue.add(num); } } return queue.isEmpty() ? 0 : queue.peek(); } //10/15 组用例通过 算法有点问题 }
// 参考4:快速排序的思想--随机选择法,时间复杂度 O(n) import java.util.*; public class Solution { public int findKth(int[] a, int n, int K) { return quickSort(a, 0, a.length - 1, K); } private int quickSort(int[] arr, int left, int right, int k){ int p = partition(arr, left, right); // 改进后,很特殊的是,p是全局下标,只要p对上topK坐标就可以返回 if (p == arr.length - k) { return arr[p]; }else if (p < arr.length - k) { // 如果基准在左边,这在右边找 return quickSort(arr, p + 1, right,k); }else { return quickSort(arr, left, p - 1,k); } } private int partition(int[] arr, int left, int right) { // 可优化成随机,或中位数 int key = arr[left]; while (left < right) { while (left < right && arr[right] >= key) right--; arr[left] = arr[right]; while (left < right && arr[left] <= key) left++; arr[right] = arr[left]; } arr[left] = key; return left; } }
Top K总结:寻找第k大元素_牛客博客 (nowcoder.net)
2、NC50 链表中的节点每k个一组翻转
import java.util.*; /* * public class ListNode { * int val; * ListNode next = null; * } */ public class Solution { /** * * @param head ListNode类 * @param k int整型 * @return ListNode类 */ public ListNode reverseKGroup (ListNode head, int k) { // write code here } }
实现
//未实现 import java.util.*; public class Solution { public ListNode reverseKGroup (ListNode head, int k) { if(head == null || head.next == null || k == 1){ return head; //判断链表为空或长度为1,或1个为一组的情况 } ListNode p = head; int len = 0; while(head != null){ len++; head = head.next; } if(len%k == 0){ }else{ } return p; } public ListNode reverseList(ListNode head) { ListNode pre = null; // 当前节点的前一个节点 ListNode next = null; // 当前节点的下一个节点 while(head != null){ next = head.next; // 记录当前节点的下一个节点位置; head.next = pre; // 让当前节点指向前一个节点位置,完成反转 pre = head; // pre 往右走 head = next;// 当前节点往右继续走 } return pre; } }
参考
【数据结构和算法】非递归和递归两种方式解决_牛客博客 (nowcoder.net)
//参考1:非递归 import java.util.*; public class Solution { public ListNode reverseKGroup(ListNode head, int k) { //先创建一个哑节点 ListNode dummy = new ListNode(0); //让哑节点的指针指向链表的头 dummy.next = head; //开始反转的前一个节点,比如反转的节点范围是[link1,link2], //那么pre就是link1的前一个节点 ListNode pre = dummy; ListNode end = dummy; while (end.next != null) { //每k个反转,end是每k个链表的最后一个 for (int i = 0; i < k && end != null; i++) end = end.next; //如果end是空,说明不够k个,就不需要反转了,直接退出循环。 if (end == null) break; //反转开始的节点 ListNode start = pre.next; //next是下一次反转的头结点,先把他记录下来 ListNode next = end.next; //因为end是这k个链表的最后一个结点,把它和原来链表断开, //这k个节点我们可以把他们看做一个小的链表,然后反转这个 //小链表 end.next = null; //因为pre是反转链表的前一个节点,我们把小链表[start,end] //反转之后,让pre的指针指向这个反转的小链表 pre.next = reverse(start); //注意经过上一步反转之后,start反转到链表的尾部了,就是已经 //反转之后的尾结点了,让他之前下一次反转的头结点即可(上面分析 //过,next就是下一次反转的头结点) start.next = next; //前面反转完了,要进入下一波了,pre和end都有重新赋值 pre = start; end = start; } return dummy.next; } //链表的反转 private ListNode reverse(ListNode head) { ListNode pre = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; curr.next = pre; pre = curr; curr = next; } return pre; } }
//参考2:递归 import java.util.*; public class Solution { public ListNode reverseKGroup(ListNode head, int k) { //边界条件判断 if (head == null || head.next == null) return head; ListNode tail = head; for (int i = 0; i < k; i++) { //剩余数量小于k的话,则不需要反转。 if (tail == null) return head; tail = tail.next; } // 反转前 k 个元素 ListNode newHead = reverse(head, tail); //下一轮的开始的地方就是tail head.next = reverseKGroup(tail, k); return newHead; } /* 链表的反转,不是反转全部,只反转区间[head,tail)中间的节点,左闭右开区间 */ private ListNode reverse(ListNode head, ListNode tail) { ListNode pre = null; ListNode next = null; while (head != tail) { next = head.next; head.next = pre; pre = head; head = next; } return pre; } }
3、NC41 最长无重复子数组
import java.util.*; public class Solution { /** * * @param arr int整型一维数组 the array * @return int整型 */ public int maxLength (int[] arr) { // write code here } }
实现
import java.util.*; public class Solution { public int maxLength (int[] arr) { if(arr.length == 0){ return -1; } if(arr.length == 1){ return 1; } Set<Integer> set = new HashSet<Integer>(); for(int num : arr){ set.add(num); } //通过set判断原数组中是否存在重复元素 if(set.size() == arr.length){ return arr.length; } //8/10 组用例通过 time out int maxLen = 1; for(int i=0; i<arr.length-1; i++){ ArrayList<Integer> list = new ArrayList<>(); list.add(arr[i]); for(int j=i+1; j<arr.length; j++){ if(!list.contains(arr[j])){ list.add(arr[j]); } if(list.size() > maxLen){ maxLen = list.size(); } } } return maxLen; } }
参考
【数据结构和算法】带图文分析的4种解法_牛客博客 (nowcoder.net)
图见参考链接
//解法1 public int maxLength(int[] arr) { if (arr.length == 0) return 0; HashMap<Integer, Integer> map = new HashMap<>(); int max = 0; for (int i = 0, j = 0; i < arr.length; ++i) { if (map.containsKey(arr[i])) { j = Math.max(j, map.get(arr[i]) + 1); } map.put(arr[i], i); max = Math.max(max, i - j + 1); } return max; }
//解法2 public int maxLength(int[] arr) { //用链表实现队列,队列是先进先出的 Queue<Integer> queue = new LinkedList<>(); int res = 0; for (int c : arr) { while (queue.contains(c)) { //如果有重复的,队头出队 queue.poll(); } //添加到队尾 queue.add(c); res = Math.max(res, queue.size()); } return res; }
//解法3 public int maxLength(int[] arr) { int maxLen = 0; Set<Integer> set = new HashSet<>(); int left = 0, right = 0; while (right < arr.length) { while (set.contains(arr[right])) set.remove(arr[left++]); set.add(arr[right++]); maxLen = Math.max(maxLen, right - left); } return maxLen; }
//解法4 public int maxLength(int[] arr) { int left = 0, right = 0, max = 0; Set<Integer> set = new HashSet<>(); while (right < arr.length) { if (set.contains(arr[right])) { set.remove(arr[left++]); } else { set.add(arr[right++]); max = Math.max(max, set.size()); } } return max; }
4、NC3 链表中环的入口结点
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } */ public class Solution { public ListNode EntryNodeOfLoop(ListNode pHead) { } }
参考
public class Solution { public ListNode EntryNodeOfLoop(ListNode pHead){ if(pHead == null || pHead.next == null){ return null; } ListNode fast = pHead; ListNode slow = pHead; while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(fast == slow){ ListNode slow2 = pHead; while(slow2 != slow){ slow2 = slow2.next; slow = slow.next; } return slow2; } } return null; } }
其他参考【数据结构和算法】NC3 链表中环的入口结点_牛客博客 (nowcoder.net)
5、NC53 删除链表的倒数第n个节点
import java.util.*; /* * public class ListNode { * int val; * ListNode next = null; * } */ public class Solution { /** * * @param head ListNode类 * @param n int整型 * @return ListNode类 */ public ListNode removeNthFromEnd (ListNode head, int n) { // write code here } }
实现
import java.util.*; public class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { // if(head == null){ // return head; // } // if(head.next == null && n == 1){ // return null; // } ListNode move = head; int len = 0; while(head != null){ len++; head = head.next; } if(len == n){ return move.next; } //链表节点从0开始编号,则被删除的节点的下标i=链表长度-n ListNode result = move; for(int i=0; i<len; i++){ if(i == len-n-1){ ListNode temp = move.next.next; move.next = temp; break; }else{ move = move.next; } } return result; } }
参考
【数据结构和算法】双指针,递归等3种解决方式_牛客博客 (nowcoder.net)
图见参考链接
public ListNode removeNthFromEnd(ListNode head, int n) { ListNode pre = head; int last = length(head) - n; //如果last等于0表示删除的是头结点 if (last == 0) return head.next; //这里首先要找到要删除链表的前一个结点 for (int i = 0; i < last - 1; i++) { pre = pre.next; } //然后让前一个结点的next指向要删除节点的next pre.next = pre.next.next; return head; } //求链表的长度 private int length(ListNode head) { int len = 0; while (head != null) { len++; head = head.next; } return len; }
public ListNode removeNthFromEnd(ListNode head, int n) { ListNode fast = head; ListNode slow = head; //fast移n步, for (int i = 0; i < n; i++) { fast = fast.next; } //如果fast为空,表示删除的是头结点 if (fast == null) return head.next; while (fast.next != null) { fast = fast.next; slow = slow.next; } //这里最终slow不是倒数第n个节点,他是倒数第n+1个节点, //他的下一个结点是倒数第n个节点,所以删除的是他的下一个结点 slow.next = slow.next.next; return head; }
public ListNode removeNthFromEnd(ListNode head, int n) { int pos = length(head, n); // 说明删除的是头节点 if (pos == n) return head.next; return head; } // 获取节点所在位置,逆序 public int length(ListNode node, int n) { if (node == null) return 0; int pos = length(node.next, n) + 1; //获取要删除链表的前一个结点,就可以完成链表的删除 if (pos == n + 1) node.next = node.next.next; return pos; }
标签:
Online Judge
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!