深圳幻海软件技术有限公司 欢迎您!

【刷题笔记】之牛客面试必刷TOP101(1)

2023-06-29

目录1.反转链表(双链表头插法/栈)2.链表内指定区间反转3.链表中的节点每k个一组翻转4.合并两个排序的链表5.合并k个已排序的链表 链接:牛客面试必刷TOP1011.反转链表(双链表头插法/栈)题目链接 反转链表_牛客题霸_牛客网(nowcoder.com)题目要求题目分析(

目录

1. 反转链表(双链表头插法 / 栈)

2.链表内指定区间反转

3. 链表中的节点每k个一组翻转

4. 合并两个排序的链表

5. 合并k个已排序的链表 


链接 :牛客面试必刷TOP101

1. 反转链表(双链表头插法 / 栈)

题目链接 反转链表_牛客题霸_牛客网 (nowcoder.com)

题目要求

题目分析(新建链表头插法)

  1. public class Solution {
  2. public ListNode ReverseList(ListNode head) {
  3. ListNode newHead = null;
  4. while(head != null) {
  5. ListNode tmp = head.next;
  6. //使用头插法
  7. head.next = newHead;
  8. newHead = head;
  9. head = tmp;
  10. }
  11. return newHead;
  12. }
  13. }

题目分析(栈)

  1. import java.util.Stack;
  2. public class Solution {
  3. public ListNode ReverseList(ListNode head) {
  4. Stack<ListNode> stack = new Stack<>();
  5. //入栈
  6. while(head != null) {
  7. stack.push(head);
  8. head = head.next;
  9. }
  10. if(stack.isEmpty()) {
  11. return null;
  12. }
  13. //出栈
  14. ListNode node = stack.pop();
  15. ListNode newHead = node;
  16. while(!stack.isEmpty()) {
  17. ListNode tmpNode = stack.pop();
  18. //建链表
  19. node.next = tmpNode;
  20. node = node.next;
  21. }
  22. //走到这里栈空 新链表建成
  23. node.next = null;
  24. return newHead;
  25. }
  26. }

2.链表内指定区间反转

题目链接 链表内指定区间反转_牛客题霸_牛客网 (nowcoder.com)

题目要求

 题目分析

上代码(保存结点位置,切断链表,反转链表,再连接)

  1. public class Solution {
  2. public ListNode reverseBetween (ListNode head, int m, int n) {
  3. //1.建立虚拟头结点
  4. ListNode temp = new ListNode(-1);
  5. temp.next = head;
  6. //2.记录n的前一个节点
  7. ListNode pro = temp;
  8. for(int i = 0; i < m-1; i++) {
  9. pro = pro.next;
  10. }
  11. //3.找到n位置下的结点
  12. ListNode nNode = pro;
  13. for(int i = 0; i < n-m+1; i++) {
  14. nNode = nNode.next;
  15. }
  16. //4.记录m位置下的结点,和n后下一个结点的位置
  17. ListNode mNode = pro.next;
  18. ListNode cur = nNode.next;
  19. //5.断开m到n之间的连接
  20. pro.next = null;
  21. nNode.next = null;
  22. //6.反转m到n之间的链表
  23. reverseList(mNode);
  24. //7.连接反转后的子链表
  25. pro.next = nNode;
  26. mNode.next = cur;
  27. return temp.next;
  28. }
  29. //反转链表
  30. public ListNode reverseList(ListNode head){
  31. //取一个新的头结点
  32. ListNode newHead = null;
  33. while(head != null) {
  34. ListNode cur = head.next;
  35. //头插法
  36. head.next = newHead;
  37. newHead = head;
  38. head = cur;
  39. }
  40. return newHead;
  41. }
  42. }

上代码(穿针引线)

  1. import java.util.*;
  2. public class Solution {
  3. //3.穿针引线
  4. public ListNode reverseBetween (ListNode head, int m, int n) {
  5. //1.设置虚拟头结点
  6. ListNode dummyNode = new ListNode(-1);
  7. dummyNode.next = head;
  8. //2.记录m前的一个节点
  9. ListNode pro = dummyNode;
  10. for(int i = 0; i < m-1; i++) {
  11. pro = pro.next;
  12. }
  13. //3.记录待反转的第一个结点和这个节点的下一个节点的位置
  14. ListNode cur = pro.next;
  15. //4.开始穿针引线
  16. for(int i = 0; i < n-m; i++) {
  17. ListNode next = cur.next;
  18. cur.next = next.next;
  19. next.next = pro.next;
  20. pro.next = next;
  21. }
  22. return dummyNode.next;
  23. }
  24. }

3. 链表中的节点每k个一组翻转

题目链接 链表中的节点每k个一组翻转_牛客题霸_牛客网 (nowcoder.com)

题目要求 

题目分析

(1)直接暴力解法,根据K给链表分组,然后子链表反转,再重新连接子链表

上代码

  1. public class Solution {
  2. /**
  3. *
  4. * @param head ListNode类
  5. * @param k int整型
  6. * @return ListNode类
  7. */
  8. public ListNode reverseKGroup (ListNode head, int k) {
  9. //创建虚拟结点
  10. ListNode dummy = new ListNode(0);
  11. dummy.next = head;
  12. //创建两个节点,分别表示每个分组的前一个和后一个节点
  13. ListNode pre = dummy;
  14. ListNode end = dummy;
  15. while(end.next != null) {
  16. //每k个一组反转,【pre,end】
  17. for(int i = 0; i < k & end != null; i++) {
  18. end = end.next;
  19. }
  20. //如果end为空,说明不满一组
  21. if(end == null) {
  22. break;
  23. }
  24. //记录每组的头结点,和下一组的头结点
  25. ListNode start = pre.next;
  26. ListNode next = end.next;
  27. //断开连接,进行分组反转
  28. end.next = null;
  29. pre.next = reverseList(start);
  30. //反转之后重新连接
  31. start.next = next;
  32. //重新给pre和end赋值
  33. pre = start;
  34. end = start;
  35. }
  36. return dummy.next;
  37. }
  38. //反转链表
  39. private ListNode reverseList(ListNode head){
  40. ListNode newHead = null;
  41. while(head != null) {
  42. ListNode temp = head.next;
  43. head.next = newHead;
  44. newHead = head;
  45. head = temp;
  46. }
  47. return newHead;
  48. }
  49. }

(2)也可以使用栈来做

创建栈  先入栈,每次判断是否够k个节点,如果够,就出栈到新的链表中。如果不够就返回

每次要把第k+1个结点记住,因为后面要把每次出完栈的子链表连接在一起

  1. public ListNode reverseKGroup (ListNode head, int k) {
  2. if(head == null) {
  3. return head;
  4. }
  5. //1.创建栈,创建虚拟头结点
  6. Stack<ListNode> stack = new Stack<>();
  7. ListNode dummy = new ListNode(-1);
  8. dummy.next = head;
  9. ListNode pre = dummy;
  10. int n = k;
  11. while(pre != null && pre.next != null) {
  12. ListNode temp = pre.next;
  13. //2.入栈
  14. while(temp != null && n > 0) {
  15. stack.add(temp);
  16. temp = temp.next;
  17. n--;
  18. }
  19. //3.记录第k+1个结点,用作连接
  20. ListNode nextNode = stack.peek().next;
  21. //4.n=0,说明栈中刚好k个结点,出栈连接
  22. if(n == 0) {
  23. while(!stack.empty()) {
  24. pre.next = stack.pop();
  25. pre = pre.next;
  26. }
  27. }else {
  28. break;
  29. }
  30. //5.连接
  31. pre.next = nextNode;
  32. n = k;
  33. }
  34. return dummy.next;
  35. }

4. 合并两个排序的链表

题目链接  合并两个排序的链表_牛客题霸_牛客网 (nowcoder.com)

题目要求 

题目分析

(1)直接迭代

1.创建一个虚拟头结点,然后再来一个节点prev指向虚拟头结点

2.两个链表头结点先比较,小的结点,放在prev后面,然后这条链表节点向后走一位

3.继续重复比较,每放一个节点prev向后走一位

4.直到某一条链表为null,剩下的这个链表直接连在prev后面

上代码

  1. public ListNode Merge(ListNode list1,ListNode list2) {
  2. ListNode newHead = new ListNode(-1);
  3. ListNode prev = newHead;
  4. while(list1 != null && list2 != null) {
  5. if(list1.val <= list2.val) {
  6. prev.next = list1;
  7. list1 = list1.next;
  8. } else {
  9. prev.next = list2;
  10. list2 = list2.next;
  11. }
  12. prev = prev.next;
  13. }
  14. //合并之后,谁还有剩余的就连在prev的后面
  15. prev.next = list1 == null ? list2:list1;
  16. return newHead.next;
  17. }

方法二:递归   先分一下

情况1.两条链表一条为空,另一条不为空,直接返回不为null的链表

情况2.刚开始两条链表头结点比较,小的结点1的那条链表,就可以想象成确定了的(也可以想象成这个节点就是结果中的结点),然后继续往下递归比较,直到出现情况1的条件

  1. public ListNode Merge(ListNode list1,ListNode list2) {
  2. if(list1 == null) {
  3. return list2;
  4. }else if(list2 == null) {
  5. return list1;
  6. }else if(list1.val < list2.val) {
  7. list1.next = Merge(list1.next,list2);
  8. return list1;
  9. }else {
  10. list2.next = Merge(list1,list2.next);
  11. return list2;
  12. }
  13. }

5. 合并k个已排序的链表 

题目链接   合并k个已排序的链表_牛客题霸_牛客网 (nowcoder.com)

题目要求

题目分析

(1)最暴力直接的解法就是,两个两个合并

既然已经给了K个已经排好序的链表,那么for循环,让每两个链表合并之后,就和下一个合并

两个链表合并,上一个题已经写过了,可以迭代也可以递归

但是这样两个合并的方法,可能会超时(如果链表特别多的情况下),不建议使用

上代码

  1. public ListNode mergeKLists(ArrayList<ListNode> lists) {
  2. ListNode ans = null;
  3. for(int i = 0; i < lists.size(); i++) {
  4. ans = mergeTwoList(ans,lists.get(i));
  5. }
  6. return ans;
  7. }
  8. private ListNode mergeTwoList(ListNode l1,ListNode l2){
  9. if(l1 == null || l2 == null) {
  10. return l1 == null ? l2:l1;
  11. }
  12. ListNode newHead = new ListNode(0);
  13. ListNode tmp = newHead;
  14. while(l1 != null && l2 != null) {
  15. if(l1.val <= l2.val) {
  16. tmp.next = l1;
  17. l1 = l1.next;
  18. }else {
  19. tmp.next = l2;
  20. l2 = l2.next;
  21. }
  22. tmp = tmp.next;
  23. }
  24. tmp.next = (l1 == null) ? l2:l1;
  25. return newHead.next;
  26. }

(2)分治(归并排序)

题中给的是ArrayList类型的链表数组

定义left和right分别指向这个链表数组的左右两边进行分组,往下递归直到left==right

然后开始合并,这就和上一题一样了

  1. public class Solution {
  2. //合并
  3. private ListNode Merge (ListNode list1,ListNode list2) {
  4. if(list1 == null) {
  5. return list2;
  6. }
  7. if(list2 == null) {
  8. return list1;
  9. }
  10. ListNode newHead = new ListNode(0);
  11. ListNode cur = newHead;
  12. //两个链表都不为null
  13. while(list1 != null && list2 != null) {
  14. //取出较小值的结点
  15. if(list1.val <= list2.val) {
  16. cur.next = list1;
  17. list1 = list1.next;
  18. }else {
  19. cur.next = list2;
  20. list2 = list2.next;
  21. }
  22. cur = cur.next;
  23. }
  24. cur.next = (list1 == null) ? list2 : list1;
  25. return newHead.next;
  26. }
  27. //划分区间
  28. private ListNode divideMerge(ArrayList<ListNode> lists,int left, int right) {
  29. if(left > right){
  30. return null;
  31. }else if(left == right) {
  32. return lists.get(left);
  33. }
  34. int mid = left + (right-1)/2;
  35. return Merge(divideMerge(lists,left,mid),divideMerge(lists,mid+1,right));
  36. }
  37. public ListNode mergeKLists(ArrayList<ListNode> lists) {
  38. //k个链表归并排序
  39. return divideMerge(lists,0,lists.size()-1);
  40. }
  41. }

(3)使用优先级队列,本质上就是堆,内置的是小根堆

1.创建一个优先级队列,并且还要重载比较方法,构造一个比较链表结点值的小根堆

2.然后遍历每个链表的头,将不为null的结点加入的优先级队列中

3.加一个虚拟头结点,将每次从堆顶取出来的元素,加入到虚拟头结点的后面

4.每次从堆顶取出一个元素后,就要给堆中重新放入刚刚取出来的结点的下一个结点。并且还要判空

  1. //使用优先级队列
  2. public ListNode mergeKLists(ArrayList<ListNode> lists) {
  3. Queue<ListNode> pq = new PriorityQueue<>(
  4. (v1,v2) -> v1.val - v2.val);
  5. for(int i = 0; i < lists.size(); i++) {
  6. //不为null,就加入小根堆
  7. if(lists.get(i) != null) {
  8. pq.add(lists.get(i));
  9. }
  10. }
  11. //加一个虚拟头结点
  12. ListNode newHead = new ListNode(-1);
  13. ListNode cur = newHead;
  14. while(!pq.isEmpty()){
  15. //小根堆,取出就是最小的元素
  16. ListNode temp = pq.poll();
  17. //连接
  18. cur.next = temp;
  19. cur = cur.next;
  20. //每次取出堆顶元素后,再放入链表的下一个元素进入小根堆
  21. if(temp.next != null) {
  22. pq.add(temp.next);
  23. }
  24. }
  25. return newHead.next;
  26. }

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览48554 人正在系统学习中