牛客Top100热题宝典--第1节
算法学习方法
- 每道题控制一下时间,如果时间过了很久没思路,直接看题解,然后手敲,如果中途依然没思路,继续手敲;
- 分析执行结果,时间复杂度+空间复杂度,要求必须100%打败对手,写完之后看评论,学习更多的思路,做到一题多解;
- 代码中写清楚注释;
- 写过的代码一定要保存在github上面 【平常写的代码等文件一定要保存好】;
- 刷了大概几百到题;
- 大厂面试至少80%都是原题;
- 刷力扣+剑指offer;
- 多刷+狂刷
01 链表
BM1 反转链表
掌握迭代或递归!!
方式一:递归
https://leetcode.cn/problems/reverse-linked-list/solution/dong-hua-yan-shi-206-fan-zhuan-lian-biao-by-user74/

class Solution {
public ListNode reverseList(ListNode head) {
if(head==null || head.next==null)
return head;
//新的head节点
ListNode cur=reverseList(head.next);
head.next.next=head;
head.next=null;
return cur;
}
}
方式二:迭代
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(head);
}
public ListNode reverse(ListNode head){
ListNode cur=head;
ListNode pre=null;
while(cur!=null){
ListNode cur_next=cur.next;
cur.next=pre;
pre=cur;
cur=cur_next;
}
return pre;
}
}
BM2 链表内指定区间反转
思路:首先找到pre和right对应的节点

public class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummyNode = new ListNode(0);
dummyNode.next = head;
ListNode node = dummyNode;
int i = 0;
ListNode preNode = null;
ListNode rightNode = null;
while (dummyNode != null) {
if (i == left - 1) {
preNode = dummyNode;
}
if (i == right ) {
rightNode = dummyNode;
}
dummyNode = dummyNode.next;
i++;
}
ListNode leftNode = preNode.next;
preNode.next = null;
ListNode succNode = rightNode.next;
rightNode.next = null;
ListNode newHead = reverse(leftNode);
preNode.next = newHead;
leftNode.next = succNode;
return node.next;
}
public ListNode reverse(ListNode start) {
ListNode cur = start, pre = null;
while (cur != null) {
ListNode cur_next = cur.next;
cur.next = pre;
pre = cur;
cur = cur_next;
}
return pre;
}
}
BM3 链表中的节点每k个一组翻转
import java.util.*;
public class Solution {
public ListNode reverseKGroup (ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}
ListNode tail = head;
for (int i = 0; i < k; i++) {
if (tail == null)
// 如果数量不够k个,那么直接返回子链表的头指针即可,自动返回整个链表
return head;
tail = tail.next;
}
ListNode newHead = reverse(head, tail);
head.next = reverseKGroup(tail, k);
return newHead;
}
private ListNode reverse(ListNode head, ListNode tail) {
ListNode next = null;
ListNode pre = null;
while (head != tail) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
BM4 合并两个排序的链表
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode resList = new ListNode(-1); // 返回的链表的头节点
ListNode res = resList; // 链表的尾节点,用于尾插入
ListNode p = list1, q = list2;
while (p != null && q != null) {
if (p.val < q.val) {
res.next = p;
p = p.next;
} else {
res.next = q;
q = q.next;
}
res = res.next;
}
//将剩余的链表拼在后面
// if (p != null ) {
// res.next = p;
// }
while (p != null) {
res.next = p;
p = p.next;
res = res.next;
}
// 直接连接指针
// if (q != null) {
// res.next = q;
// }
while (q != null) {
res.next = q;
q = q.next;
res = res.next;
}
return resList.next;
}
}
BM5 合并k个已排序的链表
思想:分治思想
时间复杂度是O(nlogn) 的排序算法
包括归并排序、堆排序和快速排序(快速排序的最差时间复杂度是 O(n^2)),其中最适合链表的排序算法是归并排序!!!。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
int n = lists.length;
return partition(lists, 0, n - 1);
}
// 划分
public ListNode partition(ListNode[] lists, int left, int right) {
int mid = left + (right - left)/2;
if (left > right) return null;
if (left == right) return lists[left];
//注意index!!!
ListNode list1 = partition(lists, left, mid);
ListNode list2 = partition(lists, mid+1, right);
// 排序
return sortTwoLists(list1, list2);
}
public ListNode sortTwoLists(ListNode list1, ListNode list2) {
ListNode dummyHead = new ListNode(0);
ListNode node = dummyHead;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
node.next = list1;
list1 = list1.next;
} else {
node.next = list2;
list2 = list2.next;
}
node = node.next;
}
if (list1 != null)
node.next = list1;
if (list2 != null)
node.next = list2;
return dummyHead.next;
}
}
BM6 判断链表中是否有环
方法二:快慢指针
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode car = head;
ListNode bike = head;
// car 和 car.next 需要不为空,否则会发生 空指针异常
// car 不为空,那么 bike 肯定也不为空
while(car != null && car.next != null) {
bike = bike.next;
car = car.next.next;
// 汽车追上自行车了,有环路
if(car == bike) return true;
}
return false;
}
}
BM8 链表中倒数最后k个结点
// 1 栈 时间复杂度 O(n),空间复杂度 O(1)。
import java.util.*;
public class Solution {
public ListNode FindKthToTail (ListNode pHead, int k) {
if(pHead==null || k==0 )
return null;
Stack<ListNode> stack = new Stack<>();
ListNode node = new ListNode(0);
while (pHead != null) {
stack.push(pHead);
pHead = pHead.next;
}
if (stack.size() < k)
return null;
for (int i = 0; i < k; i++) {
node = stack.pop();
}
return node;
}
}
// 2 快慢指针 时间复杂度 O(n),空间复杂度 O(n)。
BM9 删除链表的倒数第n个节点
要求:空间复杂度 O(1),时间复杂度 O(n)
方式一:计算链表长度
方式二:快慢指针
根据快慢指针找到倒数n+1个节点,然后删除倒数第n个节点即可

import java.util.*;
public class Solution {
public ListNode removeNthFromEnd (ListNode head, int n) {
ListNode dummyHead=new ListNode(-1);
dummyHead.next=head;
ListNode pre=dummyHead;
// 统计列表长度
int len=0;
while(head!=null){
head=head.next;
len++;
}
int idx=len-n;
while(pre!=null && idx>0){
pre=pre.next;
idx--;
}
pre.next=pre.next.next;
return dummyHead.next;
}
}
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode left = dummyNode;
ListNode right = dummyNode;
int i=0;
while (right != null && i<n+1) {
right = right.next;
i++;
}
while (right != null) {
left = left.next;
right = right.next;
}
left.next = left.next.next;
return dummyNode.next;
}
}
BM10 两个链表的第一个公共结点
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1=pHead1;
ListNode p2=pHead2;
while(p1!=p2){
p1=(p1!=null)?p1.next:pHead2;
p2=(p2!=null)?p2.next:pHead1;
}
return p1;
}
}
BM11 链表相加(二)
方式一:while内处理,两个链表分别判断是否已经到链尾
方式二:对于长度不足的链表,补零处理
import java.util.*;
public class Solution {
public ListNode addInList (ListNode head1, ListNode head2) {
if(head1==null)
return head2;
if(head2==null)
return head1;
head1 = reverse(head1);
head2 = reverse(head2);
ListNode head = new ListNode(-1);
ListNode newHead = head;
// 注意此处进位是全局变量!!
int tmp = 0;
while (head1 != null || head2 != null) {
int val = tmp;
if (head1 != null) {
val += head1.val;
head1 = head1.next;
}
if (head2 != null) {
val += head2.val;
head2 = head2.next;
}
tmp = val / 10;
newHead.next = new ListNode(val % 10);
newHead = newHead.next;
}
// 注意此处最后进位的处理
if (tmp > 0) {
newHead.next = new ListNode(tmp);
}
return reverse(head.next);
}
ListNode reverse(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
ListNode tail = cur.next;
cur.next = pre;
pre = cur;
cur = tail;
}
// 注意此处返回值
return pre;
}
}
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummyNode = new ListNode(0);
ListNode node = dummyNode;
int carry = 0;
while (l1 != null || l2 != null) {
int num1 = (l1 != null) ? l1.val : 0;
int num2 = (l2 != null) ? l2.val : 0;
int sum = num1 + num2 + carry;
node.next = new ListNode(sum % 10);
carry = sum / 10;
if(l1!=null)
l1 = l1.next;
if(l2!=null)
l2 = l2.next;
node = node.next;
}
if (carry > 0) {
node.next = new ListNode(carry);
}
return dummyNode.next;
}
}
BM12 单链表的排序
import java.util.*;
public class Solution {
public ListNode sortInList (ListNode head) {
ArrayList tmpArr = new ArrayList();
while (head != null) {
tmpArr.add(head.val);
head = head.next;
}
Collections.sort(tmpArr);
ListNode tmp = new ListNode(-1);
ListNode res = tmp;
// foreach循环
for (Object i : tmpArr) {
tmp.next = new ListNode(Integer.parseInt(String.valueOf(i)));
tmp = tmp.next;
}
return res.next;
}
}
BM13 判断一个链表是否为回文结构
O(n) 时间复杂度
O(1) 空间复杂度
思想:根据快慢指针找到链表中点,然后反转后截链表,接着将前半截链表和反转后的后半截链表的节点值进行比较
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode slow=head;
ListNode fast=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
// 此时链表节点个数是奇数
if(fast!=null){
slow=slow.next;
}
slow=reverse(slow);
fast=head;
while(slow!=null){
//注意此处是比较节点的值,而不是地址!!,否则bug!!!
if(slow.val!=fast.val){
return false;
}
slow=slow.next;
fast=fast.next;
}
return true;
}
public ListNode reverse(ListNode head){
ListNode cur=head;
ListNode pre=null;
while(cur!=null){
ListNode cur_next=cur.next;
cur.next=pre;
pre=cur;
cur=cur_next;
}
return pre;
}
}
BM14 链表的奇偶重排
import java.util.*;
public class Solution {
public ListNode oddEvenList (ListNode head) {
if (head == null) return null;
ListNode Node1 = head;
ListNode node1 = head;
ListNode Node2 = head.next;
ListNode node2 = head.next;
while (node1.next != null && node2.next != null) {
node1.next = node2.next;
node1=node1.next;
node2.next = node1.next;
node2=node2.next;
}
node1.next = Node2;
return Node1;
}
}
BM15 删除有序链表中重复的元素-I
import java.util.*;
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
// write code here
if (head == null || head.next == null)
return head;
ListNode cur = head;
// 操作时对cur进行操作
while (cur.next != null) {
if (cur.val == cur.next.val) {
cur.next = cur.next.next;
} else
cur = cur.next;
}
return head;
}
}
BM16 删除有序链表中重复的元素-II
思想:
-
要求:空间复杂度 O(n),时间复杂度 O(n)
增加辅助空间set,然后遍历原链表,构建新的链表即可
-
进阶:空间复杂度 O(1),时间复杂度 O(n)
import java.util.*;
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
if (head == null || head.next == null)
return head;
ListNode dummyNode = new ListNode(0);
dummyNode.next = head;
ListNode p = dummyNode;
while (p.next != null && p.next.next != null) {
if (p.next.val == p.next.next.val) {
int x = p.next.next.val;
while (p.next != null && p.next.val == x) {
p.next = p.next.next;
}
} else {
p = p.next;
}
}
return dummyNode.next;
}
}
143. 重排链表
方法二:寻找链表中点 + 链表逆序 + 合并链表
class Solution {
public void reorderList(ListNode head) {
ListNode node = head;
ListNode middleNode = middleNode(head);
ListNode newHead = reverseList(middleNode.next);
middleNode.next = null;
mergeList(node, newHead);
}
public ListNode mergeList(ListNode list1, ListNode list2) {
ListNode dummyNode = new ListNode(0);
ListNode node = dummyNode;
while (list1 != null || list2 != null) {
node.next = list1;
list1 = list1.next;
node = node.next;
if (list2 != null) {
node.next = list2;
list2 = list2.next;
node = node.next;
}
}
return dummyNode.next;
}
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next!=null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
ListNode cur_next = cur.next;
cur.next = pre;
pre = cur;
cur = cur_next;
}
return pre;
}
}
02 二分查找/排序

https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/solution/yi-wen-dai-ni-gao-ding-er-fen-cha-zhao-j-00kj/
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?
大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
二分法第一种写法
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
例如在数组:1,2,3,4,7,9,10中查找元素2,如图所示:

153. 寻找旋转排序数组中的最小值
153. 寻找旋转排序数组中的最小值
我们需要在一个旋转数组中,查找其中的最小值,如果我们数组是完全有序的很容易,我们只需要返回第一个元素即可,但是此时我们是旋转过的数组。
我们需要考虑以下情况
我们见上图,我们需要考虑的情况是
-
数组完全有序 nums[left] < nums[right],此时返回 nums[left] 即可
-
left 和 mid 在一个都在前半部分,单调递增区间内,所以需要移动 left,继续查找,left = mid + 1;
-
left 在前半部分,mid在后半部分,则最小值必在 left 和 mid 之间(见下图)。则需要移动right ,right = mid,我们见上图,如果我们 right = mid - 1,则会漏掉我们的最小值,因为此时 mid 指向的可能就是我们的最小值。所以应该是 right = mid
class Solution {
public int findMin(int[] nums) {
int n = nums.length;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[left] <= nums[right])
return nums[left];
if (nums[mid] >= nums[left]) {
left = mid + 1;
} else {
right = mid;
}
}
return -1;
}
}
BM17 二分查找-I
O(logn) 【重要!!】
- 时间复杂度:O(logn),其中 n是数组的长度。
- 空间复杂度:O(1)
// 版本一
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=!!!
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
BM18 二维数组中的查找
思路:(单调性扫描)
时间复杂度O(m + n)
从右上角开始搜索,
如果matrix[i][j] < target,那么向下搜索;
如果matrix[i][j] > target,那么向左搜索;
如果matrix[i][j] == target,那么返回结果。


class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row = matrix.length;
int col = matrix[0].length;
int i = 0;
int j = col - 1;
while (i < row && j >= 0) {
if (matrix[i][j] < target) {
i++;
} else if (matrix[i][j] > target) {
j--;
} else {
return true;
}
}
return false;
}
}
BM19 寻找峰值
复杂度:o(n)
import java.util.*;
public class Solution {
public int findPeakElement (int[] nums) {
if (nums.length == 2 && nums[0] > nums[1]) {
return 0;
} else if (nums.length == 2 && nums[0] < nums[1]) {
return 1;
}
for (int i = 1; i < nums.length - 1; i++) {
if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1])
return i;
else if (nums[nums.length - 1] > nums[nums.length - 2])
return nums.length - 1;
}
return 0;
}
}
O(logN)
import java.util.*;
public class Solution {
public int findPeakElement (int[] nums) {
int n = nums.length;
if (n == 1)
return 0;
if (n == 2 ) {
int peek = nums[0] > nums[1] ? 0 : 1;
return peek;
}
int low = 0, high = n - 1, mid = 0;
while (low < high) {
mid = low + (high - low) / 2;
// 峰值在右侧
if (nums[mid] < nums[mid + 1]) {
low = mid + 1;
}
//峰值在左侧
else if (nums[mid] > nums[mid + 1]) {
high = mid;
}
}
return low;
}
}
BM20 数组中的逆序对 【难!!】
【存在一定的问题!】
归并算法 O(nlogn)
「归并排序」与「逆序对」是息息相关的。归并排序体现了 “分而治之” 的算法思想,具体为:
分: 不断将数组从中点位置划分开(即二分法),将整个数组的排序问题转化为子数组的排序问题;
治: 划分到子数组长度为 1 时,开始向上合并,不断将 较短排序数组 合并为 较长排序数组,直至合并至原数组时完成排序;
public class Solution {
int count = 0;
public int InversePairs(int [] array) {
if (array.length < 2)
return 0;
mergeSort(array, 0, array.length - 1);
return count;
}
public void partition(int [] array, int left, int right) {
if (l >= r) return;
int mid = l + (r - l) / 2;
partition(nums, l, mid);
partition(nums, mid + 1, r);
mergeSort(nums, l, mid, r);
}
}
public void merge(int [] array, int left, int mid, int right) {
int[] arr = new int[right - left + 1];
int l = left;
int r = mid + 1;
int c = 0;
//注意此处s的取值
int s = left;
while (l <= mid && r <= right) {
if (array[l] <= array[r]) {
arr[c++] = array[l++];
//没有重复数字所以可以这样!!
} else {
arr[c++] = array[r++];
//这个条件不会写?
//左右两侧都是升序,所以mid右侧是右侧序列最小值,因此可以这样计算
count += mid + 1 - l;
count %= 1000000007;
}
}
while (l <= mid)
arr[c++] = array[l++];
while (r <= right)
arr[c++] = array[r++];
for (int num : arr) {
array[s++] = num;
}
}
}
BM21 旋转数组的最小数字
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if (array.length == 0) return 0;
int low = 0, high = array.length - 1;
while (low < high) {
//终止条件
if (array[low] < array[high]) {
return array[low];
}
int mid = low + (high - low) / 2;
if (array[mid] > array[high])
low = mid + 1;
else if (array[mid] < array[high]) {
high = mid ;
} else {
high--;
}
}
return array[low];
}
}
BM22 比较版本号
import java.util.*;
public class Solution {
public int compare (String version1, String version2) {
//注意转义处理
String[] str1 = version1.split("\\.");
String[] str2 = version2.split("\\.");
int i;
int maxLen = (str1.length > str1.length) ? str1.length : str2.length;
for ( i = 0; i < str1.length || i < str1.length; i++) {
int x = 0, y = 0;
if (i < str1.length) {
x = Integer.parseInt(str1[i]);
}
if (i < str2.length) {
y = Integer.parseInt(str2[i]);
}
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
}
return 0;
}
}
整理不易🚀🚀,关注和收藏后拿走📌📌欢迎留言🧐👋📣
欢迎专注我的公众号AdaCoding 和 Github:AdaCoding123

2031

被折叠的 条评论
为什么被折叠?



