《Java 数据结构和算法 第二版》
一、链表相关算法,链表翻转,链表合并等
算法 | 规则 |
---|---|
单链表反转 | 使用 【3个指针】遍历单链表,逐个链接点进行反转 |
求中间节点 | 使用 快慢指针,快指针每次走两步,慢指针每次走一步 快指针走到尾节点时,慢指针恰好在链表中间。 |
删除倒数第K个节点 | 定义两个指针: 1. 第一个指针先走k-1,第二个指针保持不动; 2. 从第k步开始,第二个指针也开始从链表的头指针开始遍历; 3. 由于两个指针的距离保持在k-1,当 第一个指针走完时,第二个指针正好是倒数第 k个结点 |
合并两个有序链表 | 递归比较两个链表中元素大小,把大的元素插入小的元素后面 |
检测环、找到入口点 | 检测环: 采用 “快慢指针” 的方法 慢指针每次走一步,快指针每次走两步 如果有环,当两个指针都进入环之后,经过一定步的操作之后二者一定能够在环上相遇 在同一个环中fast和slow之间的距离不会大于环的长度 所以,此时slow还没有绕环一圈,也就是说一定是在slow走完第一圈之前相遇 找到入口点: 快慢指针相遇后,慢指针回到head起点,快指针保持在相遇点 两个指针每次各走一步,当两个指针再次相遇时,相遇点就是环的入口 |
两个单链表相交的起始点 | 比较链表两个的长度,较长的链表的先走他们长度的差值 然后同一步伐一起走,直到走到的结点相同时,就找的了第一个公共结点 |
奇偶链表 | 给定一个单链表,把所有的奇数编号的节点、偶数编号的节点,分别排在一起 输入: 1->2->3->4->5->null 输出: 1->3->5->2->4->null 将原链表拆分成奇偶链表然后在合并 |
回文链表 | 编写一个函数,检查链表是否为回文。 给定一个链表ListNode head,请返回一个 boolean值,代表链表是否为回文 快慢指针,慢指针每次走一步,快指针每次走两步 两个指针同时开始走,慢指针指向的每个节点的值依次塞入栈中 当快指针走完后,慢指针继续走 慢指针每走一步,就从栈中取出一个值与当前指向的节点的值比较 如果不等,说明不是回文 如果是,则继续往下走,一直比较下去… 当慢指针走完时,比较的值都相等,并且栈中已经不再有数据,说明是回文 要注意的是,当链表为奇数个时,慢指针要跳过中间元素 |
Node 定义
public static class Node {
private int data;
private Node next;
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
public int getData() {
return data;
}
}
1、单链表反转
1)方法
使用 【3个指针】遍历单链表,逐个链接点进行反转
2)实现
public Node reverseSingleLinkedList(Node head){
//少于两个节点没有必要反转
if(head == null || head.next == null){
return head;
}
Node p, q, r;
p = head;
q = head.next;
//旧的头指针就是新的尾指针,next需要指向null
head.next = null;
while (q != null){
r = q.next;//先保留下一个step要处理的指针
q.next = p;//然后pq交替工作进行反向
p = q;
q = r;
}
head = p;//最后q必然指向null,所以返回了p作为新的头指针
return head;
}
2、求中间节点
1)方法
使用快慢指针,快指针每次走两步,慢指针每次走一步
快指针走到尾节点时,慢指针恰好在链表中间。
2)实现
// 求中间结点
public static Node findMiddleNode(Node head) {
if (head == null) {
return null;
}
Node fast = head;
Node slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;//快指针每次走两步
slow = slow.next;//慢指针每次走一步
}
//快指针走完时,慢指针刚好走完一半。
return slow;
}
3、删除倒数第K个节点
1)方法
为了能够只遍历一次就能找到倒数第k个节点,可以【定义两个指针】:
- 第一个指针先走k-1,第二个指针保持不动;
- 从第k步开始,第二个指针也开始从链表的头指针开始遍历;
- 由于两个指针的距离保持在k-1,当第一个指针走完时,第二个指针正好是倒数第 k个结点
2)实现
// 删除倒数第K个结点
public static Node deleteLastKth(Node head, int k) {
Node fast = head;
int i = 1;
while (fast != null && i < k) {
fast = fast.next;
++i;
}
if (fast == null) {//说明元素总数 < k 不存在倒数第k个节点,不用删除
return head;
}
Node slow = head;
Node prev = null;
while (fast.next != null) {
fast = fast.next;
prev = slow;
slow = slow.next;
}
/* prev == null 说明 fast走完 k 步后,正好走完了,slow一步都没走
* 链表总长度刚好是 k,倒数第k个元素就是 head
* 只要把 head指针指向 head.next,把 head 删除就可以了
*/
if (prev == null) {
head = head.next;
} else {//链表长度 > k,fast走完时,slow所在的位置就是倒数第k个元素的位置
/* prev 是当前 slow 的上一个节点,把prev.next 指向 prev.next.next
* 就把slow节点从链表中删除了
*/
prev.next = prev.next.next;
}
return head;
}
4、合并两个有序链表
1)方法
将两个有序链表合并为一个新的有序链表并返回。
新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
递归比较两个两边中元素大小,按顺序插入即可
2)实现
//合并两个有序链表
public static Node mergeTwoLists(Node la, Node lb){
if(la == null) {
return lb;
}
if(lb == null) {
return la;
}
Node head = null;
if(la.data <= lb.data){
head = la;
head.next = mergeTwoLists(la.next, lb);
} else {
head = lb;
head.next = mergeTwoLists(la, lb.next);
}
return head;
}
5、检测环,并找到入口点
1)方法
-
判断是否有环
采用 “快慢指针” 的方法。就是有两个指针fast和slow,开始的时候两个指针都指向链表头head,
然后在每一步操作中slow向前走一步即:slow = slow->next,
而fast每一步向前两步即:fast = fast->next->next。由于fast要比slow移动的快,如果有环,fast一定会先进入环,而slow后进入环。
当两个指针都进入环之后,经过一定步的操作之后二者一定能够在环上相遇,
并且此时slow还没有绕环一圈,也就是说一定是在slow走完第一圈之前相遇。因为在同一个环中fast和slow之间的距离不会大于环的长度,
因此,到二者相遇的时候 slow 一定还没有走完一周
(或者正好走完一周,这种情况出现在开始的时候fast和slow都在环的入口处) -
找入口点
如果单链表有环,
当slow指针和fast指针相遇时,slow指针还没有遍历完链表,而fast指针已经在环内循环n(n>=1)圈了假设此时 slow指针走了s步, fast指针走了2s步, r为fast在环内转了一圈的步数 a为链表开头与环的入口的距离, b为快慢指针相遇的地点距离环的入口, c为从相遇点再走c步到达环的入口点, L为整个链表的长度。
slow指针走的步数:
s = a + b
fast指针走的步数:
2s = s + n * r 即:s = n * r
链表的长度:
L = a + b + c = a + r
由上可得:
a + b = n * r = (n - 1)*r + r 而 r = L - a,所以: a + b = (n - 1) * r + L - a a = (n - 1) * r + L - a - b 而 L - a - b = c,所以: a = (n -1) * r + c
综上可得:
从链表头到环的入口点等于(n - 1)循环内环 + 相遇点到环入口点步数,于是在链表头和相遇点分别设置一个指针,同时出发,每次各走一步,它们必定会相遇,
且第一次相遇的点就是环入口点。
2)实现
//找到环入口点
public static Node findLoopPoint(Node head) {
if (head == null) {
return null;
}
Node fast = head,slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {//有环必然会相遇
break;
}
}
if(fast == null || fast.next == null) {
return null;
}
//如果有环,slow指向连表头,此时fast指向相遇点
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
6、两个单链表相交的起始节点
1)方法
A: a1 → a2
↘
c1 → c2 → c3
↗
B: b1 → b2 → b3
因为链表是单向链表,所以相交链表的形状就像倒置的 Y,
这就有个特点:相交点之后的结点都相同。
所以找到第一个相同的结点就是链表的第一个公共结点。
如果正向遍历链表,因为链表的长度不相同,所以无法通过的时间使他们的步伐统一。
解决办法是首先比较链表两个的长度,然后让较长的链表的先走他们长度的差值,
然后同一步伐一起走,知道走到的结点相同时,就找的了第一个公共结点。
2)实现
//循环遍历,需要借助于栈
public ListNode getIntersectionNode(Node headA, Node headB) {
if (headA == null || headB == null) {
return null;
}
//用来保存链表之间的差值
int distance = 0;
//计算两个链表的长度,较长的链表先走几步
int lenA = calculateNodes(headA);
int lenB = calculateNodes(headB);
if (lenA > lenB) {//链表A 更长
distance = lenA - lenB;//A 比 B 长 distance
for (int i = 0; i < distance; i++) {
headA = headA.next;//A链表先走 distance步
}
} else {//链表 B 更长
distance = lenB - lenA;//B 比 A 长 distance
for (int i = 0; i < distance; i++) {
headB = headB.next;//B链表先走 distance步
}
}
ListNode crossNode = null;//两个单链表第一个相交的节点
//两个链表同步走,当两个链表的节点相同时,就相交了
while (headA != null && headB != null) {
if (headA == headB) {
crossNode = headA;
break;
}
headA = headA.next;
headB = headB.next;
}
return crossNode;
}
//计算链表的长度
private int calculateNodes(Node head) {
int size = 0;
while (head != null) {
size++;
head = head.next;
}
return size;
}
7、奇偶链表
-
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。
请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 -
请尝试使用原地算法完成。
你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->null
输出: 1->3->5->2->4->null
示例 2:
输入: 2->1->3->5->6->4->7->null
输出: 2->3->6->7->1->5->4->null
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
1)方法
将原链表拆分成奇偶链表然后在合并
2)实现
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head == null) {
return null;
}
ListNode oddList = head;//奇数位值链表
ListNode evenList = head.next;//偶数位置链表
ListNode evenHead = evenList;//偶数位链表头节点
while(evenList != null && evenList.next != null) {
//奇数位链表的下一个元素,是偶数位链表的下一位
oddList.next = evenList.next;
//奇数位链表索引右移一位
oddList = oddList.next;
//偶数位链表的下一个元素,是奇数位链表的下一位
evenList.next = oddList.next;
//偶数位链表索引右移一位
evenList = evenList.next;
}
//把偶数位链表拼接到奇数位链表尾部
oddList.next = evenHead;
return head;
}
}
8、回文链表
编写一个函数,检查链表是否为回文。
给定一个链表ListNode head,请返回一个 boolean值,代表链表是否为回文
1)方法
定义快慢指针,慢指针每次走一步,快指针每次走两步
当快指针走完时,慢指针刚好走完一半。
两个指针同时开始走,慢指针指向的每个节点的值依次塞入栈中。
当快指针走完后,慢指针继续走,
这时慢指针每走一步,就从栈中取出一个值与当前指向的节点的值比较
如果不等,说明不是回文。
如果是,则继续往下走,一直比较下去…
当慢指针走完时,比较的值都相等,并且栈中已经不再有数据,说明是回文。
要注意的是,当链表为奇数个时,慢指针要跳过中间元素
2)实现
public class Palindrome {
public boolean isPalindrome(ListNode head){
ListNode fast = head;
ListNode slow = head;
Stack<Integer> stack = new Stack<Integer>();
/**
* 将链表的前半部分元素装入栈中,当快速runner
*(移动的速度是慢速runner的两倍)
* 到底链表尾部时,则慢速runner已经处于链表中间位置
*/
while(fast != null && fast.next != null){
stack.push(slow.val);
slow = slow.next;
fast = fast.next.next;
}
//当链表为奇数个时,跳过中间元素
if (fast != null) {
slow = slow.next;
}
while(slow != null){
//如果两者不相同,则该链表不是回文串
if (stack.pop() != slow.val) {
return false;
}
slow = slow.next;
}
return true;
}
}
二、二叉树相关算法前序、中序、后序遍历(递归,迭代)
三、红黑树 与 BL树 等
四、递归
五、加密相关算法
推荐阅读:
《Java 数据结构和算法 第二版》
刷题---->LeetCode