1. package LinkedListSummary;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Stack;
  5.  
  6. /**
  7. * http://blog.csdn.net/luckyxiaoqiang/article/details/7393134 轻松搞定面试中的链表题目
  8. * http://www.cnblogs.com/jax/archive/2009/12/11/1621504.html 算法大全(1)单链表
  9. *
  10. * 目录:
  11. * 1. 求单链表中结点的个数: getListLength
  12. * 2. 将单链表反转: reverseList(遍历),reverseListRec(递归)
  13. * 3. 查找单链表中的倒数第K个结点(k > 0): reGetKthNode
  14. * 4. 查找单链表的中间结点: getMiddleNode
  15. * 5. 从尾到头打印单链表: reversePrintListStack,reversePrintListRec(递归)
  16. * 6. 已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序: mergeSortedList, mergeSortedListRec
  17. * 7. 判断一个单链表中是否有环: hasCycle
  18. * 8. 判断两个单链表是否相交: isIntersect
  19. * 9. 求两个单链表相交的第一个节点: getFirstCommonNode
  20. * 10. 已知一个单链表中存在环,求进入环中的第一个节点: getFirstNodeInCycle, getFirstNodeInCycleHashMap
  21. * 11. 给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted: delete
  22. *
  23. */
  24. public class Demo {
  25.  
  26. public static void main(String[] args) {
  27. Node n1 = new Node(1);
  28. Node n2 = new Node(2);
  29. Node n3 = new Node(3);
  30. Node n4 = new Node(4);
  31. Node n5 = new Node(5);
  32. n1.next = n2;
  33. n2.next = n3;
  34. n3.next = n4;
  35. n4.next = n5;
  36.  
  37. printList(n1);
  38. // System.out.println(getListLength(n1));
  39. // Node head = reverseList(n1);
  40. // Node head = reverseListRec(n1);
  41. // printList(head);
  42.  
  43. Node x = reGetKthNode(n1, 2);
  44. System.out.println(x.val);
  45. reGetKthNodeRec(n1, 2);
  46.  
  47. // x = getMiddleNode(head);
  48. // System.out.println(x.val);
  49. // System.out.println("reversePrintListStack:");
  50. // reversePrintListStack(head);
  51. // System.out.println("reversePrintListRec:");
  52. // reversePrintListRec(head);
  53.  
  54. }
  55.  
  56. // public static void main(String[] args) {
  57. // Node n1 = new Node(1);
  58. // Node n2 = new Node(3);
  59. // Node n3 = new Node(5);
  60. // n1.next = n2;
  61. // n2.next = n3;
  62. //
  63. // Node m1 = new Node(1);
  64. // Node m2 = new Node(4);
  65. // Node m3 = new Node(6);
  66. // m1.next = m2;
  67. // m2.next = m3;
  68. //
  69. //
  70. // Node ret = mergeSortedList(n1, m1);
  71. // printList(ret);
  72. // }
  73.  
  74. private static class Node {
  75. int val;
  76. Node next;
  77.  
  78. public Node(int val) {
  79. this.val = val;
  80. }
  81. }
  82.  
  83. public static void printList(Node head) {
  84. while (head != null) {
  85. System.out.print(head.val + " ");
  86. head = head.next;
  87. }
  88. System.out.println();
  89. }
  90.  
  91. // 求单链表中结点的个数
  92. // 注意检查链表是否为空。时间复杂度为O(n)
  93. public static int getListLength(Node head) {
  94. // 注意头结点为空情况
  95. if (head == null) {
  96. return 0;
  97. }
  98.  
  99. int len = 0;
  100. Node cur = head;
  101. while (cur != null) {
  102. len++;
  103. cur = cur.next;
  104. }
  105. return len;
  106. }
  107.  
  108. // 翻转链表(遍历)
  109. // 从头到尾遍历原链表,每遍历一个结点,
  110. // 将其摘下放在新链表的最前端。
  111. // 注意链表为空和只有一个结点的情况。时间复杂度为O(n)
  112. public static Node reverseList(Node head) {
  113. // 如果链表为空或只有一个节点,无需反转,直接返回原链表表头
  114. if (head == null || head.next == null) {
  115. return head;
  116. }
  117.  
  118. Node reHead = null; // 反转后新链表指针
  119. Node cur = head;
  120.  
  121. while (cur != null) {
  122. Node preCur = cur; // 用preCur保存住对要处理节点的引用
  123. cur = cur.next; // cur更新到下一个节点
  124. preCur.next = reHead; // 更新要处理节点的next引用
  125. reHead = preCur; // reHead指向要处理节点的前一个节点
  126. }
  127.  
  128. return reHead;
  129. }
  130.  
  131. // 翻转递归(递归)
  132. // 递归的精髓在于你就默认reverseListRec已经成功帮你解决了子问题了!但别去想如何解决的
  133. // 现在只要处理当前node和子问题之间的关系。最后就能圆满解决整个问题。
  134. /*
  135. head
  136. 1 -> 2 -> 3 -> 4
  137.  
  138. head
  139. 1--------------
  140. |
  141. 4 -> 3 -> 2 // Node reHead = reverseListRec(head.next);
  142. reHead head.next
  143.  
  144. 4 -> 3 -> 2 -> 1 // head.next.next = head;
  145. reHead
  146.  
  147. 4 -> 3 -> 2 -> 1 -> null // head.next = null;
  148. reHead
  149. */
  150. public static Node reverseListRec(Node head){
  151. if(head == null || head.next == null){
  152. return head;
  153. }
  154.  
  155. Node reHead = reverseListRec(head.next);
  156. head.next.next = head; // 把head接在reHead串的最后一个后面
  157. head.next = null; // 防止循环链表
  158. return reHead;
  159. }
  160.  
  161. /**
  162. * 查找单链表中的倒数第K个结点(k > 0)
  163. * 最普遍的方法是,先统计单链表中结点的个数,然后再找到第(n-k)个结点。注意链表为空,k为0,k为1,k大于链表中节点个数时的情况
  164. * 。时间复杂度为O(n)。代码略。 这里主要讲一下另一个思路,这种思路在其他题目中也会有应用。
  165. * 主要思路就是使用两个指针,先让前面的指针走到正向第k个结点
  166. * ,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点
  167. */
  168. public static Node reGetKthNode(Node head, int k) {
  169. // 这里k的计数是从1开始,若k为0或链表为空返回null
  170. if (k == 0 || head == null) {
  171. return null;
  172. }
  173.  
  174. Node q = head; // q在p前面 p--q
  175. Node p = head; // p在q后面
  176.  
  177. // 让q领先p距离k
  178. while (k > 1 && q != null) {
  179. q = q.next;
  180. k--;
  181. }
  182.  
  183. // 当节点数小于k,返回null
  184. if (k > 1 || q == null) {
  185. return null;
  186. }
  187.  
  188. // 前后两个指针一起走,直到前面的指针指向最后一个节点
  189. while (q.next != null) {
  190. p = p.next;
  191. q = q.next;
  192. }
  193.  
  194. // 当前面的指针指向最后一个节点时,后面的指针指向倒数k个节点
  195. return p;
  196. }
  197.  
  198. /**
  199. * 递归打印出倒数第k位的值
  200. * @param head
  201. * @param dist
  202. */
  203. static int level = 0;
  204. public static void reGetKthNodeRec(Node head, int k) {
  205.  
  206. if(head == null){
  207. return;
  208. }
  209. if(k == 1){
  210. return;
  211. }
  212.  
  213. reGetKthNodeRec(head.next, k);
  214. level++;
  215. if(level == k) {
  216. System.out.println(head.val);
  217. }
  218. }
  219.  
  220. // 查找单链表的中间结点
  221. /**
  222. * 此题可应用于上一题类似的思想。也是设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,
  223. * 前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点,即第(n/2+1)个结点。注意链表为空,链表结点个数为1和2的情况。时间复杂度O(n
  224. */
  225. public static Node getMiddleNode(Node head) {
  226. if (head == null || head.next == null) {
  227. return head;
  228. }
  229.  
  230. Node q = head; // p---q
  231. Node p = head;
  232.  
  233. // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步
  234. while (q.next != null) {
  235. q = q.next;
  236. p = p.next;
  237. if (q.next != null) {
  238. q = q.next;
  239. }
  240. }
  241. return p;
  242. }
  243.  
  244. /**
  245. * 从尾到头打印单链表
  246. * 对于这种颠倒顺序的问题,我们应该就会想到栈,后进先出。所以,这一题要么自己使用栈,要么让系统使用栈,也就是递归。注意链表为空的情况
  247. * 。时间复杂度为O(n)
  248. */
  249. public static void reversePrintListStack(Node head) {
  250. Stack<Node> s = new Stack<Node>();
  251. Node cur = head;
  252. while (cur != null) {
  253. s.push(cur);
  254. cur = cur.next;
  255. }
  256.  
  257. while (!s.empty()) {
  258. cur = s.pop();
  259. System.out.print(cur.val + " ");
  260. }
  261. System.out.println();
  262. }
  263.  
  264. /**
  265. * 从尾到头打印链表,使用递归(优雅!)
  266. */
  267. public static void reversePrintListRec(Node head) {
  268. if (head == null) {
  269. return;
  270. } else {
  271. reversePrintListRec(head.next);
  272. System.out.print(head.val + " ");
  273. }
  274. }
  275.  
  276. /**
  277. * 已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
  278. * 这个类似归并排序。尤其注意两个链表都为空,和其中一个为空时的情况。只需要O(1)的空间。时间复杂度为O(max(len1, len2))
  279. */
  280. public static Node mergeSortedList(Node head1, Node head2) {
  281. // 其中一个链表为空的情况,直接返回另一个链表头,O(1)
  282. if (head1 == null) {
  283. return head2;
  284. }
  285. if (head2 == null) {
  286. return head1;
  287. }
  288.  
  289. Node mergeHead = null;
  290. // 先确定下来mergeHead是在哪里
  291. if (head1.val < head2.val) {
  292. mergeHead = head1;
  293. head1 = head1.next; // 跳过已经合并了的元素
  294. mergeHead.next = null; // 断开mergeHead和后面的联系
  295. } else {
  296. mergeHead = head2;
  297. head2 = head2.next;
  298. mergeHead.next = null;
  299. }
  300.  
  301. Node mergeCur = mergeHead;
  302. while (head1 != null && head2 != null) {
  303. if (head1.val < head2.val) {
  304. mergeCur.next = head1; // 把找到较小的元素合并到merge中
  305. head1 = head1.next; // 跳过已经合并了的元素
  306. mergeCur = mergeCur.next; // 找到下一个准备合并的元素
  307. mergeCur.next = null; // 断开mergeCur和后面的联系
  308. } else {
  309. mergeCur.next = head2;
  310. head2 = head2.next;
  311. mergeCur = mergeCur.next;
  312. mergeCur.next = null;
  313. }
  314. }
  315.  
  316. // 合并剩余的元素
  317. if (head1 != null) {
  318. mergeCur.next = head1;
  319. } else if (head2 != null) {
  320. mergeCur.next = head2;
  321. }
  322.  
  323. return mergeHead;
  324. }
  325.  
  326. /**
  327. * 递归合并两链表(优雅!)
  328. */
  329. public static Node mergeSortedListRec(Node head1, Node head2) {
  330. if (head1 == null) {
  331. return head2;
  332. }
  333. if (head2 == null) {
  334. return head1;
  335. }
  336.  
  337. Node mergeHead = null;
  338. if (head1.val < head2.val) {
  339. mergeHead = head1;
  340. // 连接已解决的子问题
  341. mergeHead.next = mergeSortedListRec(head1.next, head2);
  342. } else {
  343. mergeHead = head2;
  344. mergeHead.next = mergeSortedListRec(head1, head2.next);
  345. }
  346. return mergeHead;
  347. }
  348.  
  349. /**
  350. * 判断一个单链表中是否有环
  351. * 这里也是用到两个指针。如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步
  352. * ,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。时间复杂度为O(n)
  353. */
  354. public static boolean hasCycle(Node head) {
  355. Node fast = head; // 快指针每次前进两步
  356. Node slow = head; // 慢指针每次前进一步
  357.  
  358. while (fast != null && fast.next != null) {
  359. fast = fast.next.next;
  360. slow = slow.next;
  361. if (fast == slow) { // 相遇,存在环
  362. return true;
  363. }
  364. }
  365. return false;
  366. }
  367.  
  368. // 判断两个单链表是否相交
  369. /**
  370. * 如果两个链表相交于某一节点,那么在这个相交节点之后的所有节点都是两个链表所共有的。 也就是说,如果两个链表相交,那么最后一个节点肯定是共有的。
  371. * 先遍历第一个链表,记住最后一个节点,然后遍历第二个链表, 到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,
  372. * 否则不相交。时间复杂度为O(len1+len2),因为只需要一个额外指针保存最后一个节点地址, 空间复杂度为O(1)
  373. */
  374. public static boolean isIntersect(Node head1, Node head2) {
  375. if (head1 == null || head2 == null) {
  376. return false;
  377. }
  378.  
  379. Node tail1 = head1;
  380. // 找到链表1的最后一个节点
  381. while (tail1.next != null) {
  382. tail1 = tail1.next;
  383. }
  384.  
  385. Node tail2 = head2;
  386. // 找到链表2的最后一个节点
  387. while (tail2.next != null) {
  388. tail2 = tail2.next;
  389. }
  390.  
  391. return tail1 == tail2;
  392. }
  393.  
  394. /**
  395. * 求两个单链表相交的第一个节点 对第一个链表遍历,计算长度len1,同时保存最后一个节点的地址。
  396. * 对第二个链表遍历,计算长度len2,同时检查最后一个节点是否和第一个链表的最后一个节点相同,若不相同,不相交,结束。
  397. * 两个链表均从头节点开始,假设len1大于len2
  398. * ,那么将第一个链表先遍历len1-len2个节点,此时两个链表当前节点到第一个相交节点的距离就相等了,然后一起向后遍历,直到两个节点的地址相同。
  399. * 时间复杂度,O(len1+len2)
  400. *
  401. * ---- len2
  402. * |__________
  403. * |
  404. * --------- len1
  405. * |---|<- len1-len2
  406. */
  407. public static Node getFirstCommonNode(Node head1, Node head2) {
  408. if (head1 == null || head2 == null) {
  409. return null;
  410. }
  411. int len1 = 1;
  412. Node tail1 = head1;
  413. while (tail1.next != null) {
  414. tail1 = tail1.next;
  415. len1++;
  416. }
  417.  
  418. int len2 = 1;
  419. Node tail2 = head2;
  420. while (tail2.next != null) {
  421. tail2 = tail2.next;
  422. len2++;
  423. }
  424.  
  425. // 不相交直接返回NULL
  426. if (tail1 != tail2) {
  427. return null;
  428. }
  429.  
  430. Node n1 = head1;
  431. Node n2 = head2;
  432.  
  433. // 略过较长链表多余的部分
  434. if (len1 > len2) {
  435. int k = len1 - len2;
  436. while (k != 0) {
  437. n1 = n1.next;
  438. k--;
  439. }
  440. } else {
  441. int k = len2 - len1;
  442. while (k != 0) {
  443. n2 = n2.next;
  444. k--;
  445. }
  446. }
  447.  
  448. // 一起向后遍历,直到找到交点
  449. while (n1 != n2) {
  450. n1 = n1.next;
  451. n2 = n2.next;
  452. }
  453.  
  454. return n1;
  455. }
  456.  
  457. /**
  458. * 求进入环中的第一个节点 用快慢指针做(本题用了Crack the Coding Interview的解法,因为更简洁易懂!)
  459. */
  460. public static Node getFirstNodeInCycle(Node head) {
  461. Node slow = head;
  462. Node fast = head;
  463.  
  464. // 1) 找到快慢指针相遇点
  465. while (fast != null && fast.next != null) {
  466. slow = slow.next;
  467. fast = fast.next.next;
  468. if (slow == fast) { // Collision
  469. break;
  470. }
  471. }
  472.  
  473. // 错误检查,这是没有环的情况
  474. if (fast == null || fast.next == null) {
  475. return null;
  476. }
  477.  
  478. // 2)现在,相遇点离环的开始处的距离等于链表头到环开始处的距离,
  479. // 这样,我们把慢指针放在链表头,快指针保持在相遇点,然后
  480. // 同速度前进,再次相遇点就是环的开始处!
  481. slow = head;
  482. while (slow != fast) {
  483. slow = slow.next;
  484. fast = fast.next;
  485. }
  486.  
  487. // 再次相遇点就是环的开始处
  488. return fast;
  489. }
  490.  
  491. /**
  492. * 求进入环中的第一个节点 用HashMap做 一个无环的链表,它每个结点的地址都是不一样的。
  493. * 但如果有环,指针沿着链表移动,那这个指针最终会指向一个已经出现过的地址 以地址为哈希表的键值,每出现一个地址,就将该键值对应的实值置为true。
  494. * 那么当某个键值对应的实值已经为true时,说明这个地址之前已经出现过了, 直接返回它就OK了
  495. */
  496. public static Node getFirstNodeInCycleHashMap(Node head) {
  497. HashMap<Node, Boolean> map = new HashMap<Node, Boolean>();
  498. while (head != null) {
  499. if (map.get(head) == true) {
  500. return head; // 这个地址之前已经出现过了,就是环的开始处
  501. } else {
  502. map.put(head, true);
  503. head = head.next;
  504. }
  505. }
  506. return head;
  507. }
  508.  
  509. /**
  510. * 给出一单链表头指针head和一节点指针toBeDeleted,O(1)时间复杂度删除节点tBeDeleted
  511. * 对于删除节点,我们普通的思路就是让该节点的前一个节点指向该节点的下一个节点
  512. * ,这种情况需要遍历找到该节点的前一个节点,时间复杂度为O(n)。对于链表,
  513. * 链表中的每个节点结构都是一样的,所以我们可以把该节点的下一个节点的数据复制到该节点
  514. * ,然后删除下一个节点即可。要注意最后一个节点的情况,这个时候只能用常见的方法来操作,先找到前一个节点,但总体的平均时间复杂度还是O(1)
  515. */
  516. public void delete(Node head, Node toDelete){
  517. if(toDelete == null){
  518. return;
  519. }
  520. if(toDelete.next != null){ // 要删除的是一个中间节点
  521. toDelete.val = toDelete.next.val; // 将下一个节点的数据复制到本节点!
  522. toDelete.next = toDelete.next.next;
  523. }
  524. else{ // 要删除的是最后一个节点!
  525. if(head == toDelete){ // 链表中只有一个节点的情况
  526. head = null;
  527. }else{
  528. Node node = head;
  529. while(node.next != toDelete){ // 找到倒数第二个节点
  530. node = node.next;
  531. }
  532. node.next = null;
  533. }
  534. }
  535. }
  536.  
  537. }

面试大总结:Java搞定面试中的链表题目总结的更多相关文章

  1. (转)面试大总结之一:Java搞定面试中的链表题目

    面试大总结之一:Java搞定面试中的链表题目 分类: Algorithm Interview2013-11-16 05:53 11628人阅读 评论(40) 收藏 举报 链表是面试中常出现的一类题目, ...

  2. 面试大总结之二:Java搞定面试中的二叉树题目

    package BinaryTreeSummary; import java.util.ArrayList; import java.util.Iterator; import java.util.L ...

  3. Java原来还可以这么学:如何搞定面试中必考的集合类

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 系列文章介绍 本文是<五分钟学Java>系列文章的一篇 本系列文章主要围绕Java程序员必须掌握的核心技能,结合我个人三年 ...

  4. 【搞定面试官】谈谈你对JDK中Executor的理解?

    ## 前言 随着当今处理器计算能力愈发强大,可用的核心数量越来越多,各个应用对其实现更高吞吐量的需求的不断增长,多线程 API 变得非常流行.在此背景下,Java自JDK1.5 提供了自己的多线程框架 ...

  5. 【搞定面试官】- Synchronized如何实现同步?锁优化?(1)

    前言 说起Java面试中最高频的知识点非多线程莫属.每每提起多线程都绕不过一个Java关键字--synchronized.我们都知道该关键字可以保证在同一时刻,只有一个线程可以执行某个方法或者某个代码 ...

  6. 告别set和get,两大利器轻松搞定model转换

    场景一:一般我们遇到需要新建model,常规做法就是创建一个类,老老实实的定义好model中的所有属性,一般来说属性对应的set方法和get方法都是少不了的,有时候还需要toString甚至equal ...

  7. 大前端时代搞定PC/Mac端开发,我有绝招

    如果你是一位前端开发工程师,对"跨平台"一词应该不会感到陌生.像常见的前端框架:比如React.Vue.Angular,它们可以做网页端,也可以做移动端,但很少能做到跨PC.Mac ...

  8. 如何用python搞定验证码中的噪点

    背景:朋友在为"关山口男子职业技术学校"写一款校园应用,于是找MoonXue写一个学生选课系统的登录接口.为了搞定这个接口,不得不先搞定这个系统的验证码. 验证码大概是这个样子 看 ...

  9. 三步轻松搞定delphi中CXGRID手动添加复表头(多行表头,报表头)

    网上有代码动态生成cxgrid多行表头的源码,地址为:http://mycreature.blog.163.com/blog/static/556317200772524226400/ 如果要手动设计 ...

随机推荐

  1. jLink(v8)GDB 命令总结

    /** ****************************************************************************** * @author    Maox ...

  2. VM 打开虚拟机时报“内部错误”

    VM 打开虚拟机时报“内部错误” 你是直接双击VM软件吗? 试下右键用管理员身份打开VM吧 是不是成功了 不成功不要找我,我就是这样成功的,就自己记录下

  3. apache和php扩展问题

    1.redis扩展: windows下开发用的xampp集成的环境,想装个php-redis扩展,扩展的github地址:  https://github.com/nicolasff/phpredis ...

  4. winfrom 水晶按钮

    闲来无事,从网上找了不少自定义控件,然后整理了一下,做了一个水晶按钮 /// <summary> /// 表示 Windows 的按钮控 /// </summary> [Des ...

  5. 同时存在两个或多个homestead 虚拟box

    开发中发现,不同版本的homestead 里面的环境各不相同,里面的node,npm等版本都不一致,如果需要添加 不同版本的homestead同时存在可以按照以下办法处理. tips: 提供可以离线下 ...

  6. Sublime Text 3 使用备注

    去年开始为了正规化自己的日常编辑工作,在dw,editplus,notap++,st里做了个选择,最终决定改曾经的dw为st. 毕竟dw是上个世纪的东西了,体积比较臃肿了.所以,在这里记录关于st的使 ...

  7. php总结:1.php介绍

    1.什么是php PHP,即“Hypertext Preprocessor”,是一种被广泛应用的开源通用脚本语言,尤其适用于 Web 开发并可嵌入 HTML 中去.它的语法利用了 C.Java 和 P ...

  8. mysql之多表查询

    今天在项目中遇到一个数据库查询的问题:三张表分别放置不同的东西:分享的音频相关数据.分享的文字图片说说.分享的主题相关数据.所有分享的东西都可看做新鲜事,现在要求从这三张表将相同的几个字段的数据全部查 ...

  9. 一个基于python的即时通信程序

    5月17日更新: 广播信息.用户列表.信息确认列表以及通信信息,从原来的用字符串存储改为使用字典来存储,使代码更清晰,更容易扩展,具体更改的格式如下: 广播信息(上线): { 'status': 信息 ...

  10. WPF如何卸载U盘(弹出USB设备)

    应用程序和硬件设备的通信过程是:应用程序使用CreateFile函数打开设备,然后用DeviceIoControl()与硬件设备通信. CreateFile函数: [DllImport("k ...