链表和递归
题目:Leetcode 203. Remove Linked List Elements
示例代码:Solution.java【不使用虚拟头结点】
class Solution {
public ListNode removeElements(ListNode head, int val) {
while(head != null && head.val == val){//传入的链表不能为空【如果传入的 val 在头部,则删除头结点】
//【如果要删除的头结点与给定的头结点相等的话,则第二个节点仍与给定的节点相同,故使用 while 】
ListNode delNode = head; //待删除节点就是head节点
head = head.next; //绕过要删除的节点
delNode.next = null; //删除节点
}
if(head == null)
return head;
ListNode prev = head; //【如果传入的 val 在中间,则要找到待删除元素的前一个节点】
while(prev.next != null){ //还没有遍历到最后一个节点,每次查看下一个节点是否需要被删除
//【if 中删除结束后,prev 不要向后诺,因为下一个元素任然可能是要删除的元素,故加while】
if(prev.next.val == val) { //如果 val 和传入的 val 相同,则需要删除
ListNode delNode = prev.next; //prev 的下一个节点就是要删除的节点
prev.next = delNode.next;
delNode.next = null;
}
else
prev = prev.next; //不用被删除,则 prev 向后挪一下
}
return head;
}
}
简化代码:Solution2.java
class Solution2 {
public ListNode removeElements(ListNode head, int val) {
while(head != null && head.val == val)
head = head.next;
if(head == null)
return head;
ListNode prev = head;
while(prev.next != null){
if(prev.next.val == val)
prev.next = prev.next.next;
else
prev = prev.next;
}
return head;
}
}
示例代码:Solution3.java【使用虚拟头结点】
class Solution3 {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(-1);//dummyHead 是虚拟头结点,永远不会访问到,所以随意设置为 -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; //要返回虚拟头结点的下一个
}
}
输出:
2.代码在本地调试
示例代码:Solution3.java
/// Leetcode 203. Remove Linked List Elements
/// https://leetcode.com/problems/remove-linked-list-elements/description/
class Solution3 {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
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); //构造函数中传入数组nums
System.out.println(head); //打印以 head 作为头结点的整个链表对应的字符串
ListNode res = (new Solution3()).removeElements(head, 6);//将 head 和待删除节点6作为参数传入
System.out.println(res);
}
}
输出:
3.链表与递归
递归定义:本质上,将原来的问题,转换为更小的同一问题
例:数组求和--
Sum(arr[0...n-1]) = arr[0] + Sum(arr[1...n-1])[更小的同一问题]
Sum(arr[1...n-1]) = arr[1] + Sum(arr[2...n-1])[更小的同一问题]
. . .
Sum(arr[n-1...n-1]) = arr[n-1] + Sum([])[最基本的问题]---(只要 Sum([]) 值解决,根据同一逻辑可解决整个问题)
代码实现:
public class Sum {
public static int sum(int[] arr){ // 这个sum是由用户来使用的
return sum(arr, 0); //调用下面的sum
}
// 计算arr[l...n)这个区间内所有数字的和
private static int sum(int[] arr, int left){ //这个 sum 函数是真正的递归函数
if(left == arr.length) //相当于整个数组为空的时候
return 0;
return arr[left] + sum(arr, left + 1); //递归调用【从计算 left--n 的和变为 left+1 -- n 的和】
}
public static void main(String[] args) {
int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
System.out.println(sum(nums));
}
}
输出:
在上述代码中,1.求解最基本的问题
if(left == arr.length)
return 0;
2.把原问题转换为更小的问题,要根据更小的问题的答案构建出原问题的答案 arr[left] +
return arr[left] + sum(arr, left + 1);
4. 链表的天然递归结构性质
链表可以看做是一个 头结点 0+ 一个更短的链表,在图中,1 就是更短链表的头结点;也可以看做是 头结点 0+ 一个更短的链表,2 就是更短链表的头结点;以此类推,直到最后,可理解为 NULL 本身也是一个链表,是哪个最普通的链表
解决链表中删除元素的问题:
对于最原始的链表,可看做是 头结点e + 一个更短的链表
如何通过得到解来解决原问题的解?对原问题,就是没有考虑 头结点 :如果头结点e != v(无需删除),最终原问题的结果,就是
头结点e +求得的子问题节表;如果需要删除的话,就是后续求出的链表;
代码示例:Solution4.java
class Solution4 {
public ListNode removeElements(ListNode head, int val) {
if(head == null)
return head;
ListNode res = removeElements(head.next, val); //res 中存储将头节点后面跟着的链表中所有值为 val 的值删除后剩下的链表
if(head.val == val) //head这个节点也满足要被删除的条件
return res;
else{
head.next = res;//不删除 head,head 接head后面的链表删除 val 后的链表
return 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 Solution4()).removeElements(head, 6);
System.out.println(res);
}
}
简化代码:
class Solution5 {
public ListNode removeElements(ListNode head, int val) {
if(head == null)
return head;
head.next = removeElements(head.next, val);
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 Solution5()).removeElements(head, 6);
System.out.println(res);
}
}
输出:
5. 递归运行的机制:递归的微观解读
递归的实质:递归函数的调用就是函数调用,只不过调用的是它自身
数组求和
链表中删除元素
图解过程:
6.递归算法的调试
示例代码:
public class Solution {
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 Solution()).removeElements(head, 6, 0);
System.out.println(res);
}
}
输出:
补充:
1.双链表
2.循环链表
3.数组链表(明确知道要处理元素的个数)