Android 算法知识点-链表相关算法

《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个节点,可以【定义两个指针】:

  1. 第一个指针先走k-1,第二个指针保持不动;
  2. 从第k步开始,第二个指针也开始从链表的头指针开始遍历;
  3. 由于两个指针的距离保持在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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值