常考算法-----链表总结

面试问题总结

面试/考试速查
常考数据结构类型速查速补表
* 单链表
双向链表
约瑟夫环
栈·栈实现计算器
* 前缀,中缀,后缀表达式,逆波兰计算器的实现
*递归,迷宫回溯,八皇后
排序算法基础
*冒泡排序
选择排序
插入排序
希尔排序
* 快速排序
归并排序
基数排序
各种排序的比较

二叉排序树
BST删除一棵子树的节点
* 二叉平衡树
* 图,图的深度优先和广度优先
* 动态规划
* 暴力匹配和KMP算法
* 贪心算法

带星的重要性不言而喻,需要重点掌握!

链表

快慢指针的妙用

无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。然而面试的时候经常碰见诸如获取倒数第K个元素获取中间位置的元素判断链表是否存在环判断环的长度等和长度与位置有关的问题。这些问题都可以通过灵活使用双指针来解决。

Tips:双指针并不是固定的公式,而是一种思维方式~

我们先来简单介绍一下何为快慢指针。快慢指针就是定义两根指针,移动的速度一快一慢,以此来制造出自己想要的差值。这个差值可以让我们找到链表上相应的节点。

a.获取倒数第K个元素

思路设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 p 即指向倒数第 k 个结点。

     //获取倒数第K个元素
    //思路:两个指针同时指向头部,将一个指针fast先移动K次 再将快慢指针同时移动 直到fast指针到达尾部时 返回slow的那个节点的值即可
    public static ListNode getLastNode(ListNode head, int k) {
    
    
        //判断k
        if (k < 0) {
    
    
            return null;
        }
        //定义快慢指针
        ListNode fast = head;
        ListNode slow = head;
        //将fast移动K次
        while (k != 0) {
    
    
            fast = fast.next;
            k--;
        }
        //fast 不为空时 fast slow 一起移动
        while (fast != null){
    
    
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

b.找中间值

一般的思路 : 先遍历一次链表 记住链表一共有多少个节点,然后在遍历 寻找中间值

利用快慢指针,思路如下: 把一个链表看作是一个跑道,假设a的速度是b速度的两倍,当a跑完全程后,b正好跑完一半,以此来得到中间值。

快慢指针:slow和fast都指向第一个节点,fast一次移动2个节点,slow一次移动1个节点。

n为偶数:中间数靠后

//寻找中间值
//思路:假设两个指针都指向头节点 fast指针移动速度是slow指针移动速度的两倍 例如 fast一次移动2个节点 slow 一次移动一个节点
//当fast移动到尾部(为空)时,slow 正好处于一半的位置 为中间值
    public static ListNode findMid(ListNode head){
    
    
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null){
    
    
                fast = fast.next.next;
                slow = slow.next;
            }
        return slow;
    }

c.判断是否有环思路 : 利用快慢指针 快指针一次移动两个节点 ,慢指针一次移动一个节点,类似于一个跑道,如果有环 ,快慢指针必定相遇(相等),不相遇则没有环。

【注】快指针一次一次移动 移动之前判断是否为空

public static class ListNode{
    
    
	int val;
    ListNode next;
    
    ListNode(int x){
    
    
        val = x;
        next = null;
    }
}

private static boolean hasCycle(ListNode head){
    
    
    //定义快慢指针
    ListNode fast = head;
    ListNode slow = head;
    //判断
    while(fast != null){
    
    
        fast = fast.next;
        if(fast != null){
    
    
            fast = fast.next;
        }
        if(fast == slow){
    
    
            return true;//有环
        }
        slow = slow.next;
    }
    return false;//没有环
}

public static void main(String[] args) {
    
    
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);
        System.out.println(hasCycle(head));//false
        head.next.next.next.next.next = head.next;
        System.out.println(hasCycle(head));//true
    }

反转链表反转一个链表

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

思路:定义两个指针 cur指向头节点 pre指向null(头节点的前一个节点) 一个用于交换数据的节点tmp ,先将cur的下一个节点保存在tmp节点中,再将cur指向pre(pre = cur.next ),cur和pre同时向后移动(交换数据)

 //反转链表
    //思路:双指针迭代  遍历链表 将当前节点指向前一个节点 需要临时保存当前节点的下一个节点
    public static ListNode reverseList(ListNode head) {
    
    
        ListNode cur = head;
        ListNode pre = null;
        ListNode tmp = null;

        while (cur != null) {
    
    
            tmp = cur.next;//保存cur的下一个节点
            cur.next = pre;//当前节点指向pre

            pre = cur;//向后移
            cur = tmp;//向后移
        }
        return pre;
    }

合并两个链表右链表合并到左链表

左链表:1->2->3 右链表:4->5 合并后:1->5->2->4->3

思路:将右链表的第一个节点插入到左链表的第二个节点。左链表变成: 1->5->2->3 右链表 4 此时leftTemp要指向 2 这个节点 right.next = leftTemp ,将左右链表向后移动即可

public static ListNode merge(ListNode left,ListNdoe right){
    
    
	while(left.next != null && right != null){
    
    
	ListNode leftTemp = left.next;
	ListNode rightTemp = right.next;
	
	left.next = right;
	right.next = leftTemp;
	
	left = leftTemp;
	right = rightTemp;
	}
}

逆序两数相加###

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8

思路: 定义两个指针指向新的一个链表的头节点(0)cur pre(指向头部),一个用来保存是否进位的变量carry

将sum = l1+l2+carry,

sum = sum % 10 个位数保存再sum中 十位保存在carry中 ,

sum 加入cur.next = new ListNode(sum)

cur向后移动 cur = cur.next;

l1 l2 都向后移动

如果有一个链表为空 且carry = 1 则有进位 要再尾部新创建一个节点 cur.next = new ListNode(carry)

返回新的链表 pre.next

public static ListNode addTwoNumber(ListNode l1,ListNode l2){
    
    
	int carry = 0;
	ListNode pre = new ListNode(0);
	 ListNode cur= pre;
	 
	 while(l1 != null || l2 != null){
    
    
	 	int x = l1 == null ? 0 : l1.val;
	 	int y = l2 == null ? 0 : l2.val;
	 	int sum = x + y + carry;
	 	
	 	carry = sum / 10;
	 	sum = sum % 10;
	 	cur.next = new ListNode(sum);
	 	
	 	cur = cur.next;
	 	if(l1 != null){
    
    
	 	l1 = l1.next;
	 	}
	 	if(l2 != null){
    
    
	 	l2 = l2.next;
	 	}
	 }
	 if(carry == 1){
    
    
	 cur.next = new ListNode(carry);
	 }
	 return pre.next;
}

删除节点

思路: 将待删除的节点的下一个节点的值赋给待删除的节点,将待删除的节点指向待删除节点的下下一个节点 将待删除下一个节点的值赋给待删除节点 实际上删除的是下一个节点

public static void deleteNode(ListNode node){
    
    
	node.val = node.next.val;
	node.next = node.next.next;
}

删除倒数第K个元素

思路:先找到倒数第K个元素,删除即可(slow.next = slow.next.next)

Tips: 用到预先指针。对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 pre,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果。

合并两个有序链表

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

思路:递归

public static 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;
	}
}

非递归:定义一个新的链表(预先指针pre 需要通过头节点返回链表 首先考虑预先指针 还要一个临时指针 用来遍历整个链表的位置) 判断l1、l2 两个链表的第一个的大小 小的加到新链表上,新链表指针向后移动,小的节点链表也想后移动 直到一方为空 再将另一链表加入新链表中(因为是有序链表无需比较) 迭代

public static ListNode megerTwoLists(ListNode l1, ListNode l2){
    
    
	ListNode pre = new ListNode(0);
	ListNode temp = pre;
	while(l1 != null && l2 != null){
    
    
		if(l1.val < l2.val){
    
    
			temp.next = l1;
			temp = temp.next;
			l1 = l1.next;
		}
		else{
    
    
			temp.next = l2;
			temp = temp.next;
			l2 = l2.next;
		}
	}
	if(l1 == null){
    
    
		temp.next = l2;
	}else{
    
    
		temp.next = l1;
	}
	return pre.next;
}

猜你喜欢

转载自blog.csdn.net/weixin_45262118/article/details/108566103