算法7-数据结构-链表-删除链表中的节点-删除链表中的倒数第N个节点-反转链表-从尾到头打印链表-复杂链表的复制-

链表

Java中的链表形式

 public class ListNode {
    int val; // 模拟链表中具体的值
    ListNode next; // 模拟链表中的下一个结点
    ListNode(int x) { val = x; }  // 构造方法,用于修改结点的值
}

注意2022/6/3
注意链表的起始节点保存,要拼接链表的话,对前一个节点的保存;

删除链表中的节点

需求
请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。
题目数据保证需要删除的节点 不是末尾节点 。
示例
输入:head = [4,5,1,9], node = 5 输出:[4,1,9]
输入:head = [4,5,1,9], node = 1 输出:[4,5,9]

方法1:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

时间复杂度:O(1)
空间复杂度:O(1)
备注

2022/6/2
想不到这种方式,确实会想为什么没有传入head
确实有在想遍历找到node再去删除

删除链表的倒数第N个节点

需求
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
输入:head = [1], n = 1 输出:[]
输入:head = [1,2], n = 1 输出:[1]

ListNode

public class ListNode {
     int val; // 结点的值
     ListNode next; // 结点的中存储的下一个元素的指针
     ListNode() {} // 无参构造方法
     ListNode(int val) { this.val = val; } // 给结点设置值
     ListNode(int val, ListNode next) { this.val = val; this.next = next; } // 给结点设置值和指针
 }

方法1:非递归求解

public ListNode removeNthFromEnd(ListNode head, int n) {
	// 定义要删除节点的前一个
	ListNode pre = head;
	
	// 找到要删除节点前所有节点的个数
	int len = getLength(head) - n ;

	// 获取删除节点前一个节点
	for(int i = 0 ; i < len-1 ; i ++) {
		pre = pre.next;
	}
	pre.next = pre.next.next;
	return head;
}

// 定义方法计算链表的长度
public int getLength(ListNode head){
	int count = 1; // 统计链表的长度
	while(head.next != null){
		head = head.next;
	}
	return count;
}

获取删除节点的前一个节点for循环的条件判断语句len-1
在这里插入图片描述

方法2:递归求解

public ListNode removeNthFromEnd(ListNode head, int n) {
	// 得到 递归结束时的长度
	int pos = getLength(head,n);
	// 如果pos等于n,表示删除的节点是head
	if(pos == n)
		return head.next;
	return head;
}
// 递归
public int getLength(ListNode node, int n){
	// 递归结束条件
	if(node == null)
		return 0;
	int pos = getLength(node.next,n) + 1;
	
	if(pos == n+1)
		node.next = node.next.next;
	return node;
}

图示:
在这里插入图片描述

方法3:双指针

public ListNode removeNthFromEnd(ListNode head, int n) {
	// 定义两个指针
	ListNode fast = head;
	ListNode slow = head;
	// fast先向后移动n个位置
	for(int i = 0 ; i < n ; i ++)
		fast = fast.next;
	
	// 临界值判断,如果fast是null 说明要删除的是head节点
	if(fast == null)
		return head.next;
	
	// 两个指针都向后移动
	while(fast.next != null){
		fast = fast.next;
		slow = slow.next;
	}
	slow.next = slow.next.next;
	return head;
}

图示:
在这里插入图片描述

反转链表

需求
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
输入:head = [1,2] 输出:[2,1]
输入:head = [] 输出:[]

方法1:使用栈的形式

链表的反转,常考题目;
使用栈,因为栈是先进后出的;
实现原理是把链表节点一个个入栈,当全部入栈完之后再一个个出栈,出栈的时候再把出栈的节点串成一个新的链表;

public ListNode reverseList(ListNode head) {
	// 定义堆栈
	Stack<ListNode> stack = new Stack<ListNode>();

	// 入栈
	while(head != null){
		stack.push(head);
		head = head.next;
	}

	// 临界值判断
	if(stack.empty()
		return null;
	// 出栈
	ListNode node = stack.pop(); // 链表的最后一个节点
	ListNode res_list = node; // 链表的第一个节点
	while( !stack.empty()){
		// 新取出来的节点
		ListNode tempNode = stack.pop();
		// 赋值给链表的最后一个节点的下一个
		node.next = tempNode;
		// 重新找链表的最后一个节点
		node = node.next;
	}
	// 给node节点的下一个节点赋值为null
	node.next = null;
	return res_list;
}

图示:
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // 使用栈的形式
        Stack<ListNode> stack = new Stack<ListNode>();

        while(head != null){
            stack.push(head);
            head = head.next;
        }
        // 链表的第一个节点
        head = null;
        // 栈出栈的元素
        ListNode node = new ListNode();
        // 出栈元素所要插入位置的表头节点
        ListNode temp = new ListNode();
        while(!stack.empty()){
            node = stack.pop();
            if(head == null){
                head = node;
                temp = node;
            }else{
                temp.next = node;
                temp = node;
                node.next = null;
            }
        }
        return head;
    }
}

方法2:双链表求解

思路:
每次从旧链表上摘下一个节点,那么就把节点放在新链表的头部;
注意保留旧链表头节点的下一个节点地址;

public ListNode reverseList(ListNode head) {
	// 定义新的链表
	ListNode head_new = null;
	
	// 定义存储旧链表下一个位置的节点
	ListNode head1 = null;
	// 遍历旧链表
	while(head != null){
	// 保存旧链表下一次要访问的首节点
		head1 = head.next;
		// 修改head的下一个节点位置
		head.next = head_new;
		// 修改新节点的首节点
		head_new = head;
		// 给head节点重新赋值
		head = head1;
	}
	// 链表为空判断
	if(head_new == null)
		return null;
	return head_new;
}

图示:
在这里插入图片描述

方法3:递归 = 从后往前处理(头递归)

public ListNode reverseList(ListNode head) {
        // 递归终止条件
        if(head == null || head.next == null)
            return head;
        // 前者 表示最初的head链表就为空;后者表示 已经找到链表的最后一个元素

        // 逻辑处理过程
        // 保存当前节点的下一个节点
        ListNode next = head.next;

        // 递归调用
        ListNode reverse = reverseList(next);

        // 逻辑处理
        next.next = head;
        head.next = null;
        return reverse;
    }

图示:
在这里插入图片描述
分析
该递归形式是 向下传递,基本没有逻辑,当往回反弹的时候才开始处理,从链表的尾端往前开始处理;

方法4:递归 = 从前往后处理(尾递归+一次性出栈)

分析:
其中递归的输入是head和newhead,head表示原始链表的首节点,newhead表示新链表的首节点;
尾递归 虽然会不停的压栈,但由于最后返回的是递归函数的值,所以在返回的时候都会一次性出栈,不会一个个出栈这么慢;

public ListNode reverseList(ListNode head) {
	return reverseListIte(head,null);
}
public ListNode reverseListIte(ListNode head,ListNode oldhead){
	// 递归结束条件
	if(head == null)
		return oldhead;
	// 处理逻辑
	ListNode next = head.next; //保存head节点的下一个节点
	head.next = oldhead;

	// 递归调用
	return reverseListIte(next,head);
}

图示:
在这里插入图片描述

方法5:递归 = 从前先后处理(尾递归+一个个出栈)

public ListNode reverseList(ListNode head) {
	return reverseListIte(head,null);
}
public ListNode reverseListIte(ListNode head,ListNode oldhead){
	// 递归结束条件
	if(head == null)
		return oldhead;
	// 逻辑处理
	ListNode next = head.next;
	head.next = oldhead;

	// 递归调用
	ListNode node = reverseListIte(next,head);
	return node;
}

方式6:双指针 = 双链表迭代

public ListNode reverseList(ListNode head) {
	// 临界条件判断
	if(head == null)
		return head;
	
	// 保存head的下一个节点
	ListNode next = head.next;
	head.next = null;
	// 存储中间元素
	ListNode temp = null;
	// 循环遍历链表元素
	while(next != null){
		temp = next.next;
		next.next = head;
		head = next;
		next = temp;
	}

	return head;
}

图示:
在这里插入图片描述

offer06:从尾到头打印链表

需求
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例
输入:head = [1,3,2] 输出:[2,3,1]

方法1:双层for循环

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        // 双层for循环
        // 确定链表的长度
        int len = 0;
        ListNode node = head;
        while(node != null){
            len ++;
            node = node.next;
        }
        // 定义数组
        node = head;
        int[] arr = new int[len];
        for(int i = len-1; i >= 0;i--){
            arr[i] = node.val;
            node = node.next;
        }
        return arr;
    }
}
自己写的。2022/7/9
时间复杂度:O(N) 两个循环 遍历次数都是N
空间复杂度:O(N) 存储输出元素的数组

方法2:递归法(+ArrayList)

分析
先递推至链表的尾部;
回溯时,依次将节点值加入列表,既可实现链表值的倒序输出。

递推终止条件:head == null
递推过程:head = head.next
回溯阶段:节点的head.val加入到列表中。

集合ArrayList存储节点内容(倒叙),再遍历存储到数组中。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    ArrayList<Integer> list = new ArrayList<Integer>();
    public int[] reversePrint(ListNode head) {
        // 递归法
        recur(head);
        int[] ret = new int[list.size()];
        for(int i = 0 ; i < list.size(); i++){
            ret[i] = list.get(i);
        }
        return ret;
    }
    public void recur(ListNode node){
        if(node == null){
            return;
        }
        recur(node.next);
        list.add(node.val);
    }
}
自己分析2022/7/9
时间复杂度 :O(N)
遍历链表 递归N次
空间复杂度:O(N)
系统递归需要使用O(N)的栈空间

方法3:辅助栈法

分析
链表只能从前至后的访问每个节点,题目要求倒叙输出,这种先入后出的需求可以借助栈来实现。

栈内存储的是ListNode的val

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        // 将元素存储到栈中
        int len = 0;
        Stack<Integer> sta = new Stack<Integer>();
        while(head != null){
            sta.push(head.val);
            head = head.next;
            len++;
        }
        // 建立数组 存放倒叙元素
        int[] arr = new int[len];
        for(int i = 0 ; i < len; i++)
            arr[i] = sta.pop();
        return arr;
    }
}

时间复杂度:入栈和出栈共使用O(N)时间;
空间复杂度:辅助栈stack和数组arr共使用O(N)的额外空间。

栈内存储的元素是ListNode

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        // 辅助栈方法
        Stack<ListNode> sta = new Stack<ListNode>();
        ListNode temp = head;
        while(temp != null){
            sta.push(temp);
            temp = temp.next;
        }

        // 定义数组用于存储val内容
        int[] arr = new int[sta.size()];
        int i = 0 ; 
        while(!sta.empty()){
            arr[i++] = sta.pop().val;
        }
        return arr;
    }
}

方法4:辅助集合

使用ArrayList辅助集合,循环使用一个变量

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        // 使用辅助集合存储ListNode节点
        List<ListNode> li = new ArrayList<ListNode>();

        ListNode temp = head;
        while(temp != null){
            li.add(temp);
            temp = temp.next;
        }

        // 创建数组
        int[] arr = new int[li.size()];
        for(int i = li.size()-1 ; i >= 0 ;i--){
            arr[li.size()-i-1] = li.get(i).val;
        }
        return arr;
    }
}

使用ArrayList,循环使用两个变量

 public int[] reversePrint(ListNode head) {
        List<ListNode> list = new ArrayList<ListNode>();
        ListNode temp = head;
        while(temp != null){
            list.add(temp);
            temp = temp.next;
        }
        int[] array = new int[list.size()];
        int j = 0;
        for(int i = list.size() -1;i >= 0; i--){
            array[j++] = list.get(i).val; 
        }
        return array;
    }

方法5:辅助LinkedList

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        // 辅助列表 LinkedList
        LinkedList<ListNode> li = new LinkedList<ListNode>();
        ListNode temp = head;
        while(temp != null){
            li.add(temp);
            temp = temp.next;
        }
        // 定义集合长度
        int len = li.size();

        // 定义数组
        int[] arr = new int[len];
        // 对于列表li进行了处理 那么li.szie会改变 而不是之前的内容。
        for(int i = 0 ; i < len;i++){
            arr[i] = li.removeLast().val;
        }
        return arr;
    }
}

方法6:辅助集合+Collections

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        // 辅助集合 Collections.reverse方法
        List<ListNode> li = new ArrayList<ListNode>();

        while(head != null){
            li.add(head);
            head = head.next;
        }
        // 实现集合翻转
        Collections.reverse(li);
        
        // 创建数组
        int[] arr = new int[li.size()];
        for(int i = 0 ; i < arr.length ; i++){
            arr[i] = li.get(i).val;
        }
        return arr;
    }
}

offer35 复杂链表的复制

需求
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例
在这里插入图片描述
random指向前一个元素、指向null、指向第一个元素、指向null之前的元素;
在这里插入图片描述
random指向自己;
在这里插入图片描述
random指向前一个元素、指向后一个元素、指向null;
在这里插入图片描述
给定链表为空,返回null。

方法1:map集合(Node,Node)

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        // 两次使用map
        Map<Node,Node> map = new HashMap<Node,Node>();

        if(head == null)
            return head;
        // 保留原始链表的头head
        Node cur = head;
        // 将链表元素添加到map集合中
        while(cur != null){
            map.put(cur,new Node(cur.val));
            cur = cur.next;
        }
        // 修改map集合中的value部分 也就是说map中键和值存储内容一样。
        cur = head;
        while(cur != null){
            // cur的value值的next属性 设置为 cur的next属性key 对应的value
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}

方法2:递归

未理解

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    // 创建map集合存储节点
    Map<Node,Node> map = new HashMap<Node,Node>();
    public Node copyRandomList(Node head) {
        // 递归形式(尾递归)

        // 递归结束条件
        // 没有节点的情况
        if(head == null) return head;
        // 节点已经全部放入map集合中
        if(map.containsKey(head)) return map.get(head);

        // 逻辑处理
        Node node = new Node(head.val);
        map.put(head,node);

        // 递归调用
        node.next = copyRandomList(head.next);
        node.random = copyRandomList(head.random);
        return node;
    }
}

图示
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值