【剑指offer】数据结构——链表

news2024/12/25 0:14:11

在这里插入图片描述

目录

  • 数据结构——字符串
    • 直接解
      • 【剑指offer】06. 从尾到头打印链表
        • 牛客
        • 力扣
      • 【剑指offer】24. 反转链表
      • 【剑指offer】25. 合并两个排序的链表
      • 【剑指offer】35. 复杂链表的复制
      • 【剑指offer】52. 两个链表的第一个公共结点
    • 特殊解——双指针
      • 【剑指offer】18. 删除链表的节点
      • 【剑指offer】18.2 删除链表中重复的结点
      • 【剑指offer】22. 链表中倒数第k个节点

数据结构——字符串

直接解

【剑指offer】06. 从尾到头打印链表

题目描述

力扣

牛客

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

题解

牛客

// 数组翻转法
// 时间复杂度:14ms
// 空间复杂度:9524KB
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> array = new ArrayList<>();
        while (listNode != null) {
            array.add(listNode.val);
            listNode = listNode.next;
        }
        Collections.reverse(array);
        return array;
    }
}



// 头插法
// 运行时间 12ms
// 占用内存 9692KB
import java.util.ArrayList;
import java.util.Collections;
import java.util.*;

public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res = new ArrayList<>();
        while (listNode != null) {
            res.add(0, listNode.val);
            listNode = listNode.next;
        }
        return res;
    }
}


// 栈辅助
// 时间复杂度:13ms
// 空间复杂度:9528kB
public class Solution {    
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> res = new ArrayList<>();
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while (!stack.isEmpty()) {
            res.add(stack.pop());
        }
        return res;
    }
}

// 递归法
// 时间复杂度:12 ms
// 空间复杂度:9384 KB
public class Solution {
	public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
		ArrayList<Integer> ret = new ArrayList<>();
		if (listNode != null) {
			ret.addAll(printListFromTailToHead(listNode.next));
			ret.add(listNode.val);
		}
		return ret;
	}
}



// 运行时间 13ms
// 占用内存 9656KB
import java.util.ArrayList;
import java.util.Collections;
import java.util.*;

public class Solution {
    private ArrayList<Integer> res;
    
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        this.res = new ArrayList<>();
        recur(listNode);
        return res;
    }
    
    private void recur(ListNode listNode) {
        if (listNode == null) {
            return;
        }
        recur(listNode.next);
        res.add(listNode.val);
    }
}

力扣

// 力扣与牛客网不同的是,它需要你返回int[],而不是ArrayList
// 所以其实是更难了

// 栈存法
// 时间复杂度:2 ms , 在所有 Java 提交中击败了 40.69% 的用户
// 空间复杂度:39 MB , 在所有 Java 提交中击败了 91.29% 的用户
import java.util.*;

class Solution {
    public int[] reversePrint(ListNode head) {
		Stack<Integer> stack = new Stack<Integer>();
		ListNode temp = head;
		while (temp != null) {
			stack.push(temp.val);
			temp = temp.next;
		}
		int size = stack.size();
		int[] result = new int[size];
		for (int i = 0; i <= result.length - 1; i++) {
			result[i] = stack.pop();
		}
		return result;
    }
}

// 递归法
// 时间复杂度:2 ms , 在所有 Java 提交中击败了 40.69% 的用户
// 空间复杂度:40.4 MB , 在所有 Java 提交中击败了 6.12% 的用户
import java.util.Arrays;

class Solution {
	
	ArrayList<Integer> tmp = new ArrayList<Integer>();
    public int[] reversePrint(ListNode head) {
		recur(head);
		int size = tmp.size();
		int[] result = new int[size];
		for (int i = 0; i <= size - 1; i++) {
			result[i] = tmp.get(i);
		}
		return result;
    }
	
	void recur(ListNode head) {
		if (head != null) {
			recur(head.next);
			tmp.add(head.val);
		}
		else {
			return;
		}
	}
}



【剑指offer】24. 反转链表

题目描述

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

// 力扣
// 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

// 牛客
// 输入一个链表,反转链表后,输出新链表的表头。

题解

// 方法应该有很多

 直接法 //
// 笨办法,直接遍历所有元素,存起来,再倒叙提取出来放进新构造的链表中

// 牛客
// 运行时间:12ms
// 占用内存:9920k
import java.util.Stack;
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null)
            return head;
		// 新建一个栈stack存储链表元素val,弹出的val正好是反转顺序
        Stack<Integer> stack = new Stack<>();  
        ListNode index = head;  // 初始化链表索引,从头遍历链表
		// while循环直到index到达链表尾后一位才停止,
		// 所以循环结束后index会到达链表尾结点的下一个位置
		// 我们将这个位置作为新链表的头结点,开始创建新链表
        while (index != null) {  
            stack.push(index.val);  // 每遍历个结点就将val压入栈存储
            index = index.next;  // 所以index右移
        }
		// index位置创建新结点,弹出一次stack存的val
        index = new ListNode(stack.pop());  
		// 当前index位置作为新链表的头结点,创建一个指向记为result
        ListNode result = index;  
		// index右移创建新结点,弹出stack存储的元素作为新结点的元素,
        while (!stack.isEmpty()) {  
            index.next = new ListNode(stack.pop());  // 创建新结点
            index = index.next;  // index右移
        }
        return result;  // 返回刚刚新链表(片段)的头结点
    }
}


// 力扣
// 执行用时:2 ms, 在所有 Java 提交中击败了7.85%的用户
// 内存消耗:38.2 MB, 在所有 Java 提交中击败了71.94%的用户
import java.util.Stack;
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null)
            return head;
        Stack<Integer> stack = new Stack<>();  
        ListNode index = head; 
        while (index != null) {  
            stack.push(index.val);
            index = index.next; 
        }
        index = new ListNode(stack.pop());
        ListNode result = index;  
        while (!stack.isEmpty()) {  
            index.next = new ListNode(stack.pop());
            index = index.next;  
        }
        return result;
    }
}

 头插法 /
// 这个方法画图就懂了,是比较推荐的方法

// 牛客
// 运行时间:10ms
// 占用内存:9780k
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null)
            return head;
		ListNode newList = new ListNode(-1);
		while (head != null) {
			ListNode next = head.next;
			head.next = newList.next;
			newList.next = head;
			head = next;
		}
		return newList.next;
    }
}


// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38 MB, 在所有 Java 提交中击败了91.31%的用户
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null)
            return head;
		ListNode newList = new ListNode(-1);
		while (head != null) {
			ListNode next = head.next;
			head.next = newList.next;
			newList.next = head;
			head = next;
		}
		return newList.next;
    }
}
// 三指针法 
// 也是比较推荐的方法,我画一个示意图方便理解

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38 MB, 在所有 Java 提交中击败了92.77%的用户

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
		ListNode pre = null;
		ListNode nex = null;
		ListNode cur = head;
		while (cur != null) {
			nex = cur.next;
			cur.next = pre;
			pre = cur;
			cur = nex;
		}
		return pre;
    }
}

// 牛客
// 运行时间:11ms
// 占用内存:9876k
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
		ListNode pre = null;
		ListNode nex = null;
		ListNode cur = head;
		while (cur != null) {
			nex = cur.next;
			cur.next = pre;
			pre = cur;
			cur = nex;
		}	
		return pre;
    }
}

三指针法示意图:

在这里插入图片描述

/ 递归法 /

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:38.5 MB, 在所有 Java 提交中击败了32.42%的用户
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
        ListNode next = head.next;
        head.next = null;
        ListNode newHead = reverseList(next);
        next.next = head;
        return newHead;
    }
}

// 牛客
// 运行时间:12ms
// 占用内存:10048k
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
        ListNode next = head.next;
        head.next = null;
        ListNode newHead = ReverseList(next);
        next.next = head;
        return newHead;
    }
}




【剑指offer】25. 合并两个排序的链表

题目描述

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

// 25. 合并两个排序的链表

// 输入两个递增排序的链表,合并这两个链表并使新链表中的
// 节点仍然是递增排序的。

// 输入两个单调递增的链表,输出两个链表合成后的链表,当然我
// 们需要合成后的链表满足单调不减规则。

题解

// 遍历法 
// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了98.60%的用户
// 内存消耗:38.3 MB, 在所有 Java 提交中击败了95.77%的用户
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		if (l1 == null)
			return l2;
		if (l2 == null)
			return l1;
		ListNode head = new ListNode(-1);
		ListNode cur = head;  // 单结点
		
		// l1和l2这两个给定的指针作为遍历指针,在cur的前面遍历
		while (l1 != null && l2 != null) {
			// 谁小,cur.next会指向谁
			if (l1.val <= l2.val) {  // 如果l1值比l2值小
				cur.next = l1;  // cur.next指向l1
				l1 = l1.next;  // l1指针右移
			}
			else {
				cur.next = l2;
				l2 = l2.next;
			}
			// 上面的if else判据里,
			// 如果l1值比l2值小,之前cur.next会指向l1,此时cur右移到之前l1位置
			// 如果l2值比l1值小,之前cur.next会指向l2,此时cur右移到之前l2位置
			cur = cur.next;  
		}
		// 如果有链表没遍历完(该链表比另一个链表长)
		// cur.next指向该链表当前指针,直接连上长链表未遍历的尾巴
		if (l1 != null) {
			cur.next = l1;
		}
		if (l2 != null) {
			cur.next = l2;
		}
		return head.next;  // 返回head.next
    }
}

// 牛客
// 运行时间:16ms
// 占用内存:9948k
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null)
            return list2;
        if (list2 == null)
            return list1;
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                cur.next = list1;
                list1 = list1.next;
            }
            else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        if (list1 != null) {
            cur.next = list1;
        }
        if (list2 != null) {
            cur.next = list2;
        }
        return head.next;
    }
}



// leetcode中的不同解法
// 运行时间:19ms 超过77.29%用Java提交的代码
// 占用内存:9916KB 超过70.27%用Java提交的代码
public class Solution {
    public ListNode Merge(ListNode l1,ListNode l2) {
        if (l1 == null)
            return l2;
        else if (l2 == null)
            return l1;

        ListNode temp = new ListNode(0);
        ListNode res;
        if (l1.val <= l2.val) res = l1;
        else res = l2;

        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                while (l1.next != null && l1.next.val <= l2.val) 
                    l1 = l1.next;
                temp = l1;
                l1 = l1.next;
                temp.next = l2;
            }
            else { // l1.val > l2.val
                while (l2.next != null && l1.val > l2.next.val) 
                    l2 = l2.next;
                temp = l2;
                l2 = l2.next;
                temp.next = l1;
            }
        }
		return res;
    }
}

遍历法图解(非leetcode中的解法)

在这里插入图片描述

/ 递归法 
// 递归思路与遍历法是一样的,所以还是推荐遍历法,只是理解起来好看
// ,其资源消耗不可谓不小,因此该方法在牛客是无法通过的

// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了98.60%的用户
// 内存消耗:38.8 MB, 在所有 Java 提交中击败了26.71%的用户
class Solution {
	public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		if (l1 == null) {
			return l2;
		}
		if (l2 == null) {
			return l1;
		}
		ListNode head = new ListNode(-1);
		if (l1.val <= l2.val) {
			head = l1;
			head.next = mergeTwoLists(l1.next, l2);
		}
		else {
			head = l2;
			head.next = mergeTwoLists(l1, l2.next);
		}
		return head;
	}
}


在这里插入图片描述



【剑指offer】35. 复杂链表的复制

题目描述

在这里插入图片描述

在这里插入图片描述

// 35. 复杂链表的复制

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


// 牛客
// 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下
// 一个节点,另一个特殊指针random指向一个随机节点),请对此链表进
// 行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参
// 数中的节点引用,否则判题程序会直接返回空)

题解

复制结点部分图解:

在这里插入图片描述

random指向图解:

在这里插入图片描述

// 力扣
// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:37.5 MB, 在所有 Java 提交中击败了98.32%的用户
/*
// 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) {
		if (head == null)
			return null;
		// 每一个结点之后插入其复制结点
		Node cur = head;  // 初始化遍历指针
		while (cur != null) {
			Node clone = new Node(cur.val);  // 复制cur遍历的原链表结点,记为clone
			clone.next = cur.next;  // clone.next与cur.next一致
			cur.next = clone;  // 然后让cur.next指向clone本身
			cur = clone.next;  // 移动cur到下一个原链表结点
		}
		// 构建random指向
		cur = head;  // 重置cur回head
		while (cur != null) {  // 循环遍历链表
			// 令clone结点为cur的下一个结点(clone结点即为cur的复制结点)
			Node clone = cur.next;  
			if (cur.random != null)  // 如果cur.random不为null
				// 由于cur和clone的关系,clone是cur的下一个结点
				// clone.random即为cur.random的下一个结点
				clone.random = cur.random.next;  
			cur = clone.next;  // cur移动至下一个原链表结点
		}
		// 两个链表拆分
		cur = head;  // 重置cur回head
		Node cloneHead = head.next;  // 设定复制链表的头结点
		while (cur.next != null) {  // 循环遍历,直到cur.next为空
			// 令cur.next为next,到此我们有cur和next(cur.next)双指针
			Node next = cur.next;  
			// 令cur的next直接指向next.next(cur.next.next)
			// 原链表cur越过了cur的复制点,连上了原链表cur的下一个结点
			cur.next = next.next; 
			cur = next;  // cur右移
		}
		return cloneHead;  // 拆分结束,最后返回复制链表头结点
    }
}


// 牛客
// 运行时间:12ms
// 占用内存:9804k
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if (pHead == null)
            return null;
        // 每个结点复制
        RandomListNode cur = pHead;
        while (cur != null) {
            RandomListNode clone = new RandomListNode(cur.label);
            clone.next = cur.next;
            cur.next = clone;
            cur = clone.next;
        }
        // random指向部署
        cur = pHead;
        while (cur != null) {
            RandomListNode clone = cur.next;
            if (cur.random != null)
                clone.random = cur.random.next;
            cur = clone.next;
        }
        // 分离
        cur = pHead;
        RandomListNode cloneHead = pHead.next;
        while (cur.next != null) {
            RandomListNode next = cur.next;
            cur.next = next.next;
            cur = next;
        }
        return cloneHead;
    }
}




【剑指offer】52. 两个链表的第一个公共结点

题目描述
在这里插入图片描述

在这里插入图片描述

// 52. 两个链表的第一个公共结点

// 力扣
// 输入两个链表,找出它们的第一个公共节点。

// 牛客
// 输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,
// 所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)


题解

// HashSet法,
// 构建hashset用于存储遍历过的结点。
// 先让指针cur从headA移动,将遍历过的结点存入set。遍历完之后,
// 让cur回到headB,再遍历一次B链表,结点存入set,存不进的结点就是
// 相交的起点。
// 执行用时:10 ms, 在所有 Java 提交中击败了10.60%的用户
// 内存消耗:41.9 MB, 在所有 Java 提交中击败了10.07%的用户
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();
        ListNode cur = headA;
        while (cur != null) {
            set.add(cur);
            cur = cur.next;
        }
        cur = headB;
        while (cur != null) {
            if (!set.add(cur))
                return cur;
            cur = cur.next;
        }
        return null;
    }
}

// 牛客
// 双指针
// 假设俩链表A B,俩链表的公共长度是P,
// A链表的总长度是 A(私有链)+P(公共链)
// B链表的总长度是 B(私有链)+P(公共链),那么肯定有A+P+B = B+P+A。
// 我们让两个指针p1, p2分别从A B链表头出发,p1指针从A表头出发,
// 走A->P->B的路线,p2指针从B表头出发,走B->P->A的路线,最后两个指针
// 一定会在第一个公共结点相遇。
// 运行时间:12ms,超过95.24%用Java提交的代码
// 占用内存:9816KB,超过74.81%用Java提交的代码
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
		ListNode p1 = pHead1;
		ListNode p2 = pHead2;
		while (p1 != p2) {
			if (p1 != null)
				p1 = p1.next;
			else 
				p1 = pHead2;
			
			if (p2 != null)
				p2 = p2.next;
			else 
				p2 = pHead1;
		}
		return p1;
    }
}



// 牛客
// 运行时间:16ms,超过74.58%用Java提交的代码
// 占用内存:9988KB,超过73.19%用Java提交的代码
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
		ListNode p1 = pHead1;
		ListNode p2 = pHead2;
		while (p1 != p2) {
			p1 = (p1 == null) ? pHead2 : p1.next;
			p2 = (p2 == null) ? pHead1 : p2.next;
		}
		return p1;
    }
}


// 力扣
// 执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:41.1 MB, 在所有 Java 提交中击败了82.68%的用户
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA, p2 = headB;
        while (p1 != p2) {
            p1 = (p1 == null) ? headB : p1.next;
            p2 = (p2 == null) ? headA : p2.next;
        }
        return p1;
    }
}


特殊解——双指针

【剑指offer】18. 删除链表的节点

题目描述

在这里插入图片描述

// 力扣
// 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
// 返回删除后的链表的头节点。
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */

题解

// 双指针法 //
// 对数据结构有印象的就知道,删除一个链表结点,需要找待删除结点的前一个结点
// 令前一个结点直接指向待删除结点的next,这个待删除结点就从链表结构中被去除了。


// 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
// 内存消耗:37.9 MB, 在所有 Java 提交中击败了71.42%的用户
class Solution {
	public ListNode deleteNode(ListNode head, int val) {
        if (head == null)
            return null;
        // 假定val是一定找得到的,head.next不存在,那待删除结点是head本身,删除后返回null
        if (head.next == null)  
            return null;
        // 如果待删除节点是头结点,直接返回head.next
        if (head.val == val && head.next != null)
            return head.next;
		
		// new一个链表引用pre,它的next指向head,即head前面多了一个节点
		// 这个引用指针用来遍历待删除节点的前一个结点
		ListNode pre = new ListNode(0);  // pre.val初始化为0(无影响)
		pre.next = head;
		
		// 定义链表引用cur,它直接指向head,
		// 这个引用指针用于遍历整个链表
		ListNode cur = head;
		while (cur != null) {
			// 非尾结点
			// 若cur指针找到了val所在的结点
			if (cur.val == val && cur.next != null) {
				// 令pre.next直接越过待删除结点cur
				// 直接指向cur的下一个结点cur.next
				pre.next = cur.next;  
				cur = null;  // 规范操作,令cur元素清空
				break;
			}
			// 尾结点
			if (cur.val == val && cur.next == null) {
                pre.next = null;  // 直接令pre.next指向null
				break;
			}
			pre = pre.next;  // 没找到val,右移一位
			cur = cur.next;  // 没找到val,右移一位
		}
		return head;  // 题目要求返回删除后的链表,返回头引用即可
	}
}
// 递归法 //

class Solution {

	// 解题函数,也是递归函数
	// 递归函数检查head.next是否为待删除结点
	public ListNode deleteNode(ListNode head, int val) {
		// 以下三个if语句跟双指针法是一样的,但是意义不一样
		
		// 若head第一个结点就不存在,返回null
		if (head == null)
            return null;
        // 1.当deleteNode还没被递归调用时:
		// 此时head还指向头结点,如果head.next为null,说明整个链表就一个结点,删除之后为null
		// 2.当deleteNode被递归调用后:
		// 由于递归调用的语句是head.next = deleteNode(head.next, val);
		// 因此字段head实际表示head.next,那字段head.next实际表示head.next.next,
		// 若head.next.next为null,说明head.next是待删除尾结点,直接返回null,
		// 这时候head.next会被指向这个返回的null,删除完成
		// (注:如果这句if能触发,说明在head.next到达尾结点之前一直都没return过
		//   说明尾结点必为待删除结点)
        if (head.next == null)  
            return null;
        // 1.当deleteNode还没被递归调用时:
		// 如果待删除节点是头结点,直接返回head.next
		// 2.当deleteNode被递归调用后:
		// head实际表示head.next,所以如果head.next.val == val
		// 说明head.next是待删除结点,返回head.next,也就是返回
		// head.next.next,这时head.next会指向这个返回的head.next.next
		// 原来的head.next从链表中被移除。
        if (head.val == val && head.next != null)
            return head.next;
		// 将head.next指向deleteNode的返回值
		// deleteNode将被递归调用,输入为head.next和val
		head.next = deleteNode(head.next, val);
		// 递归完成,返回删除好的链表头索引head
		return head;
	}
}



【剑指offer】18.2 删除链表中重复的结点

题目描述

在这里插入图片描述

// 18.2 删除链表中重复的结点

// 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,
// 重复的结点不保留,返回链表头指针。例如,链表1->2->3->3->4->4->5
// 处理后为 1->2->5

题解


 双指针 
// 牛客


// 运行时间:14ms
// 占用内存:9916k
public class Solution{
	public ListNode deleteDuplication(ListNode pHead) {
		if (pHead == null || pHead.next == null)
			return pHead;
		ListNode dummy = new ListNode(-1);
		dummy.next = pHead;
		
		ListNode pre = dummy;
		ListNode cur = pHead;
		cur = cur.next;  // 先让cur右移一格,我们比较的是pre.next和cur
		while (cur != null) {
			// 如果发现重复结点
			if (pre.next.val == cur.val) {
				if (cur.next == null) {  // 判断cur右移后是否到达链表尾
					pre.next = null;  // 到达链表尾,就直接让pre.next指向null
					break;  // 打断循环,返回结果
				}
				
				cur = cur.next;  // cur继续右移(直到重复串的尾部)
				// 如果右移后cur到达重复子串的尾部
				if (pre.next.val != cur.val) { 
					pre.next = cur;  // 直接令pre.next指向cur
					cur = cur.next;  // cur右移一格
				}
			}
			// 未发现重复结点,一起右移
			else {  
				pre = pre.next;
				cur = cur.next;
			}
		}
		return dummy.next;  // 不要返回pHead头节点了
	}
}


// 简化版
// 运行时间:14ms
// 占用内存:9984k
public class Solution{
	public ListNode deleteDuplication(ListNode pHead) {
		if (pHead == null || pHead.next == null)
			return pHead;
		ListNode dummy = new ListNode(-1);
		dummy.next = pHead;
		
		ListNode pre = dummy;
		ListNode cur = pHead;
		while (cur != null) {
			// 如果发现重复结点
			if (cur.next != null && cur.next.val == cur.val) {
				while (cur.next != null && cur.next.val == cur.val) {
					cur = cur.next;  // cur继续右移(直到重复串的尾部)
				}
				// 再前进一格
				cur = cur.next;
				pre.next = cur;
			}
			// 未发现重复结点,一起右移
			else {  
				pre = pre.next;
				cur = cur.next;
			}
		}
		return dummy.next;
	}
}

/// 递归法 //
// 牛客
// 运行时间:11ms
// 占用内存:9948k
public class Solution {
	public ListNode deleteDuplication(ListNode pHead) {
		// 未开始递归时,pHead是头结点,开始递归后,pHead为遍历结点

		if (pHead == null || pHead.next == null) {
			return pHead;
		}
		// 1.未开始递归时,取头结点next记为next
		// 2.开始递归后,pHead为遍历节点,且实际被pHead.next指向,
		// next为pHead.next,所以实际被pHead.next.next指向
		ListNode next = pHead.next;  
		// 1.未开始递归时,若头结点出现重复,则if成立,
		// 直接开始while循环找重复子串尾结点
		// 2.开始递归后,如果pHead和next的val相等
		// 开始while循环找重复子串尾结点
		if (pHead.val == next.val) {
			// while循环继承了if的判断,同时保证next(pHead.next.next)不为null
			while (next != null && pHead.val == next.val) 
				next = next.next;  // next右移,直到遍历到重复子串的下一个结点
			// 返回deleteDuplication递归调用
			// 递归到底并返回之后,由于next被pHead.next指向,所以
			// next循环右移就已经剔除了重复结点
			return deleteDuplication(next);  
		}
		// 若头结点未出现重复,else成立
		else {
			// 令pHead.next指向eleteDuplication的递归调用,输入pHead.next
			// 假设链表一直没有重复节点,函数输入pHead实际是被上一个递归
			// 的pHead.next指向,再递归取pHead.next,对于上一次递归
			// 就是pHead.next.next,一直这样下去,实际就是遍历了整个
			// 链表一次。
			pHead.next = deleteDuplication(pHead.next);
			return pHead;
		}
	}
}



【剑指offer】22. 链表中倒数第k个节点

题目描述
在这里插入图片描述
在这里插入图片描述

// 力扣
// 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,
// 本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有
// 6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的
// 倒数第3个节点是值为4的节点。



// 牛客
// 输入一个链表,输出该链表中倒数第k个结点。

题解

 双指针滑窗 //
// 由于需要返回以倒数第k个结点为头结点。第k结点往后的链表。
// 假如1->2->3->4->5,k = 2。我们需要返回的就是4->5。
// 那么我们就从链表头构建两个指针pre和end,end先右移扩大窗口,
// 直到pre到end的窗口的长度就等于第k结点往后的链表长度为止。
// 再以这个窗口滑动遍历链表,窗口右顶点到达链表尾即可返回。


// 力扣
// 力扣的版本比较简单,给定的k不会为0,也不会超过链表的长度,比较容易理解
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if (head == null)  // 特殊情况判定
            return head;
        ListNode pre = head;  // 滑窗前指针
        ListNode end = head;  // 滑窗后指针
        int i = 1;  // 
		// 将end右移,直到pre到end的窗口长度等于k到链表尾的长度
        while (i < k) {  
            end = end.next;
            i += 1;
        }
        while (end.next != null) {  // 窗口右移,直到右顶点到达链表尾
            end = end.next;
            pre = pre.next;   
        }
        return pre;  // 此时的窗口左顶点位置即为倒数第k结点位置,直接返回
    }
}

// 牛客
// 运行时间:14ms
// 占用内存:9976k
public class Solution {
    public ListNode FindKthToTail(ListNode head, int k) {
        if (head == null)
            return head;
        if (k == 0)  // 如果k是0,返回null
            return null;
        
        ListNode pre = head;
        ListNode end = head;
        int i = 1;
		// 保证不要超过链表长度范围,把end指针向后,移动次数记为i
        while (i != k && end.next != null) {  
            end = end.next;  
            i += 1;  // end右移一次,i计数一次
        }
		// 如果k大于整个链表长度,i的大小是达不到k的,所以直接返回null
        if (i < k)  
            return null;
		// 如果k没有大于整个链表长度,滑窗
        while (end.next != null) {
            end = end.next;
            pre = pre.next;   
        }
        return pre;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/579657.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

六级备考23天|CET-6|翻译技巧4|2013年官方样题|新年|9:45~11:00

目录 1 PRACTICE ANSWER 2 PRACTICE ANSWER 3 ​ PRACTICE ANSWER 4 PRACTICE ANSWER 5 PRACTICE ANSWER 6 ​ PRACTICE ANSWER ​​​​​​​ 答案整合​​​​​​​ 1 PRACTICE Chinese new year is the Chinese most important traditional festival, wh…

2023上半年软考系统分析师科目一整理-02

2023上半年软考系统分析师科目一整理-02 1. 安全2. 知识产权 1. 安全 对称加密算法中&#xff0c;由于加密解密都使用同样的密钥&#xff0c;所以密钥需要进行共享&#xff0c;故也被称共享密钥算法。 三重DES加密是使用2个DES密钥&#xff0c;进行多次操作来完成的&#xff…

Redis相关

Redis基本概念 一、Redis的持久化方式二、Redis的单机、主从、哨兵、集群Redis主从复制的原理 三、Redis分布式锁的实现四、缓存穿透 击穿 雪崩 一、Redis的持久化方式 1&#xff09;RDB方式 2&#xff09;AOF方式 二、Redis的单机、主从、哨兵、集群 单机的问题&#xf…

机器学习 | SVD奇异值分解

本文整理自哔哩哔哩视频&#xff1a;什么是奇异值分解SVD–SVD如何分解时空矩阵 &#x1f4da;奇异值分解是什么&#xff1f; M是原始矩阵&#xff0c;它可以是任意的矩阵&#xff0c;奇异值分解就是将它分解为三个矩阵相乘。U和V是方阵&#xff0c;∑是不规则矩阵&#xff0c;…

django组件552

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

618京东预售一般便宜多少?跟直接买有啥区别?

618京东预售一般便宜多少?跟直接买有啥区别? 京东作为消费者比较喜欢的电商购物平台之一&#xff0c;经常会推出促销打折的活动&#xff0c;以吸引用户到平台上购物。在这些大促活动中&#xff0c;平台会在预售环节设置专属的优惠&#xff0c;让消费者下单提前锁定这些折扣&a…

一、stable diffusion的发展史

一、stable diffusion的发展史 本文目标&#xff1a;学习交流 对于熟悉SD的同学&#xff0c;一起学习和交流使用过程中的技巧和心得。 帮助新手 帮助没有尝试过SD但又对它感兴趣的同学快速入门&#xff0c;并且能够独立生成以上效果图。 1.发展史介绍&#xff1a; 2015年的时候…

RepGhost 解析

paper&#xff1a;RepGhost: A Hardware-Efficient Ghost Module via Re-parameterization official implementation&#xff1a;https://github.com/chengpengchen/repghost 存在的问题 特征重用feature reuse是轻量网络设计中常用的一种技术&#xff0c;现有的方法通常使…

[元带你学: eMMC协议详解 10] Device 识别流程 与 中断模式

依JEDEC eMMC 5.1及经验辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 所在专栏 《元带你学: eMMC协议详解》 全文2700字&#xff0c;重点需掌握设备识别过程&#xff08;CMD1 -> CMD2 -> CMD3&#xff09;, 这很常用&#xff0c; 也是最容易出现异常的地方。其他…

Git进阶之代码回滚、合并代码、从A分支选择N次提交,合并到B分支【revert、merge、rebase、cherry-pick】

B站视频地址&#xff1a; https://www.bilibili.com/video/BV1KX4y1a7N9 Git学习文档&#xff1a;https://d9bp4nr5ye.feishu.cn/wiki/PeDPw3mm3iFA36k9td9cVeignsZ 在很长一段时间里&#xff0c;我对Git的操作只限于&#xff1a;提交代码&#xff0c;拉取代码&#xff0c;合…

研报精选230528

目录 【行业230528华金证券】传媒行业深度研究&#xff1a;AIGC最新应用与场景研究 【行业230528国海证券】电动船舶行业深度报告&#xff1a;绿色智能大势已至&#xff0c;驶向电化百亿蓝海 【行业230528华西证券】纺织服装行业周报&#xff1a;5月增长放缓无碍中长期出清逻辑…

Linux下的yum和vim

目录 一、Linux软件包管理器yum1.1 何为软件包&#xff1f;1.2 rzsz工具1.3 如何安装和卸载软件&#xff1f;1.4 Linux的软件生态 二、vim文本编辑器 一、Linux软件包管理器yum 1.1 何为软件包&#xff1f; 软件包可以理解成是windows下别人提前编译好的安装包程序&#xff0…

任务7 课程信息管理系统

系列文章 任务7 课程信息管理系统 已知课程的信息包括&#xff1a;课程编号&#xff0c;课程名称&#xff0c;课程性质&#xff08;必修、选修&#xff09;&#xff0c;课时&#xff0c;学分&#xff0c;考核方式&#xff08;考试、考查课&#xff09;&#xff0c;开课学期&a…

day41_servlet

今日内容 零、 复习昨日 一、Cookie 二、Session 三、拦截器 四、登录认证、全局编码格式 零、 复习昨日 注解 热部署 请求转发 重定向 路径问题 总结使用经验: 无论请求路径是多层是单层,在写路径时都从/开始,即从根开始如果是服务器动作,从/开始直接写如果是浏览器动作,从/开…

CVPR 2018 | Spotlight论文:单摄像头数秒构建3D人体模型

想把自己的身体形象投射进电子游戏里?现在已经是很容易的事了。人工智能算法此前已被广泛应用于虚拟现实头像、监视、服装试穿或电影等多种任务的人体建模上,但大多数方法需要特殊的照相设备来检测景深,或从多个角度探查人体。近日,来自德国布伦瑞克工业大学和 Max Planck …

js获取Element元素的常用方法

js中获取Element元素的常用方法有以下四种&#xff1a; 【方法一】根据元素ID&#xff1a;document.getElementById() 【方法二】根据元素标签&#xff1a;document.getElementsByTagName() 【方法三】根据元素class名&#xff1a;document.getElementsByClassName() 【方法…

yolov5刚开始train时的环境问题

torch会自动被requirement.txt替换 在对yolov5_5.0进行pip install requirement.txt后&#xff0c;yolo5_5.0会将虚拟环境中中的torch替换为2.0.1版本的&#xff0c;但要注意查看该torch是否为gpu版本&#xff0c;查看方式如下&#xff1a;打开Anaconda Prompt&#xff0c;激活…

渗透测试 | 端口扫描

0x00 免责声明 本文仅限于学习讨论与技术知识的分享&#xff0c;不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;本文作者不为此承担任何责任&#xff0c;一旦造成后果请自行承担…

推荐系统算法详解

文章目录 基于人口统计学的推荐算法用户画像 基于内容的推荐算法相似度计算基于内容推荐系统的高层次结构特征工程数值型特征处理类别特征处理时间型特征处理统计型特征处理 推荐系统常见反馈数据基于UGC的推荐TF-IDFTF-IDF算法示例1. 引入依赖2. 定义数据和预处理3. 进行词数统…

12.区块链系列之比特币NFT

1. NFT协议Ordinals 2023年1月30日&#xff0c;比特币核心开发者Casey Rodarmor创建了NFT协议Ordinals Ordinals序数: 比特币的最小单位是Satoshi聪,1BTC1亿聪&#xff0c;每个聪的比特币都是同质化代币&#xff0c;它们之间并没有任何差别。Ordinals给聪打上了编号&#xff0…