链表回顾:
leetcode 题目(No.203 移除链表元素):
不使用虚拟头节点:

1 class Solution {
2 public ListNode removeElements(ListNode head, int val) {
3 //1,head.val 就是应该删除的节点
4 // if(head != null && head.val == val ){
5 while(head != null && head.val == val ){ //之所以使用while是因为可能有多个。
6 ListNode delNode = head;
7 head = delNode.next;
8 delNode.next = null;
9 }
10 //2,head.val 不是应该删除的节点
11 if(head ==null){
12 return null;
13 }
14 ListNode prePtr = head; //删除时应该要知道前一个节点。
15 while (prePtr.next != null){
16 if(prePtr.next.val == val){
17 ListNode delNode = prePtr.next;
18 prePtr.next = delNode.next;
19 delNode.next = null;
20 }else{
21 prePtr = prePtr.next;
22 }
23 }
24 return head;
25 }
26 }
使用虚拟头节点:

1 class Solution {
2 public ListNode removeElements(ListNode head, int val) {
3 //使用虚拟头节点 将代码处理逻辑统一。
4 ListNode dummyNode = new ListNode(0);
5 dummyNode.next = head;
6 ListNode prePtr = dummyNode;
7 while(prePtr.next != null){
8 if(prePtr.next.val == val){
9 ListNode delNode = prePtr.next;
10 prePtr.next = delNode.next;
11 delNode.next = null;
12 }else{
13 prePtr = prePtr.next;
14 }
15 }
16 return dummyNode.next;
17
18
19 }
20 }
链表和递归:
递归是一个很重要的概念,它甚至是 初级程序员和高级程序员 拉开距离的分水岭。
递归基础和递归的宏观语意:
它的本质:
将原来的问题,转化为更小的同一问题!

下面我们用递归来实现一下 数组求和。

1 package cn.zcb.demo01;
2
3 public class MySum {
4 public static void main(String[] args) {
5 int [] arr = {1,2,3,4,5,6,7,8,9,10};
6 int ret = sum(arr);
7 System.out.println(ret);
8
9 }
10 public static int sum(int []arr){
11 //为用户设计
12 return sum(arr,0); // 初始索引是0
13 }
14 private static int sum(int []arr,int leftIdx){
15 //真正的递归函数,它额外需要一个左边界的索引 。
16 //终止条件
17 if(leftIdx == arr.length ){
18 return 0;
19 }
20 //递归链条
21 return arr[leftIdx] + sum(arr,leftIdx+1);
22 }
23 }

上面递归分两处:
1,终止条件。终止条件一般比较简单,
2,递归链条。一般难的是下面的递归链表的创建(即 把原问题 转化成更小的问题)。
其实有的时候,我们不必关系递归的具体每一步骤,
而仅仅知道 该函数是干嘛的就行了(即递归函数的宏观语意)。然后,围绕它来构建终止条件 和 递归链条。
总结:宏观语意很重要,可以直接将 函数中调用自身的函数当做是已经解决的函数。
链表的天然递归结构:

所以,对于链表来说 ,绝大多数都是可以通过递归来进行解决的。
下面使用递归来解决上面的LeetCode题目。


1 class Solution {
2 public ListNode removeElements(ListNode head, int val) {
3 //终止条件
4 if(head == null)
5 return null;
6
7 //递归链条
8 ListNode res = removeElements(head.next,val); //假设 res 是已经清理好的链表。
9 if(head.val == val){ //如果head 需要清理 直接返回 res
10 return res;
11 }else{
12 head.next = res;
13 return head;
14 }
15 }
16 }

1 package cn.zcb.demo01;
2 class Solution {
3 public ListNode removeElements(ListNode head, int val) {
4 //终止条件
5 if(head == null)
6 return null;
7
8 //递归链条
9 head.next = removeElements(head.next,val); //假设函数的返回值是 已经清理好的链表。
10 if(head.val == val){ //如果head 需要清理 直接返回head.next
11 return head.next;
12 }else{
13 return head;
14 }
15 }
16 }

1 package cn.zcb.demo01;
2 class Solution {
3 public ListNode removeElements(ListNode head, int val) {
4 //终止条件
5 if(head == null)
6 return null;
7
8 //递归链条
9 head.next = removeElements(head.next,val); //假设函数的返回值是 已经清理好的链表。
10 return head.val == val?head.next:head;
11 }
12 }
递归运行的机制:递归的“微观”解读:
就解决问题本身而言,我们只需要掌握 “宏观”语意,基本上问题都能解决。
下面我们也看下微观发生了什么!


另一题:



递归的代价:

对于线性结构来说,我们是可以直接使用循环来解决的。所以,我们不能发现递归的很多好处。
但是对于非线性结构(树啊,图啊之类的)来说,我们就会发现递归的好处。它的书写逻辑是清晰的。而且是简单的。
递归算法的调试(程序中调试):
上面是在图纸上写的。对于平时写程序来说,如果每个都写在纸上也挺浪费时间,虽然它的学习效果是最好的。
其实,我们是完全可以通过打印输出进行递归调试 的 。

1 package cn.zcb.demo01;
2 class Solution {
3 public ListNode removeElements(ListNode head, int val,int depth) { //depth 是递归的深度
4 //终止条件
5 //1,一进来就输出个 关于深度的信息。 这里用- 表示, - 表示一个深度。
6 String depthString = generateDepthString(depth); //专门用来生成深度信息的函数
7 System.out.print(depthString);
8 System.out.println("Call: remove " + val + " in "+head);
9
10 if(head == null) {
11 //2, 结束递归时候的输出。
12 System.out.print(depthString);
13 System.out.println("0Return: "+head);
14 return null;
15 }
16 //递归链条
17 ListNode res = removeElements(head.next,val,depth+1);
18 //3 处理完小问题之后输出一下。
19 System.out.print(depthString);
20 System.out.println("After remove "+val +": "+res);
21
22 ListNode ret;
23 if(head.val == val){
24 ret = res;
25 }else{
26 head.next = res;
27 ret = head;
28 }
29 System.out.print(depthString);
30 System.out.println("1Return: "+ret);
31 return ret;
32 }
33 private String generateDepthString(int depth){
34 StringBuilder builder = new StringBuilder();
35 for (int i=0;i<depth;i++){
36 builder.append("-");
37 }
38 return builder.toString();
39
40
41 }
42 public static void main(String[] args) {
43 int [] nums = {1,2,6,3,4,5,6};
44 ListNode head = new ListNode(nums);
45 System.out.println(head);
46
47 //构建链表完毕,下面为测试 removeElements() 代码
48 ListNode res = (new Solution()).removeElements(head,6,0);
49 System.out.println(res);
50 }
51
52 }

1 package cn.zcb.demo01;
2
3 public class ListNode {
4 public int val;
5 public ListNode next;
6 public ListNode(int val){
7 this.val = val;
8 }
9 //int [] 数组作为参数的构造器
10 public ListNode(int [] arr){
11 if(arr == null || arr.length ==0 ){
12 throw new IllegalArgumentException("数组不能为空 !");
13 }
14 this.val = arr[0];
15
16 ListNode temp = this;
17 for (int i=1;i<arr.length;i++){
18 temp.next = new ListNode(arr[i]);
19 temp = temp.next;
20 }
21 }
22 //重写toString()
23 @Override
24 public String toString(){
25 StringBuilder builder = new StringBuilder();
26 ListNode temp =this;
27 while (temp != null){
28 builder.append(temp.val +"->");
29 temp = temp.next;
30 }
31 builder.append("null");
32 return builder.toString();
33 }
34 }
原本只有四行的代码,被我们整成了 这么多行, 它说明 牛逼的算法可能是需要上百上千的代码才可以透彻的理解的。才能潇洒的写出那四行代码的。
更多的 和 链表相关的话题:

链表的形态了解:
1,双链表:

我们之前的问题,在对队列尾端删除的时候,即使有tail ,也需要O(n) 的时间复杂度。
其实,我们可以用双链表来 解决这个事情。

不过,这样带给我们的代价是,由于有两个指针,所以维护起来会比较麻烦。
当然,双链表也可以通过使用 虚拟头节点 去使得处理逻辑统一。
2,循环链表:
进一步,有循环链表(双向),它可避免使用tail 指针了就。

链表也可以用数组来实现:
3,数组链表:

至此,之前的内容都是关于线性的。下面将进入非线性。首先的就是二分搜索树。
