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;
}