大家好,文章题目是codeTop上的热门题目,本文章是京东面试题目热门题目。虽然算法在后端日常开发中应用比较少,但是它对思维的开发是很有益的,也是进中、大厂的必备的东西。文章致力于用最简单的语言阐述解题思路和解题方法,欢迎大家讨论更优秀的解题方案。
ps:对题目的讲解分为三部分,题意描述、思路描述和代码编写。众所周知有思路不一定能编写出代码,思路的完整性有助于一次AC。
强烈建议参考代码一起思考,有些东西用语言表达不出来,也就是脑子会了,手不会。题解地址
给出一个链表,链表包含本身数值和下一个数据的地址。需要判断出此链表存不存在环形结构,返回入环的节点。
若链表有环,那么循环链表的话会永远找不到出口,可以利用这个特性,就像人在操场跑步,只要第一名足够快,第一名和最后一名一定会相遇,在链表中循环也是一样利用套圈会相遇的特点,使用两个循环若相遇(相同值)就是有环,否则无环。
但是有个问题,虽然能相遇,但是不一定是在开始循环的节点相遇,还需要计算出入环节点。
这个时候就需要公式推导了(为什么公式可以推导?因为这个是有规律的)
我们称快的为fast,慢的为slow。为了好计算,fast的速度是2,slow为1,即fast一次循环两个节点,slow为一个。
res为从起点到入环的距离,即slow再走res步就是答案。换个说法,从head走res步也是入环节点,那么从head和slow位置同时走,走res步则会相遇,只要判断相遇,相遇的点就是入环点,就是答案。
有了思路,剩下的是转换为代码,参考
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;public class Main {public static void main(String[] args) {ListNode listNode1 = new ListNode(3);ListNode listNode2 = new ListNode(2);ListNode listNode3 = new ListNode(0);ListNode listNode4 = new ListNode(-4);listNode1.next = listNode2;listNode2.next = listNode3;listNode3.next = listNode4;listNode4.next = listNode2;ListNode res1 = detectCycle1(listNode1);System.out.println(res1.val);ListNode res2 = detectCycle2(listNode1);System.out.println(res2.val);}/*** 哈希法判断* 利用哈希表进行判断是是否存在此节点来判断是否存在环形* 每次循环时,首先判断是否在Set中* - 若不存在则加入到Set中* - 若存在则返回节点* - 若节点为空,返回空* * 复杂度分析:O(N) O(N)** @param head 链表* @return 结果*/public static ListNode detectCycle1(ListNode head) {Set nodeSet = new HashSet<>();while (true) {if (head == null) {return null;}if (nodeSet.contains(head)) {return head;}nodeSet.add(head);head = head.next;}}/*** 双指针* AB两指针分别一次走2和1的距离,因为有距离差,若存在环形结构,一定会被套圈追上* 首先计算出相遇的位置,这个位置可以用公式表示(起点到环形距离为lenA,环形长度为lenB):* ∵ A走的速度快* ∴ Sa = 2 * Sb(A走过的距离=2*B走过的距离)* ∵ A走的多,在追B时,多走了很多圈* ∴ Sa = Sb + n * lenB* 整合后* ∴ Sb = n * lenB Sa = 2 * n * lenB* 所以AB走的距离都可以用lenB表示。* * 若从起点出发,最终停留在循环入口处,期间转了m圈用公式表示:* Res = lenA + m * lenB ①* ∵ n m 都是基于链表的实际数字,圈数与位置没有关系* ∵ Sb停留的位置一定在环上,但不一定在起点处,结合上面公式①* ∴ Sb + lenA 的位置一定是起点,也就是结果*
* 当B走lenA后,即为答案,问题转换为求lenA* Sb = n * lenB* 令Sa = 0* 当Sa = lenA时,Sb = lenA + n * lenB** @param head 链表* @return 结果*/public static ListNode detectCycle2(ListNode head) {ListNode fast = head;ListNode slow = head;while (true) {// 无环退出if (fast == null || fast.next == null) {return null;}// 走一次两个距离fast = fast.next.next;// 走一次一个距离slow = slow.next;// 相遇退出if (fast == slow) {break;}}// 最后一步,相遇即可fast = head;while (fast != slow) {fast = fast.next;slow = slow.next;}return fast;}static class ListNode {int val;ListNode next;ListNode(int x) {val = x;next = null;}}
}
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
既然是链表,进行循环即可,因为需要反向输出,循环的第一个节点就是最后一个节点,每次循环时,新建一个节点,使此节点的next指针指向循环节点的值。
题外话:程序是一个呆板的、写死的、只能按照预先设置好的执行,所以思考时需要往通用型方向思考,特殊情况特殊判断,即可完成一个健壮性程序。
public class ReverseList {/*** 题意:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。* 思路:既然是链表,进行循环即可,因为需要反向输出,循环的第一个节点就是最后一个节点,每次循环时,新建一个节点,使此节点的next指针指向循环节点的值。** @param args arg*/public static void main(String[] args) {ListNode listNode1 = new ListNode(1);ListNode listNode2 = new ListNode(2);ListNode listNode3 = new ListNode(3);ListNode listNode4 = new ListNode(4);listNode1.next = listNode2;listNode2.next = listNode3;listNode3.next = listNode4;ListNode res = reverseList(listNode1);while (res != null) {System.out.print(res.val + " ");res = res.next;}}public static ListNode reverseList(ListNode head) {// 结果链表ListNode res = new ListNode();// 第一个节点特殊判断,作为新链表最后一个节点的值if (head != null) {res.val = head.val;} else {return null;}while (true) {if (head.next != null) {head = head.next;// 新节点作为父节点,t一直为循环至此的,结果的,第一个节点ListNode t = new ListNode();t.val = head.val;t.next = res;// res替换为新的第一个节点res = t;} else {break;}}return res;}static class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) {this.val = val;}ListNode(int val, ListNode next) {this.val = val;this.next = next;}}
}
给你一个整数数组 nums,请你将该数组升序排列。
快速排序。
快速排序是采用二分的思维方式,选中数组中的一个数字,以它为标准,左放比它小的数字,右边放大于它的数字,那么这个数字的位置就可以确定了,然后左边的数字、右边的数字重复这一操作,最终排序的数组为一个数字,那么排序也就完成了。
即每次只排一个数字。
优化:选取数字标准的时候,可以从数组中随机挑取,这样会加快排序速度。
import java.util.Arrays;
import java.util.Random;public class QuickSort {private static final Random RANDOM = new Random();/*** 题意:给你一个整数数组 nums,请你将该数组升序排列。* 解析:快速排序。* 快速排序是采用二分的思维方式,选中数组中的一个数字,以它为标准,左放比它小的数字,右边放大于它的数字,那么这个数字的位置就可以确定了,然后左边的数字、右边的数字重复这一操作,最终排序的数组为一个数字,那么排序也就完成了。* 即每次只排一个数字。* 优化:选取数字标准的时候,可以从数组中随机挑取,这样会加快排序速度。** @param args arg*/public static void main(String[] args) {int[] nums = new int[]{5, 1, 1, 2, 0, 0};int[] ints = sortArray(nums);System.out.println(Arrays.toString(ints));}public static int[] sortArray(int[] nums) {// 快速排序return quickSort(nums);}/*** 快速排序** @param nums 数组* @return 结果*/private static int[] quickSort(int[] nums) {// 递归处理quickSortFun(nums, 0, nums.length - 1);return nums;}private static void quickSortFun(int[] nums, int left, int right) {// left,right分表代表着需要排序数组的区间,若不满足下属要求,说明已经不需要排序了if (left >= right) {return;}// 从数组中随机挑取一个数组与最左边的数字交换,即最左边为此次排序的标准int randomIndex = left + RANDOM.nextInt(right - left + 1);swap(nums, randomIndex, left);// 为何+1,是因为left数字为此次排序标准数,不参与排序,只需要最后放到它应该在的地方即可int i = left + 1;int j = right;int index = nums[left];while (true) {// 从左到右,比标准数小的话直接跳过,相等是不跳过的(原因如下),i是可以到达right的,while (i <= right && nums[i] < index) {i++;}// 从右到左,比标注数大的话直接跳过,相等是不跳过的(原因如下),j不允许到达leftwhile (j > left && nums[j] > index) {j--;}// 限制左右循环不允许相遇(相遇说明排序完成了)if (i >= j) {break;}// i,j分别停留在了大于、小于标准数的位置,交换两个数字使数组满足要求// 相等的元素通过交换,等概率分到数组的两边swap(nums, i, j);i++;j--;}// 最终停留位置与标准数交换,那么标准数已经放到最终位置swap(nums, left, j);quickSortFun(nums, left, j - 1);quickSortFun(nums, j + 1, right);}private static void swap(int[] nums, int index1, int index2) {int temp = nums[index1];nums[index1] = nums[index2];nums[index2] = temp;}
}