目录
9.排序链表(O(nlogn)时间复杂度、空间复杂度为O1)
一、链表
1.环形链表
方法一:哈希表
方法二:双指针法:
判断是否有环:
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
判断入口节点
参考链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode/
Floyd 算法
想法
当然一个跑得快的人和一个跑得慢的人在一个圆形的赛道上赛跑,会发生什么?在某一个时刻,跑得快的人一定会从后面赶上跑得慢的人。
算法
Floyd 的算法被划分成两个不同的 阶段 。在第一阶段,找出列表中是否有环,如果没有环,可以直接返回 null 并退出。否则,用 相遇节点 来找到环的入口。
阶段 1
1.设定两个初始指针 快指针F 和慢指针S
2.S走到环的入口0时,假设F已经走到距离环入口H的位置
3.那么再走 C-H :
S-》C-H
F-》H+2(C-H)=2C-H=C-H
此时S和F走到一起,证明了F和S指针最终会相遇
阶段2
1.假设F和S现在同时处于距离入口H的位置
2.在相遇前S走的距离是 F+nl+a
注:F是起点到环入口距离 , l是环的长度 , n是走了几圈 , a 是相遇点(离入口距离为a个单位)3. F在相遇前走的距离是:F+ml+a
4.因为F的速度是S的两倍,所以距离也是S的两倍,因此S的路程*2=F的路程
即:2*(F+nl+a) = F+ml+a
-》F+(2*n-m)l+a = 0
注:b为入口到交接点的距离,a是交接点到入口的距离,-》a=l-b-》F+(2*n-m+1)l = b
-》F=b
即表示,从起点走F步和h走F步会同时到达入口处
总结:
1.快慢指针一定会相遇
2.快慢指针相遇后,同时不断向下走一步直至相遇,相遇点就是环的入口点
编码实现:
1.判断是否有环
2.找到第一次相遇的节点
3.从相遇节点和头结点同时往后走,直到相遇,就是入口节点
public class Solution {
private ListNode getIntersect(ListNode head) {
ListNode tortoise = head;
ListNode hare = head;
// A fast pointer will either loop around a cycle and meet the slow
// pointer or reach the `null` at the end of a non-cyclic list.
while (hare != null && hare.next != null) {
tortoise = tortoise.next;
hare = hare.next.next;
if (tortoise == hare) {
return tortoise;
}
}
return null;
}
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
// If there is a cycle, the fast/slow pointers will intersect at some
// node. Otherwise, there is no cycle, so we cannot find an e***ance to
// a cycle.
ListNode intersect = getIntersect(head);
if (intersect == null) {
return null;
}
// To find the e***ance to the cycle, we have two pointers traverse at
// the same speed -- one from the front of the list, and the other from
// the point of intersection.
ListNode ptr1 = head;
ListNode ptr2 = intersect;
while (ptr1 != ptr2) {
ptr1 = ptr1.next;
ptr2 = ptr2.next;
}
return ptr1;
}
}
拓展:计算出环的长度
第一步找到第一次相遇点h
然后快慢指针按照原先速度继续前进,再次相遇所走过的数量,就是环的长度
因为他们相遇时,快指针走了两圈,而慢指针走了一圈
public int getLoopLength(ListNode head) {
ListNode slow = head;
ListNode fast = head.next;
while (fast != null || fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast)break; //第一次相遇
}
slow = slow.next;
fast = fast.next.next;
int len = 1;
while (fast!=slow){ //再次相遇
slow = slow.next;
fast = fast.next.next;
len++;
}
//当slow再次与fast相遇,得到环的长度
return len;
}
2反转链表
//链表的反转题:要注意next尽量都要放到判断语句中去赋值,避免越界
public ListNode reverse(ListNode head){
if (head==null)return null;
ListNode pre = null; //存储前一个节点
ListNode next = null; //存储后一个节点,断开后可以使用
while (head!=null){
next=head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
//递归
public ListNode Rreverse(ListNode head){
if (head==null||head.next==null)return head;
//递归反转子链表
ListNode newListNode = Rreverse(head.next);
//将下一个元素指向自己,自己指向null,实现反转
head.next.next = head;
head.next = null ;
return newListNode;
}
3.反转部分链表
1 2 3 4 5 [2,,4]
1 4 3 2 5
package IMUHERO;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
public class Main {
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummyHead=new ListNode(-1);
dummyHead.next=head;
ListNode preOne=dummyHead;
ListNode pre=dummyHead;
ListNode cur=head;
ListNode nextNode;
ListNode reverseEnd;
int i=0;
//先把节点移动到范围的前一个位置(preOne表示反转的前一个,cur表示反转的第一个)
while (i<m-1){
i++;
preOne=preOne.next;
cur=cur.next;
}
//让pre成为反转的第一个,cur成为反转的第二个
pre=cur;
cur=cur.next;
reverseEnd=preOne.next;//记录一个还没反转的第一个(之后它就是尾节点)
while (i<n-1){
nextNode=cur.next; //特别注意,这个节点是第三个节点,很容易越界,必须要放在i<n-1判断里面才去实现它
cur.next=pre;
pre=cur;
cur=nextNode;
i++;
}
//最后pre所在位置成为反转范围内的头结点,将首尾相连
preOne.next=pre;
reverseEnd.next=cur;
return dummyHead.next;
}
}
4.反转K个一组的链表
反转K个一组的链表
在做这道题之前,我们不仿先来看看如果从头部开始组起的话,应该怎么做呢?
例如:链表:1->2->3->4->5->6->7->8->null, K = 3。
调整后:3->2->1->6->5->4->7->8->null。
其中 7,8不调整,因为不够一组
public ListNode reverseK(ListNode head , int k){
ListNode temp = head; //temp用于移动找到第k个节点
for (int i=1;i<k&&temp!=null;i++){
temp=temp.next;
}
if (temp==null)return head; //如果不满K,则直接返回head
ListNode nextListNode = temp.next; //这是下一部分K节点的起始
temp.next = null; //断开连接,方便反转
ListNode newListNode = reverse(head); //按照反转整个链表的思路来反转
ListNode nextFirst = reverseK(nextListNode,k); //递归进行
head.next = nextFirst; //将前后部分的头尾相接
return newListNode; //返回的是部分反转后的头结点
}
//反转一个链表
private ListNode reverse(ListNode head){
if (head==null||head.next==null)return head;
ListNode newListNode = reverse(head.next);
head.next.next = head;
head.next = null;
return newListNode;
}
5.从后往前K个一组反转链表
给定一个单链表的头节点 head,实现一个调整单链表的函数,使得每K个节点之间为一组进行逆序,并且从链表的尾部开始组起,头部剩余节点数量不够一组的不需要逆序。(不能使用队列或者栈作为辅助)
例如:
链表:1->2->3->4->5->6->7->8->null, K = 3。那么 6->7->8,3->4->5,1->2各位一组。调整后:1->2->5->4->3->8->7->6->null。其中 1,2不调整,因为不够一组。
基本思路和上一题是一样的,只要先整体反转一次链表,然后使用上一题的解法,再反转一次链表即可
-》两次反转,相当于没有反转
public ListNode backReverseK(ListNode head, int k) {
// 调用逆序函数
head = reverse(head);
// 调用每 k 个为一组的逆序函数(从头部开始组起)
head = reverseK(head, k);
// 在逆序一次
head = reverse(head);
return head;
}
+第四题的代码
6.两数相加I
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
public ListNode addTwoNumbersI (ListNode l1 ,ListNode l2){
ListNode dummyHead = new ListNode(-1);
ListNode cur = dummyHead;
int carry = 0;
int sum = 0;
while (l1!=null||l2!=null){
int a = l1!=null?l1.val:0;
int b = l2!=null?l2.val:0;
sum = a+b+carry; //注意此处取余
carry = sum/10;
cur.next = new ListNode(sum%10); //注意!要在此处%10
cur = cur.next; //注意此处cur = cur.next
if (l1!=null)l1 = l1.next; //注意再次判断是否为Null
if (l2!=null)l2 = l2.next;
}
if (carry==1){ //注意最后一次可能为1
cur.next = new ListNode(1);
}
return dummyHead.next;
}
7.两数相加II
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
解法一:使用栈
// 1->2->3->4
// + 9->8->7->6
// ——————————————
// 1->1->1->1->0
public ListNode addTwoNumbersII (ListNode l1 ,ListNode l2){
Stack<ListNode> stack1 = new Stack();
Stack<ListNode> stack2 = new Stack();
while (l1!=null){
stack1.push(l1);
l1 = l1.next;
}
while (l2!=null){
stack2.push(l2);
l2 = l2.next;
}
int sum = 0;
int carry = 0;
ListNode pre = null;
ListNode cur = null;
while (!stack1.isEmpty()||!stack2.isEmpty()){
int a = stack1.isEmpty()?0: stack1.pop().val;
int b = stack2.isEmpty()?0: stack2.pop().val;
sum =a+b+carry;
carry =sum/10;
cur = new ListNode(sum%10);
cur.next = pre;
pre = cur;
}
if (carry == 1){
cur = new ListNode(1);
cur.next = pre;
}
return cur;
}
解法二:先反转后求和
// 1->2->3->4
// + 9->8->7->6
// ——————————————
// 1->1->1->1->0
public ListNode addTwoNumbersII (ListNode l1 ,ListNode l2){
ListNode n1 = reverse(l1);
ListNode n2 = reverse(l2);
ListNode newListNode = addTwoNumbersI(n1,n2);
return reverse(newListNode);
}
public ListNode reverse(ListNode head){
if (head==null||head.next==null)return head;
ListNode newListNode = reverse(head.next);
head.next.next = head;
head.next = null;
return newListNode;
}
public ListNode addTwoNumbersI (ListNode l1 ,ListNode l2){
ListNode dummyHead = new ListNode(-1);
ListNode cur = dummyHead;
int carry = 0;
int sum = 0;
while (l1!=null||l2!=null){
int a = l1!=null?l1.val:0;
int b = l2!=null?l2.val:0;
sum = a+b+carry; //注意此处取余
carry = sum/10;
cur.next = new ListNode(sum%10); //注意!要在此处%10
cur = cur.next; //注意此处cur = cur.next
if (l1!=null)l1 = l1.next; //注意再次判断是否为Null
if (l2!=null)l2 = l2.next;
}
if (carry==1){ //注意最后一次可能为1
cur.next = new ListNode(1);
}
return dummyHead.next;
}
8.归并链表
1.归并两个链表
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummyHead = new ListNode(-1);
ListNode cur1=l1;
ListNode cur2=l2;
ListNode cur=dummyHead;
while(cur1!=null&&cur2!=null){
if(cur1.val<=cur2.val){
cur.next=cur1;
cur1=cur1.next;
}
else{
cur.next=cur2;
cur2=cur2.next;
}
cur=cur.next;
}
cur.next=(cur1!=null)?cur1:cur2;
return dummyHead.next;
}
}
递归的归并写法
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
2.归并k个链表
(0)其他方法:
(1)方法一:优先队列
时间复杂度:O(Nlogk)
弹出操作时,比较操作的代价会被优化到 O(\log k)O(logk) 。同时,找到最小值节点的时间开销仅仅为 O(1)O(1)。
最后的链表中总共有 NN 个节点。空间复杂度:O(k)
每次都只将k个链表的最前面丢进去,然后把他们从优先队列取出
用容量为K的最小堆优先队列,把链表的头结点都放进去,然后出队当前优先队列中最小的,挂上链表,,然后让出队的那个节点的下一个入队,再出队当前优先队列中最小的,直到优先队列为空。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
ListNode dummyHead = new ListNode(0);
ListNode curr = dummyHead;
PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
for (ListNode list : lists) {
if (list == null) {
continue;
}
pq.add(list);
}
while (!pq.isEmpty()) {
ListNode nextNode = pq.poll();
curr.next = nextNode;
curr = curr.next;
if (nextNode.next != null) {
pq.add(nextNode.next);
}
}
return dummyHead.next;
}
}
(2)方法二:分治
时间复杂度 O(KlogN)
空间复杂度 O (1)
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
return merge(lists, 0, lists.length - 1);
}
private ListNode merge(ListNode[] lists, int left, int right) {
if (left == right) return lists[left];
int mid = left + (right - left) / 2;
ListNode l1 = merge(lists, left, mid);
ListNode l2 = merge(lists, mid + 1, right);
return mergeTwoLists(l1, l2);
}
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
9.排序链表(O(nlogn)时间复杂度、空间复杂度为O1)
前面一个函数用来计算中点
下面一个函数用来merge
题解见:https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/
public ListNode sortList(ListNode head){
if (head==null||head.next==null)return head;
//使用快慢指针,快指针到达null,或者fast.next==null的时候,slow的位置即是我们的中点位置
//奇数情况:1->2->3->null 2是中点
//偶数情况:1->2->3->4->null 2是中点
ListNode slow = head;
ListNode fast = head.next;
while (fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
ListNode nextHead = slow.next;
slow.next = null;
ListNode left = sortList(head);
ListNode right = sortList(nextHead);
return mergeRecution(left,right);//注意!此处是对left和right进行merge,而不是对head和nexthead
//因为两边分割的链表在向上合并的过程中,头节点可能发生变化
}
private ListNode mergeRecution(ListNode l1 , ListNode l2){
if (l1==null)return l2;
if (l2==null)return l1;
if (l1.val<l2.val){
l1.next = mergeRecution(l1.next,l2);
return l1;
}
else {
l2.next = mergeRecution(l1,l2.next);
return l2;
}
}
10.删除链表中的重复元素(所有元素)
循环版本:
//D->1->null
//null
//D->1->1->null
//D->1->1->2->null
//D->1->1->2->2->null
public ListNode delete1(ListNode head){
if (head==null||head.next==null)return head;
ListNode dummyHead = new ListNode(-1);
ListNode pre = dummyHead;
ListNode cur = head;
ListNode next = head.next;
while (next!=null){
if (cur.val==next.val){
while (next!=null && cur.val==next.val){
next = next.next;
}
cur = next;
pre.next = cur;
if (next!=null)next=next.next; //注意防止next越界
}
else {
pre.next = cur;
pre = cur;
cur = next;
next = next.next;
}
}
return dummyHead.next;
}
递归版本:
public ListNode deleteDuplicates(ListNode head) {
if (head==null||head.next==null)return head;
if (head.val == head.next.val){
while (head.next!=null && head.val == head.next.val){
head=head.next;
}
return deleteDuplicates(head.next);
}
head.next = deleteDuplicates(head.next);
return head;
}
二、二叉树
1.非递归遍历:前、中、后、层
public class Order {
public List<Integer> preOrder(TreeNode root){
LinkedList<Integer> list = new LinkedList<>();
if (root==null)return list;
Stack<TreeNode>stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur = stack.pop();
list.add(cur.val);
if (cur.right!=null)stack.push(cur.right);
if (cur.left!=null)stack.push(cur.left);
}
return list;
}
public List<Integer> inOrder(TreeNode root){
LinkedList<Integer>list = new LinkedList<>();
if (root==null)return list;
Stack<TreeNode>stack = new Stack<>();
TreeNode cur = root;
while (!stack.isEmpty()||cur!=null){
if (cur!=null){
stack.push(cur);
cur=cur.left;
}
else {
cur = stack.pop();
list.add(cur.val);
cur = cur.right;
}
}
return list;
}
public List<Integer> postOrder(TreeNode root){
LinkedList<Integer>list = new LinkedList<>();
if (root==null)return list;
Stack<TreeNode>stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur = stack.pop();
list.add(0,cur.val);
if (cur.left!=null)stack.push(cur.left);
if (cur.right!=null)stack.push(cur.right);
}
return list;
}
public List<LinkedList<Integer>> levelOrder(TreeNode root){
LinkedList<LinkedList<Integer>> res = new LinkedList<>();
if (root==null)return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
int size = queue.size();
LinkedList<Integer>list = new LinkedList<>();
while (size>0){
TreeNode cur = queue.poll();
list.add(cur.val);
if (cur.left!=null)queue.offer(cur.left);
if (cur.right!=null)queue.offer(cur.right);
size--;
}
res.add(list);
}
return res;
}
}
2.二叉树的最小深度
public int minDepth(TreeNode root){
if (root==null)return 0;
if (root.left==null&&root.right==null)return 1;
int a = Integer.MAX_VALUE;
if (root.left!=null){
a = minDepth(root.left);
}
if (root.right!=null){
a = Math.min(a,minDepth(root.right));
}
return a+1;
}
3.二叉树节点个数合集
1.二叉树中节点的个数:
private int numOfNode(TreeNode root){
if (root==null)return 0;
return numOfNode(root.left)+numOfNode(root.right)+1;
}
2.二叉树叶子节点的个数
private int numOfLeaf(TreeNode root){
if (root==null)return 0;
if (root.left==null&&root.right==null)return 1;
return numOfLeaf(root.left)+numOfLeaf(root.right);
}
3.二叉树第K层节点的个数
public int numOfKLevel(TreeNode root,int k){
if(root==null)return 0;
if (k==1)return 1;
return numOfKLevel(root.left,k-1)+numOfKLevel(root.right,k-1);
}
4.二叉树是否是完全二叉树
5.二叉树的最低公共祖先
6.前序遍历和后序遍历构建二叉树
7.在二叉树中插入节点
8.二叉树内两个节点的最长距离
9.不同的二叉树数量