牛客Top100热题宝典--第1节

算法学习方法

  1. 每道题控制一下时间,如果时间过了很久没思路,直接看题解,然后手敲,如果中途依然没思路,继续手敲;
  2. 分析执行结果,时间复杂度+空间复杂度,要求必须100%打败对手,写完之后看评论,学习更多的思路,做到一题多解;
  3. 代码中写清楚注释;
  4. 写过的代码一定要保存在github上面 【平常写的代码等文件一定要保存好】;
  5. 刷了大概几百到题;
  6. 大厂面试至少80%都是原题;
  7. 刷力扣+剑指offer;
  8. 多刷+狂刷

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 链表内指定区间反转

92. 反转链表 II

思路:首先找到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

思想

  1. 要求:空间复杂度 O(n),时间复杂度 O(n)

    增加辅助空间set,然后遍历原链表,构建新的链表即可

  2. 进阶:空间复杂度 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. 重排链表

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 二分查找/排序

image.png

https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/solution/yi-wen-dai-ni-gao-ding-er-fen-cha-zhao-j-00kj/

微信图片_20201226200821

这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。

​ 二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 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. 寻找旋转排序数组中的最小值
我们需要在一个旋转数组中,查找其中的最小值,如果我们数组是完全有序的很容易,我们只需要返回第一个元素即可,但是此时我们是旋转过的数组。

我们需要考虑以下情况

微信图片_20201226204620

我们见上图,我们需要考虑的情况是

  • 数组完全有序 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 时,开始向上合并,不断将 较短排序数组 合并为 较长排序数组,直至合并至原数组时完成排序;

image-20210623223031128
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
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值