(一)递归
递归:本质上,将原来的问题,转化为更小的问题
应用:数组求和
package cn.leetCode;
public class Sum {
public static int sum(int[] arr){
return sum(arr,0);
}
//计算arr[l..n)这个区间(从l到n-1)内所有数字的和,私有化
private static int sum(int[] arr,int l){
if(l == arr.length){
//说明数组为空
return 0;
}
return arr[l] + sum(arr,l+1);
}
public static void main(String[] args) {
int[] nums = {1,2,3,4,5,6,7,8};
System.out.println(sum(nums));
}
}
控制台输出:36
分析:
//计算arr[l,n)范围里的数字和
public static int sum(int[] arr,int l){
//求解最基本的问题
if(l == arr.length){
return 0;
}
//把原问题转化成更小的问题
return arr[l]+sum(arr,l+1);
}
递归(函数A中调用函数A与函数A中调用函数B没有本质区别,写程序时可以以第二种方式思考):
在进行递归调用时,就将调用的函数想成一个子函数,它具有一定的功能,至于具体功能如何实现,不用去细致追究
只要可以调用子函数的功能实现上层函数的功能就行
1.注意递归函数的"宏观"语义
2.递归函数就是一个函数,完成一个功能
(二)删除链表中某个节点
1.构建链表节点类
package cn.leetCode;
//此类为一个节点
public class ListNode {
int val;
ListNode next;
ListNode(int x){
val = x;
}
//链表节点的构造函数,构造以ListNode为头结点的链表
//使用arr为参数,创建一个链表,当前的ListNode为链表的头结点
public ListNode(int[] arr){
if(arr == null || arr.length == 0){
throw new IllegalArgumentException("arr can not be empty");
}
this.val = arr[0];//当前节点
ListNode cur = this;
//将数组后面的元素挂到链表后面
for(int i=1;i<arr.length;i++){
cur.next = new ListNode(arr[i]);
cur = cur.next;
}
}
//返回以当前节点为头结点的链表信息字符串
@Override
public String toString(){
StringBuilder res = new StringBuilder();
//从自身开始进行循环
ListNode cur = this;
while(cur != null){
res.append(cur.val + "->");
cur = cur.next;
}
//添加null,代表链表的尾部
res.append("NULL");
return res.toString();
}
}
(a)不存在虚拟头结点的情况删除链表中的节点
//不存在虚拟头结点的情况
package cn.leetCode;
public class Solution {
//删除链表中value=val的节点
public ListNode removeElements(ListNode head,int val){
//在没有虚拟头结点的情况下,删除头结点需要进行单独处理(比较特殊)
//若head节点的值为val,那么此时删除的节点就是头结点
//要注意边界条件,比如传进来的是空链表的时候,头结点也为空
//if(head!=null && head.val == val){
//若删完之后新的头结点的值还为val,那就要继续删除了,所以要使用while循环
while(head!=null && head.val == val){
ListNode delNode = head;
head = head.next;
//是要删除的的节点指向空,脱离链表
delNode.next = null;
//head = head.next;
}
//若整个链表全为要删除的节点组成,那么在进行上述操作后,链表为空,就没必要进行后面的操作了
if(head == null){
return null;
}
//接下来删除链表中间需要删除的节点(删除操作的关键是:找到待删除的节点的前一个节点)
//运行到此处,若链表有头结点,那么一定不是需要删除的节点,而是在此之后的节点
ListNode prev = head;
//prev.next!= null意为没有到链表的尾节点
while(prev.next!= null){
if(prev.next.val == val){
ListNode delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
//prev.next = prev.next.next;
}else{
//不用删除,继续后续遍历寻找需要删除的节点
prev = prev.next;
}
}
return head;
}
//制作测试用例,测试上面的代码
public static void main(String[] args) {
int[] nums = {1,2,6,3,4,5,6};
ListNode head = new ListNode(nums);
//打印以head为头结点的链表
System.out.println(head);
ListNode res = (new Solution()).removeElements(head, 6);
System.out.println(res);
}
}
控制台输出结果:
1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL
(b)设置虚拟头结点删除链表中节点
//存在虚拟头结点的情况
package cn.leetCode;
public class Solution2 {
public ListNode removeElements(ListNode head, int val){
//创建一个虚拟的头结点指向head,我们永远都不会去访问虚拟头结点的值,将其设为某个值就行
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
//由于存在dummyHead,因此不需要像第一种方法一样对删除头结点操作进行特殊处理
//从头开始遍历链表,查看是否有需要删除的节点
ListNode prev = dummyHead;
while(prev.next != null){
if(prev.next.val == val){
prev.next = prev.next.next;
}else{
prev = prev.next;
}
}
return dummyHead.next;
}
//制作测试用例,测试上面的代码
public static void main(String[] args) {
int[] nums = {1,2,6,3,4,5,6};
ListNode head = new ListNode(nums);
//打印以head为头结点的链表
System.out.println(head);
ListNode res = (new Solution2()).removeElements(head, 6);
System.out.println(res);
}
}
控制台输出:
1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL
注:链表和递归的特点:
链表天然的递归性
0->1->2->3->4->null
可看作
0->一个更短的链表(少了一个节点)
递归是有代价的:函数调用+系统栈空间
关于递归:近乎和链表相关的所有操作,都可以使用递归的形式完成
建议同学们对链表的增删改查进行递归实现
(c)使用递归的方法删除链表中的节点
package cn.leetCode;
import cn.data.LinkedList.Main;
//使用递归的方法删除链表中的元素
public class Solution3 {
public ListNode removeElements(ListNode head, int val){
//对于一个空链表,删除了值为val的节点,链表还是为空
if(head == null){
return null;
}
//使用递归删除头结点后面的链表中值为val的节点,返回删除元素后的链表
ListNode res = removeElements(head.next,val);
//此时会出现两种情形
//(1)头结点是需要删除的节点,后面的链表中没有元素被删除
//(2)头结点不删除,头结点后面跟随的链表中有元素需要被删除
//在对某个链表中的元素进行删除的过程中,以上两种情形有可能是交叉进行的,但都符合递归的形式
if(head.val == val){
//情形(1)
return res;
}else{
//如果头结点不是需要删除的节点,将头结点后面连接的链表传入函数
//待删完需要删除的节点后,再将链表接在head节点后面
head.next = res;
return head;
}
/**
* 另一种写法:
* head.next = removeElements(head.next,val);
* if(head.val == val){
* return head.next;
* }else{
* return head;
* }
*三目运算符return head.val == val?head.next:head;
* */
}
public static void main(String[] args){
int[] nums = {1,2,6,3,4,5,6};
ListNode head = new ListNode(nums);
System.out.println(head);
ListNode res = (new Solution3()).removeElements(head, 6);
System.out.println(res);
}
}
控制台输出:
1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL
(三)通过打印输出的方式显示递归调用的过程
package cn.leetCode;
public class showDiGui {
//depth表示递归深度,函数每实现一次调用,递归深度加一
//通过打印输出的方式来显示递归调用的过程
public ListNode removeElements(ListNode head,int val,int depth){
String depthString = generateDepthString(depth);
System.out.print(depthString);
System.out.println("Call: remove " + val + " in " + head);
if(head == null){
System.out.print(depthString);
System.out.println("return: " + head);
return head;
}
ListNode res = removeElements(head.next,val,depth+1);
System.out.print(depthString);
System.out.println("After remove " + val + ": " + res);
ListNode ret;
if(head.val == val){
ret = res;
}else{
head.next = res;
ret = head;
}
System.out.print(depthString);
System.out.println("Return: " + ret);
return ret;
}
//深度字符串“--”,字符串越长,代表递归深度越深
private String generateDepthString(int depth){
StringBuilder res = new StringBuilder();
for(int i = 0;i<depth;i++){
res.append("--");
}return res.toString();
}
public static void main(String[] args){
int[] nums = {1,2,6,3,4,5,6};
ListNode head = new ListNode(nums);
System.out.println(head);
ListNode res = (new showDiGui()).removeElements(head, 6,0);
System.out.println(res);
}
}
控制台输出:
1->2->6->3->4->5->6->NULL
Call: remove 6 in 1->2->6->3->4->5->6->NULL 从链表1->2->6->3->4->5->6->null中移除6,此时调用头结点后面的链表,后面类似
--Call: remove 6 in 2->6->3->4->5->6->NULL
----Call: remove 6 in 6->3->4->5->6->NULL
------Call: remove 6 in 3->4->5->6->NULL
--------Call: remove 6 in 4->5->6->NULL
----------Call: remove 6 in 5->6->NULL
------------Call: remove 6 in 6->NULL
--------------Call: remove 6 in null 从空链表中删除6
--------------return: null 由于空链表的头节点的值不等于6,所以直接返回null
------------After remove 6: null
------------Return: null 此时链表为6->null
----------After remove 6: null 头结点元素值为6,是需要删除的节点,故只剩下null
----------Return: 5->NULL 再返回上一层调用现场,将后面的null接在5后面
--------After remove 6: 5->NULL
--------Return: 4->5->NULL 5->6->null删除6后的链表(5->null)接在4后面,得到4->5->null
------After remove 6: 4->5->NULL 4->-5->null中没有要删除的元素,将其连接在3后面并返回
------Return: 3->4->5->NULL
----After remove 6: 3->4->5->NULL
----Return: 3->4->5->NULL
--After remove 6: 3->4->5->NULL “--”深度一样代表调用和返回的是一起的
--Return: 2->3->4->5->NULL
After remove 6: 2->3->4->5->NULL
Return: 1->2->3->4->5->NULL
1->2->3->4->5->NULL