数组、链表专题

数组、链表专题


不要纠结,干就完事了,熟练度很重要!!!多练习,多总结!!!

前缀和数组

LeetCode 303. 区域和检索 - 数组不可变

在这里插入图片描述

解题思路

核心思路是我们 new 一个新的数组preSum出来,preSum[i]记录nums[0…i-1]的累加和,看图 10 = 3 + 5 + 2:
在这里插入图片描述
看这个preSum数组,如果我想求索引区间[1, 4]内的所有元素之和,就可以通过preSum[5] - preSum[1]得出。
这样,sumRange函数仅仅需要做一次减法运算,避免了每次进行 for 循环调用,最坏时间复杂度为常数O(1)。

代码实现

class NumArray {

    int[] preSum;
    public NumArray(int[] nums) {
        preSum = new int[nums.length +1];

        for(int i = 1;i < preSum.length;i++){
            preSum[i] = preSum[i-1]+nums[i-1];
        }
    }
    
    public int sumRange(int left, int right) {
        return preSum[right+1]-preSum[left];
    }
}

LeetCode 304. 二维区域和检索 - 矩阵不可变

在这里插入图片描述

解题思路

在这里插入图片描述
如果我想计算红色的这个子矩阵的元素之和,可以用绿色矩阵减去蓝色矩阵减去橙色矩阵最后加上粉色矩阵,而绿蓝橙粉这四个矩阵有一个共同的特点,就是左上角就是(0, 0)原点。

那么我们可以维护一个二维preSum数组,专门记录以原点为顶点的矩阵的元素之和,就可以用几次加减运算算出任何一个子矩阵的元素和。

代码实现

class NumMatrix {
    int[][] preSum;

    public NumMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        preSum = new int[m+1][n+1];

        for(int i = 1;i <= m;i++){
            for(int j = 1;j <= n;j++){
                preSum[i][j] = preSum[i-1][j]+preSum[i][j-1]+matrix[i-1][j-1]-preSum[i-1][j-1];
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return preSum[row2+1][col2+1]-preSum[row2+1][col1]-preSum[row1][col2+1]+preSum[row1][col1];
    }
}

LeetCode 560. 和为 K 的子数组

在这里插入图片描述

解题思路

nt subarraySum(int[] nums, int k) {
    int n = nums.length;
    // 构造前缀和
    int[] preSum = new int[n + 1];
    preSum[0] = 0; 
    for (int i = 0; i < n; i++)
        preSum[i + 1] = preSum[i] + nums[i];

    int res = 0;
    // 穷举所有子数组
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < i; j++)
            // 子数组 nums[j..i-1] 的元素和
            if (preSum[i] - preSum[j] == k)
                res++;

    return res;
}

这个解法的时间复杂度O(N^2)空间复杂度O(N),并不是最优的解法。不过通过这个解法理解了前缀和数组的工作原理之后,可以使用一些巧妙的办法把时间复杂度进一步降低。
第二层 for 循环在干嘛呢?翻译一下就是,在计算,有几个j能够使得preSum[i]和preSum[j]的差为k。毎找到一个这样的j,就把结果加一。
我直接记录下有几个preSum[j]和preSum[i] - k相等,直接更新结果,就避免了内层的 for 循环。我们可以用哈希表,在记录前缀和的同时记录该前缀和出现的次数

代码实现

class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer, Integer> preSum = new HashMap<>();
        int res = 0, sum_i = 0;
        preSum.put(0, 1);
        for(int i = 0;i < nums.length;i++){
            sum_i +=nums[i];
            int sum_j = sum_i-k;
            if(preSum.containsKey(sum_j)){
                res+=preSum.get(sum_j);
            }
            preSum.put(sum_i, preSum.getOrDefault(sum_i, 0)+1);
        }
        return res;
    }
}

差分数组

差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。
我给你输入一个数组nums,然后又要求给区间nums[2…6]全部加 1,再给nums[3…9]全部减 3,再给nums[0…4]全部加 2,再给…
类似前缀和技巧构造的prefix数组,我们先对nums数组构造一个diff差分数组,diff[i]就是nums[i]和nums[i-1]之差:
在这里插入图片描述

int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
    res[i] = res[i - 1] + diff[i];
}

这样构造差分数组diff,就可以快速进行区间增减的操作,如果你想对区间nums[i…j]的元素全部加 3,那么只需要让diff[i] += 3,然后再让diff[j+1] -= 3即可。

只要花费 O(1) 的时间修改diff数组,就相当于给nums的整个区间做了修改。多次修改diff,然后通过diff数组反推,即可得到nums修改后的结果。

// 差分数组工具类
class Difference {
    // 差分数组
    private int[] diff;

    /* 输入一个初始数组,区间操作将在这个数组上进行 */
    public Difference(int[] nums) {
        assert nums.length > 0;
        diff = new int[nums.length];
        // 根据初始数组构造差分数组
        diff[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
    }

    /* 给闭区间 [i,j] 增加 val(可以是负数)*/
    public void increment(int i, int j, int val) {
        diff[i] += val;
        if (j + 1 < diff.length) {
            diff[j + 1] -= val;
        }
    }

    /* 返回结果数组 */
    public int[] result() {
        int[] res = new int[diff.length];
        // 根据差分数组构造结果数组
        res[0] = diff[0];
        for (int i = 1; i < diff.length; i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
}

public void increment(int i, int j, int val) {
    diff[i] += val;
    if (j + 1 < diff.length) {
        diff[j + 1] -= val;
    }
}

当j+1 >= diff.length时,说明是对nums[i]及以后的整个数组都进行修改,那么就不需要再给diff数组减val了

LeetCode 303. 区域和检索 - 数组不可变

在这里插入图片描述

解题思路

核心思路是我们 new 一个新的数组preSum出来,preSum[i]记录nums[0…i-1]的累加和,看图 10 = 3 + 5 + 2:
在这里插入图片描述
看这个preSum数组,如果我想求索引区间[1, 4]内的所有元素之和,就可以通过preSum[5] - preSum[1]得出。
这样,sumRange函数仅仅需要做一次减法运算,避免了每次进行 for 循环调用,最坏时间复杂度为常数O(1)。

代码实现

int[] getModifiedArray(int length, int[][] updates) {
    // nums 初始化为全 0
    int[] nums = new int[length];
    // 构造差分解法
    Difference df = new Difference(nums);

    for (int[] update : updates) {
        int i = update[0];
        int j = update[1];
        int val = update[2];
        df.increment(i, j, val);
    }

    return df.result();
}

LeetCode 1109. 航班预订统计

在这里插入图片描述

解题思路

给你输入一个长度为n的数组nums,其中所有元素都是 0。再给你输入一个bookings,里面是若干三元组(i,j,k),每个三元组的含义就是要求你给nums数组的闭区间[i-1,j-1]中所有元素都加上k。请你返回最后的nums数组是多少?

代码实现

class Solution {
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] nums = new int[n];
        Difference diff = new Difference(nums);
        for(int[] arr:bookings){
            int first = arr[0]-1;
            int last = arr[1]-1;
            int val = arr[2];
            diff.increment(first, last, val);
        }
        return diff.result();
    }
}

class Difference{

    int[] diff;
    public Difference(int[] nums){
        diff = new int[nums.length];
        diff[0] = nums[0];
        for(int i = 1; i < nums.length;i++){
            diff[i] = nums[i]-nums[i-1];
        }
    }

    public void increment(int i, int j, int val){
        diff[i]+=val;
        if(j+1<diff.length){
            diff[j+1]-=val;
        }
    }

    public int[] result(){
        int[] res = new int[diff.length];
        res[0] = diff[0];
        for(int i = 1; i < diff.length;i++){
            res[i] = diff[i]+res[i-1];
        }
        return res;
    }
}

LeetCode 1094. 拼车

在这里插入图片描述

解题思路

trips[i]代表着一组区间操作,旅客的上车和下车就相当于数组的区间加减;只要结果数组中的元素都小于capacity,就说明可以不超载运输所有旅客。

代码实现

class Solution {
    public boolean carPooling(int[][] trips, int capacity) {
        int[] nums = new int[1001];
        Difference diff = new Difference(nums);
        for(int[] t:trips){
            int val = t[0];
            int from = t[1];
            int to = t[2]-1;
            diff.increment(from, to, val);
        }
        int[] res = diff.result();
        for(int i = 0;i < res.length;i++){
            if(res[i] > capacity){
                return false;
            }
        }
        return true;
    }
}

class Difference{
    int[] diff;
    Difference(int[] nums){
        diff = new int[nums.length];
        diff[0] = nums[0];
        for(int i = 1;i < diff.length;i++){
            diff[i] = nums[i]-nums[i-1];
        }
    }

    public void increment(int i,int j, int val){
        diff[i]+=val;
        if(j+1 < diff.length){
            diff[j+1]-=val;
        }
    }

    public int[] result(){
        int[] res = new int[diff.length];
        res[0] = diff[0];
        for(int i = 1; i < diff.length;i++){
            res[i] = diff[i]+res[i-1];
        }
        return res;
    }

}

矩阵

LeetCode 54. 螺旋矩阵

在这里插入图片描述

代码实现

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res=new ArrayList<>();
        if (matrix.length==0){
            return res;
        }
        int up=0,down=matrix.length-1,left=0,right=matrix[0].length-1;
        int x=0,y=0;
        while (up<=down&&left<=right){
            for (y=left;y<=right&&(left<=right&&up<=down);y++){
                res.add(matrix[x][y]);
            }
            y--;
            up++;
            for(x=up;x<=down&&(left<=right&&up<=down);x++){
                res.add(matrix[x][y]);
            }
            x--;
            right--;
            for (y=right;y>=left&&(left<=right&&up<=down);y--){
                res.add(matrix[x][y]);
            }
            y++;
            down--;
            for (x=down;x>=up&&(left<=right&&up<=down);x--){
                res.add(matrix[x][y]);
            }
            x++;
            left++;
        }
        return res;
    }
}

单链表专题-双指针技巧

LeetCode 21. 合并两个有序链表

在这里插入图片描述

解题思路

在这里插入图片描述
这个算法的逻辑类似于「拉拉链」,l1, l2类似于拉链两侧的锯齿,指针p就好像拉链的拉索,将两个有序链表合并。

代码中还用到一个链表的算法题中是很常见的「虚拟头节点」技巧,也就是dummy节点。

代码实现

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }

        ListNode pHead = new ListNode(-1);
        ListNode p = pHead;
        while(list1 != null && list2 != null){
            if(list1.val <= list2.val){
                pHead.next = list1;
                list1 = list1.next;
            }else{
                pHead.next = list2;
                list2 = list2.next;
            }
            pHead = pHead.next;
        }
        if(list1 != null){
            pHead.next = list1;
        }
        if(list2 != null){
            pHead.next = list2;
        }
        return p.next;

    }
}

LeetCode 88. 合并两个有序数组

在这里插入图片描述

解题思路

逆向双指针
在这里插入图片描述

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = m - 1, p2 = n - 1;
        int tail = m + n - 1;
        int cur;
        while (p1 >= 0 || p2 >= 0) {
            if (p1 == -1) {
                cur = nums2[p2--];
            } else if (p2 == -1) {
                cur = nums1[p1--];
            } else if (nums1[p1] > nums2[p2]) {
                cur = nums1[p1--];
            } else {
                cur = nums2[p2--];
            }
            nums1[tail--] = cur;
        }
    }
}

LeetCode 23. 合并 K 个升序链表

在这里插入图片描述

解题思路

优先队列pq中的元素个数最多是k,所以一次poll或者add方法的时间复杂度是O(logk);所有的链表节点都会被加入和弹出pq,所以算法整体的时间复杂度是O(Nlogk),其中k是链表的条数,N是这些链表的节点总数。

代码实现

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> queue = new PriorityQueue<>((a, b)->(a.val-b.val));
        for(ListNode list :lists){
            if(list != null){
                queue.add(list);
            }
        }
        ListNode pHead = new ListNode(-1);
        ListNode p = pHead;
        while(!queue.isEmpty()){
            ListNode node = queue.poll();
            p.next = node;
            if(node.next != null){
                queue.add(node.next);
            }
            p = p.next;
        }
        return pHead.next;

    }
}

LeetCode 19. 删除链表的倒数第 N 个结点

在这里插入图片描述

解题思路

要想删除倒数第N个节点,首先你要找到这个节点,下面双指针是非常经典的思路:

// 返回链表的倒数第 k 个节点
ListNode findFromEnd(ListNode head, int k) {
    ListNode p1 = head;
    // p1 先走 k 步
    for (int i = 0; i < k; i++) {
        p1 = p1.next;
    }
    ListNode p2 = head;
    // p1 和 p2 同时走 n - k 步
    while (p1 != null) {
        p2 = p2.next;
        p1 = p1.next;
    }
    // p2 现在指向第 n - k 个节点
    return p2;
}

代码实现

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode p1 = dummy, p2 = head;
        while(n > 0 && p2 != null){
            n--;
            p2 = p2.next;
        }
        while(p1 != null && p2 != null){
            p1 = p1.next;
            p2 = p2.next;
        }

        p1.next = p1.next.next;
        return dummy.next;
    }


}

LeetCode 876. 链表的中间结点

在这里插入图片描述

解题思路

两个指针slow和fast分别指向链表头结点head。
每当慢指针slow前进一步,快指针fast就前进两步,这样,当fast走到链表末尾时,slow就指向了链表中点。

代码实现

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}

LeetCode 141. 环形链表

在这里插入图片描述

解题思路

每当慢指针slow前进一步,快指针fast就前进两步。
如果fast最终遇到空指针,说明链表中没有环;如果fast最终和slow相遇,那肯定是fast超过了slow一圈,说明链表中含有环。

代码实现

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                return true;
            }
        }
        return false;
    }
}

LeetCode 142. 环形链表 II

在这里插入图片描述

解题思路

fast一定比slow多走了k步,这多走的k步其实就是fast指针在环里转圈圈,所以k的值就是环长度的「整数倍」。
在这里插入图片描述

假设相遇点距环的起点的距离为m,那么结合上图的 slow 指针,环的起点距头结点head的距离为k - m,也就是说如果从head前进k - m步就能到达环起点。

巧的是,如果从相遇点继续前进k - m步,也恰好到达环起点。因为结合上图的 fast 指针,从相遇点开始走k步可以转回到相遇点,那走k - m步肯定就走到环起点了:
在这里插入图片描述

代码实现

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null || head.next == null){
            return null;
        }
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(fast == slow){
                break;
            }
        }
        if(fast == null || fast.next == null){
            return null;
        }
        slow = head;
        while(slow != fast){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

LeetCode 160. 相交链表

在这里插入图片描述

解题思路

解决这个问题的关键是,通过某些方式,让p1和p2能够同时到达相交节点c1

所以,可以让p1遍历完链表A之后开始遍历链表B,让p2遍历完链表B之后开始遍历链表A,这样相当于「逻辑上」两条链表接在了一起。

如果这样进行拼接,就可以让p1和p2同时进入公共部分,也就是同时到达相交节点c1:
在这里插入图片描述

代码实现

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA, p2 = headB;
        while(p1 != p2){
            if(p1 == null){
                p1 = headB;
            }else{
                p1 = p1.next;
            }
            if(p2 == null){
                p2 = headA;
            }else{
                p2 = p2.next;
            }
        }
        return p1;
    }
}

LeetCode 148. 排序链表

在这里插入图片描述

解题思路

参考数组归并排序思路,先找到链接中间节点,一分为二,左右两部分做排序递归,之后整合左右两个部分有序的链表合并为一个有序链表。
在这里插入图片描述

代码实现

class Solution {
    public ListNode sortList(ListNode head) {
        if(head==null||head.next==null){
            return head;
        }
        ListNode slow=head;
        ListNode fast=head.next;//设置为next是为了在链表只剩下两个元素时,不会走while循环,而直接将链表分为两半
                                 //如果设置为head,走while循环那么slow指向第二个元素,而fast为空!出错
        while (fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        ListNode last=slow.next;
        slow.next=null;
        ListNode first=sortList(head);
        ListNode second=sortList(last);
        ListNode res=merge(first,second);
        return res;
    }

    public ListNode merge(ListNode node1,ListNode node2){
        ListNode pHead=new ListNode(0);
        ListNode cur=pHead;
        while (node1!=null&&node2!=null){
            if (node1.val<node2.val){
                cur.next=node1;
                node1=node1.next;
            }else {
                cur.next=node2;
                node2=node2.next;
            }

            cur=cur.next;
        }
        if (node1!=null){
            cur.next=node1;
        }
        if (node2!=null){
            cur.next=node2;
        }
        //cur.next=node1!=null?node1:node2;
        return pHead.next;
    }
}

LeetCode 234. 回文链表

在这里插入图片描述

代码实现

方法一:将值复制到数组中后用双指针法

class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals = new ArrayList<Integer>();

        // 将链表的值复制到数组中
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }

        // 使用双指针判断是否回文
        int front = 0;
        int back = vals.size() - 1;
        while (front < back) {
            if (!vals.get(front).equals(vals.get(back))) {
                return false;
            }
            front++;
            back--;
        }
        return true;
    }
}

方法二:与反转链表逐个比对

class Solution {
    public boolean isPail (ListNode head) {
        //快慢指针找到中点
        ListNode fast = head, slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //fast != null 说明是奇数长度
        if (fast != null) slow = slow = slow.next;
        
        slow = reverseList(slow);
        fast = head;
        while (slow != null && fast != null) {
            if (slow.val != fast.val) return false;
            fast = fast.next;
            slow = slow.next;
        }

        return true;
    }

    public ListNode reverseList(ListNode head) {

        if (head == null || head.next == null) return head;

        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null;

        return last;

    }
}

反转链表

LeetCode 206. 反转链表

在这里插入图片描述

解题思路

在这里插入图片描述
翻转后处理头节点:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

非递归版:

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }

        ListNode pre = null, cur = head;
        while(cur != null){
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        
        return pre;
    }
}

递归版本:

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }
}

反转链表前 N 个节点

解题思路

在这里插入图片描述
在这里插入图片描述

  1. base case 变为n == 1,反转一个元素,就是它本身,同时要记录后驱节点。
  2. 刚才直接把head.next设置为 null,因为整个链表反转后原来的head变成了整个链表的最后一个节点。但现在head节点在递归反转之后不一定是最后一个节点了,所以要记录后驱successor(第 n + 1 个节点),反转之后将head连接上。

因为是反转的前N个节点和全部反转不同,链表后半部分还是原来顺序,所以要有一个successor的连接点,这个连接点就是反转后head.next需要连接的。原来整体反转后head就是尾节点可以设置为null,但现在部分反转后是不行的。

代码实现

ListNode successor = null; // 后驱节点

// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode reverseN(ListNode head, int n) {
    if (n == 1) { 
        // 记录第 n + 1 个节点
        successor = head.next;
        return head;
    }
    // 以 head.next 为起点,需要反转前 n - 1 个节点
    ListNode last = reverseN(head.next, n - 1);

    head.next.next = head;
    // 让反转之后的 head 节点和后面的节点连起来
    head.next = successor;
    return last;
}    

LeetCode 92. 反转链表 II

在这里插入图片描述

解题思路

如果m != 1怎么办?如果把head的索引视为 1,那么是想从第m个元素开始反转对吧;如果把head.next的索引视为 1 呢?那么相对于head.next,反转的区间应该是从第m - 1个元素开始的;那么对于head.next.next呢……

代码实现

class Solution {
    ListNode successor = null;
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if(left == 1){
            return reverseN(head, right);
        }
        head.next = reverseBetween(head.next, left-1, right-1);
        return head;
    }

    public ListNode reverseN(ListNode head, int n){
        if(n == 1){
            successor = head.next;
            return head;
        }
        ListNode last = reverseN(head.next, n-1);
        head.next.next = head;
        head.next = successor;
        return last;
    }
}

数组专题-双指针技巧

LeetCode 26. 删除有序数组中的重复项

在这里插入图片描述

解题思路

由于数组已经排序,所以重复的元素一定连在一起,找出它们并不难。
让慢指针slow走在后面,快指针fast走在前面探路,找到一个不重复的元素就赋值给slow并让slow前进一步。

这样,就保证了nums[0…slow]都是无重复的元素,当fast指针遍历完整个数组nums后,nums[0…slow]就是整个数组去重之后的结果。

代码实现

class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums.length == 0){
            return 0;
        }
        int slow = 0, fast = 0;
        while(fast < nums.length){
            if(nums[slow] != nums[fast]){
                slow++;
                nums[slow] = nums[fast];
            }
            fast++;
        }
        return slow+1;
    }
}

LeetCode 83. 删除排序链表中的重复元素

在这里插入图片描述

解题思路

思路同LeetCode 26. 删除有序数组中的重复项,唯一区别数组变链表,循环完毕后,slow节点next置为null。

代码实现

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode slow = head, fast = head;
        while(fast != null){
            if(slow.val != fast.val){
                slow.next = fast;
                slow = slow.next;
            }
            fast = fast.next;
        }
        slow.next = null;
        return head;
    }
}

LeetCode 80. 删除有序数组中的重复项 II

在这里插入图片描述

解题思路

为了让解法更具有一般性,我们将原问题的「保留 2 位」修改为「保留 k 位」。

  • 由于是保留 k 个相同数字,对于前 k 个数字,我们可以直接保留
  • 对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留

代码实现

class Solution {
   public int removeDuplicates(int[] nums) {
     int k = 2; // 每个元素最多出现的次数
    if (nums.length <= k) return nums.length; // 如果数组长度小于等于 k,直接返回数组长度

    // 初始化慢指针和快指针
    int slow = k, fast = k;
    while (fast < nums.length) {
        // 如果 nums[fast] 不等于 nums[slow - k]
        // 则将 nums[fast] 复制到 nums[slow],并将 slow 向前移动一位
        if (nums[fast] != nums[slow - k]) { //nums[slow - k] 是当前考虑的元素在新数组中的第一个可能的位置
            nums[slow] = nums[fast];
            slow++;
        }
        // 将 fast 向前移动一位,以检查下一个元素
        fast++;
    }
    // 返回新数组的长度
    return slow;
}

LeetCode 27. 移除元素

在这里插入图片描述

解题思路

和有序数组(LeetCode 26. 删除有序数组中的重复项)去重的解法有一个细节差异,这里是先给nums[slow]赋值然后再给slow++,**因为数组第一位可能就是等于val的元素,所以slow++操作要后置。而数组元素去重,第一位一定可以保留,从第二位开始判断是否和前一位元素重复。**这样保证nums[0…slow-1]是不包含值为val的元素的,最后的结果数组长度就是slow。

代码实现

class Solution {
    public int removeElement(int[] nums, int val) {
        int slow = 0, fast = 0;
        while(fast < nums.length){
            if(nums[fast] != val){
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }

        return slow;
    }
}

LeetCode 283. 移动零

在这里插入图片描述

解题思路

该题配合LeetCode 27. 移除元素即可完成,先将元素为0的进行剔除,最后剩下的直接赋值为0即可。

代码实现

class Solution {
    public void moveZeroes(int[] nums) {
        int idx = removeEle(nums, 0);
        for(; idx < nums.length;idx++){
            nums[idx] = 0;
        }
    }

    public int removeEle(int[] nums, int val){
        int slow = 0, fast = 0;
        while(fast < nums.length){
            if(nums[fast] != val){
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }
}

LeetCode 344. 反转字符串

在这里插入图片描述

解题思路

左右指针置换即可。

代码实现

class Solution {
    public void reverseString(char[] s) {
        int left = 0, right = s.length-1;
        while(left < right){
            char tmp = s[left];
            s[left] = s[right];
            s[right] = tmp;
            left++;
            right--;
        }
    }
}

LeetCode 5. 最长回文子串

在这里插入图片描述

解题思路

回文串的的长度可能是奇数也可能是偶数,解决该问题的核心是从中心向两端扩散的双指针技巧。

如果回文串的长度为奇数,则它有一个中心字符;如果回文串的长度为偶数,则可以认为它有两个中心字符。
该段代码主要计算字符串(i,j)为中心的最长回文串:

public String subStr(String s, int i, int j){
    while(i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)){
        i--;
        j++;
    }
    return s.substring(i+1, j);
}

之后遍历字符串时,每次都计算奇数和偶数情况下的最长回文串,然后比对长度。

代码实现

class Solution {
    public String longestPalindrome(String s) {
        String res = "";
        for(int i = 0;i < s.length();i++){
            String s1 = subStr(s, i, i);
            String s2 = subStr(s, i, i+1);
            res = res.length() > s1.length()? res:s1;
            res = res.length() > s2.length()? res:s2;
        }
        return res;
    }

    public String subStr(String s, int i, int j){
        while(i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)){
            i--;
            j++;
        }
        return s.substring(i+1, j);
    }
}

LeetCode 870. 优势洗牌

在这里插入图片描述

解题思路

需要对两个数组排序,但是nums2中元素的顺序不能改变,因为计算结果的顺序依赖nums2的顺序,所以不能直接对nums2进行排序,而是利用其他数据结构来辅助。

int n = nums1.length;

sort(nums1); // 田忌的马
sort(nums2); // 齐王的马

// 从最快的马开始比
for (int i = n - 1; i >= 0; i--) {
    if (nums1[i] > nums2[i]) {
        // 比得过,跟他比
    } else {
        // 比不过,换个垫底的来送人头
    }
}

代码实现

class Solution {
    public int[] advantageCount(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int[] res = new int[n];
        PriorityQueue<int[]> queue = new PriorityQueue<>(
            (int[] p1, int[] p2)-> {
                return p2[1]-p1[1];
            }
        );

        for(int i = 0;i < n;i++){
            queue.offer(new int[]{i, nums2[i]});
        }

        Arrays.sort(nums1);
        int left = 0, right = n-1;

        while(!queue.isEmpty()){
            int[] pair = queue.poll();
            int idx = pair[0], val = pair[1];
            if(val >= nums1[right]){
                res[idx] = nums1[left];
                left++;
            }else{
                res[idx] = nums1[right];
                right--;
            }
        }
        return res;
    }
}

LeetCode 42. 接雨水

在这里插入图片描述

解题思路

法一:动态规划
在这里插入图片描述

法二:双指针
在这里插入图片描述

代码实现

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        if (n == 0) {
            return 0;
        }

        int[] leftMax = new int[n];
        leftMax[0] = height[0];
        for (int i = 1; i < n; ++i) {
            leftMax[i] = Math.max(leftMax[i - 1], height[i]);
        }

        int[] rightMax = new int[n];
        rightMax[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; --i) {
            rightMax[i] = Math.max(rightMax[i + 1], height[i]);
        }

        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans += Math.min(leftMax[i], rightMax[i]) - height[i];
        }
        return ans;
    }
}
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = Math.max(leftMax, height[left]);
            rightMax = Math.max(rightMax, height[right]);
            if (height[left] < height[right]) {
                ans += leftMax - height[left];
                ++left;
            } else {
                ans += rightMax - height[right];
                --right;
            }
        }
        return ans;
    }
}

NSum问题

LeetCode 1. 两数之和

在这里插入图片描述

解题思路

本题求的是数组下标集合,如果是数组元素集合,可以先对数组排序,然后执行下面逻辑,即可求出所有结果集合:

while (lo < hi) {
    int sum = nums[lo] + nums[hi];
    // 记录索引 lo 和 hi 最初对应的值
    int left = nums[lo], right = nums[hi];
    if (sum < target)      lo++;
    else if (sum > target) hi--;
    else {
        res.push_back({left, right});
        // 跳过所有重复的元素
        while (lo < hi && nums[lo] == left) lo++;
        while (lo < hi && nums[hi] == right) hi--;
    }
}

代码实现

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int left = 0, right = nums.length-1;
        Map<Integer, Integer> map = new HashMap<>();
        int[] res = new int[2];
        for(int i = 0; i < nums.length;i++){
            if(map.containsKey(target-nums[i])){
                res[0] = i;
                res[1] = map.get(target-nums[i]);
            }
            map.put(nums[i], i);
        }
        
        return res;
    }
}

LeetCode 15. 三数之和

在这里插入图片描述

解题思路

代码实现

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for(int i = 0;i < nums.length-2;i++){
            int target = 0-nums[i];
            int left = i+1, right = nums.length-1;
            if(nums[i] > 0){
                break;
            }
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            while(left < right){
                int sum = nums[left]+nums[right];
                if(target == sum){
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    while(left < right && nums[left] == nums[left+1]){
                        left++;
                    }
                    while(left < right && nums[right] == nums[right-1]){
                        right--;
                    }
                    left++;
                    right--;
                }else if(sum < target){
                    left++;
                }else if(sum > target){
                    right--;
                }
            }
        }
        return res;
    }
}

LeetCode 18. 四数之和

在这里插入图片描述

解题思路

代码实现

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
        for(int i = 0;i < nums.length-2;i++){
            for(int j = i+1; j < nums.length-2;j++){
                int tar = target-nums[i]-nums[j];
                int low = j+1, high = nums.length-1;
            
                if (i==0||nums[i]!=nums[i-1]||nums[j]!=nums[j-1]){
                    while(low < high){
                        int left = nums[low], right = nums[high];
                        int sum = nums[low]+nums[high];
                        if(sum == tar){
                            res.add(Arrays.asList(nums[i], nums[j], left, right));
                            while(low < high && nums[low+1] == left){
                                low++;
                            }
                            while(low < high && nums[high-1] == right){
                                high--;
                            }
                            low++;
                            high--;
                        }else if(sum < tar){
                            low++;
                        }else if(sum > tar){
                            high--;
                        }
                    }
                }
            }
        }

        for (List<Integer> tmp:res){
            if (!result.contains(tmp)){
                result.add(tmp);
            }
        }
        return result;
    }
}

NSum之和

代码实现

/* 注意:调用这个函数之前一定要先给 nums 排序 */
vector<vector<int>> nSumTarget(
    vector<int>& nums, int n, int start, int target) {

    int sz = nums.size();
    vector<vector<int>> res;
    // 至少是 2Sum,且数组大小不应该小于 n
    if (n < 2 || sz < n) return res;
    // 2Sum 是 base case
    if (n == 2) {
        // 双指针那一套操作
        int lo = start, hi = sz - 1;
        while (lo < hi) {
            int sum = nums[lo] + nums[hi];
            int left = nums[lo], right = nums[hi];
            if (sum < target) {
                while (lo < hi && nums[lo] == left) lo++;
            } else if (sum > target) {
                while (lo < hi && nums[hi] == right) hi--;
            } else {
                res.push_back({left, right});
                while (lo < hi && nums[lo] == left) lo++;
                while (lo < hi && nums[hi] == right) hi--;
            }
        }
    } else {
        // n > 2 时,递归计算 (n-1)Sum 的结果
        for (int i = start; i < sz; i++) {
            vector<vector<int>> 
                sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
            for (vector<int>& arr : sub) {
                // (n-1)Sum 加上 nums[i] 就是 nSum
                arr.push_back(nums[i]);
                res.push_back(arr);
            }
            while (i < sz - 1 && nums[i] == nums[i + 1]) i++;
        }
    }
    return res;
}

滑动窗口专题

套路模版

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 增大窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);
        /********************/

        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

LeetCode 76. 最小覆盖子串

在这里插入图片描述

解题思路

使用 Java 的读者要尤其警惕语言特性的陷阱。Java 的 Integer,String 等类型判定相等应该用equals方法而不能直接用等号==,这是 Java 包装类的一个隐晦细节。

代码实现

class Solution {
    public String minWindow(String s, String t) {
        Map<Character, Integer> window = new HashMap<>();
        Map<Character, Integer> need = new HashMap<>();
        int valid = 0;
        int left = 0, right = 0;
        int start = 0, len = Integer.MAX_VALUE;
        for(char c : t.toCharArray()){
            need.put(c, need.getOrDefault(c,0)+1);
        }

        while(right < s.length()){
            char d = s.charAt(right);
            right++;
            if(need.containsKey(d)){
                window.put(d, window.getOrDefault(d, 0)+1);
                if(need.get(d).equals(window.get(d))){
                    valid++;
                }
            }

            while(valid == need.size()){
                if(right-left < len){
                    start = left;
                    len = right-left;
                }
                char c = s.charAt(left);
                left++;
                if(need.containsKey(c)){
                    if(need.get(c).equals(window.get(c))){
                        valid--;
                    }
                    window.put(c, window.getOrDefault(c, 0)-1);
                }
            }
        }
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start+len);

    }
}

LeetCode 567. 字符串的排列

在这里插入图片描述

解题思路

在《LeetCode 76. 最小覆盖子串》的基础之上,额外修改两个地方:

  1. 本题移动left缩小窗口的时机是窗口大小大于t.size()时,应为排列嘛,显然长度应该是一样的。

  2. 当发现valid == need.size()时,就说明窗口中就是一个合法的排列,所以立即返回true。

代码实现

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();
        int valid = 0;
        for(char c: s1.toCharArray()){
            need.put(c, need.getOrDefault(c, 0)+1);
        }
        int left = 0, right = 0;
        while(right < s2.length()){
            char d = s2.charAt(right);
            right++;
            if(need.containsKey(d)){
                window.put(d, window.getOrDefault(d,0)+1);
                if(need.get(d).equals(window.get(d))){
                    valid++;
                }
            }

            while(right-left>=s1.length()){
                if(need.size() == valid){
                    return true;
                }
                char c = s2.charAt(left);
                left++;
                if(need.containsKey(c)){
                    if(window.get(c).equals(need.get(c))){
                        valid--;
                    }
                    window.put(c, window.get(c)-1);
                }
            }
        }
        return false;
    }
}

LeetCode 438. 找到字符串中所有字母异位词

在这里插入图片描述

解题思路

核心记住下面:
异位词即长度相同,那么窗口缩小实际就是right-left=p字符串长度,更进一步如果valid=needs.size(),说明找到了一个符合条件的异位词。

代码实现

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        Map<Character, Integer> needs = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();
        int valid = 0;
        int left = 0, right = 0;
        for(char c: p.toCharArray()){
            needs.put(c, needs.getOrDefault(c,0)+1);
        }
        List<Integer> res = new ArrayList<>();
        while(right < s.length()){
            char d = s.charAt(right);
            right++;
            if(needs.containsKey(d)){
                window.put(d, window.getOrDefault(d,0)+1);
                if(needs.get(d).equals(window.get(d))){
                    valid++;
                }
            }
            while(right-left == p.length()){
                if(valid == needs.size()){
                    res.add(left);
                }
                char c = s.charAt(left);
                left++;
                if(needs.containsKey(c)){
                    if(needs.get(c).equals(window.get(c))){
                        valid--;
                    }
                    window.put(c, window.get(c)-1);
                }
            }
        }
        return res;
    }
}

LeetCode 3. 无重复字符的最长子串

在这里插入图片描述

解题思路

连need和valid都不需要,而且更新窗口内数据也只需要简单的更新计数器window即可。
当window[c]值大于 1 时,说明窗口中存在重复字符,不符合条件,就该移动left缩小窗口了嘛。
唯一需要注意的是,在哪里更新结果res呢?我们要的是最长无重复子串,哪一个阶段可以保证窗口中的字符串是没有重复的呢?
这里和之前不一样,要在收缩窗口完成后更新res
,因为窗口收缩的 while 条件是存在重复元素,换句话说收缩完成后一定保证窗口中没有重复嘛。

代码实现

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> window = new HashMap<>();
        int left = 0, right = 0;
        int res = 0;
        while(right < s.length()){
            char d = s.charAt(right);
            right++;
            window.put(d, window.getOrDefault(d,0)+1);
            while(window.get(d)>1){
                char c = s.charAt(left);
                left++;
                window.put(c, window.get(c)-1);
            }
            res = Math.max(res, right-left);
        }
        return res;
    }
}

二分查找专题

套路模版

寻找一个数(基本的二分搜索)

int binarySearch(int[] nums, int target) {
    int left = 0; 
    int right = nums.length - 1; // 注意

    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; // 注意
        else if (nums[mid] > target)
            right = mid - 1; // 注意
    }
    return -1;
}

寻找左侧边界的二分搜索

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length; // 注意

    while (left < right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    // target 比所有数都大
	if (left == nums.length) return -1;
	// 类似之前算法的处理方式
	return nums[left] == target ? left : -1;
}

寻找右侧边界的二分查找

int right_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0, right = nums.length;

    while (left < right) {
        int mid = (left + right) / 2;
        if (nums[mid] == target) {
            left = mid + 1; // 注意
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    if (left == 0) return -1;
	return nums[left-1] == target ? (left-1) : -1;
}

“一统天下”

int binary_search(int[] nums, int target) {
    int left = 0, right = nums.length - 1; 
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1; 
        } else if(nums[mid] == target) {
            // 直接返回
            return mid;
        }
    }
    // 直接返回
    return -1;
}

int left_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定左侧边界
            right = mid - 1;
        }
    }
    // 最后要检查 left 越界的情况
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}


int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定右侧边界
            left = mid + 1;
        }
    }
    // 最后要检查 right 越界的情况
    if (right < 0 || nums[right] != target)
        return -1;
    return right;
}

在这里插入图片描述

LeetCode 875. 爱吃香蕉的珂珂

在这里插入图片描述

解题思路

x:吃香蕉速度,f(x)吃香蕉耗时为x的递减函数。
吃香蕉最小值为1,最大就是数组元素最大值(开区间right要加1)
在这里插入图片描述

代码实现

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        int left = 1, right = 1000000000+1;
        while(left < right){
            int mid = left+(right-left)/2;
            if(f(piles, mid) == h){
                right = mid;
            }else if(f(piles, mid) > h){
                left = mid+1;
            }else if(f(piles, mid) < h){
                right = mid;
            }
        }
        return left;
    }


    public int f(int[] piles, int k){
        int res = 0;
        for(int p:piles){
            res+=p/k;
            if(p%k > 0){
                res++;
            }
        }
        return res;
    }
}

LeetCode 1011. 在 D 天内送达包裹的能力

在这里插入图片描述

解题思路

思路同《LeetCode 875. 爱吃香蕉的珂珂》,只不过区间最值不同,一天至少能装满一船,所以最小值为数组中最重的包裹,最大值就是一天把所有包裹全部运完。

代码实现

class Solution {
    public int shipWithinDays(int[] weights, int days) {
        int left = 0, right = 1;
        for(int w:weights){
            left = Math.max(left, w);
            right+=w;
        }
        while(left < right){
            int mid = left+(right-left)/2;
            if(f(weights, mid) == days){
                right = mid;
            }else if(f(weights, mid) > days){
                left = mid+1;
            }else if(f(weights, mid) < days){
                right = mid;
            }
        }
        return left;
    }

    public int f(int[] weights, int x){
        int day = 0;
        for(int i = 0;i < weights.length;){
            int cap = x;
            while(i < weights.length){
                if(cap < weights[i]){
                    break;
                }else{
                    cap-=weights[i];
                }
                i++;
            }
            day++;
        }
        return day;
    }
}

LeetCode 410. 分割数组的最大值

在这里插入图片描述

解题思路

本题代码与《LeetCode 1011. 在 D 天内送达包裹的能力》一模一样,但是你是不是还没缓过神来? 那么我把题目翻译一下:现在有一堆货物,重量为nums[i],要求在m天内运送完毕,求货船最小载重为多少? 这本质是不是和题目一个意思!

代码实现

class Solution {
    public int splitArray(int[] nums, int k) {
        int left = 0, right = 1;
        for(int c : nums){
            left = Math.max(c, left);
            right+=c;
        }
        while(left < right){
            int mid = left+(right-left)/2;
            if(f(nums, mid) == k){
                right = mid;
            }else if(f(nums, mid) > k){
                left = mid+1;
            }else if(f(nums, mid) < k){
                right = mid;
            }
        }
        return left;
    }

    public int f(int[] nums, int x){
        int res = 0;
        for(int i = 0;i < nums.length;){
            int c = x;
            while(i < nums.length){
                if(c < nums[i]){
                    break;
                }else{
                    c-=nums[i];
                }
                i++;
            }
            res++;
        }
        return res;
    }
}

LCR 172. 统计目标成绩的出现次数

在这里插入图片描述

代码实现

class Solution {
    public int countTarget(int[] nums, int target) {
         // write code here
        int leftIndex = leftBoundSearch(nums, target);
        int rightIndex = rightBoundSearch(nums, target);
        
        if(leftIndex == -1 || rightIndex == -1) return 0;

        return rightIndex - leftIndex + 1;
    }

    private int leftBoundSearch(int[] nums, int target){

        int left = 0,right = nums.length - 1;
        //搜索区间为 [left,right]
        while(left <= right){

            int mid = left + (right - left)/2;
            if(nums[mid] < target){
                //搜索区间变为 [mid + 1,right]
                left = mid + 1;
            }else if(nums[mid] > target){
                //搜索区间变为 [left,mid - 1]
                right = mid - 1;
            }else if(nums[mid] == target){
                //收缩右侧边界
                right = mid - 1;
            }

        }
        //检查出界情况
        if(left >= nums.length || nums[left] != target) return -1;

        return left;
    }


    private int rightBoundSearch(int[] nums, int target){

        int left = 0,right = nums.length - 1;
        while(left <= right){

            int mid = left + (right - left)/2;
            if(nums[mid] < target){
                left = mid + 1;
            }else if(nums[mid] > target){
                right = mid - 1;
            }else if(nums[mid] == target){
                //收缩左侧边界
                left = mid + 1;
            }

        }
        //检查出界情况
        if(right < 0 || nums[right] != target) return -1;

        return right;
    }
}

区间问题

LeetCode 1288. 删除被覆盖区间

在这里插入图片描述

解题思路

在这里插入图片描述

  • 对于情况一,找到了覆盖区间。
  • 对于情况二,两个区间可以合并,成一个大区间。
  • 对于情况三,两个区间完全不相交。

代码实现

class Solution {
    public int removeCoveredIntervals(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> {
            return a[0]-b[0] == 0 ? b[1]-a[1]:a[0]-b[0];
        });
        int left = intervals[0][0];
        int right = intervals[0][1];
        int res = 0;
        for(int i = 1; i < intervals.length;i++){
            int[] arr = intervals[i];
            if(arr[0] >= left && arr[1] <= right){
                res++;
            }

            if(right < arr[0]){
                left = arr[0];
                right = arr[1];
            }

            if(right > arr[0] && right < arr[1]){
                right = arr[1];
            }
        }
        return intervals.length - res;
    }
}

LeetCode 56. 合并区间

在这里插入图片描述

解题思路

对于几个相交区间合并后的结果区间x,x.start一定是这些相交区间中start最小的,x.end一定是这些相交区间中end最大的。
在这里插入图片描述

代码实现

class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> {
            if(a[0] - b[0] == 0){
                return b[0]-a[0];
            }
            return a[0]-b[0];
        });

        List<int[]> res = new ArrayList<>();
        for(int i = 0;i < intervals.length;i++){
            int left = intervals[i][0];
            int right = intervals[i][1];
            while(i < intervals.length-1 && right >= intervals[i+1][0]){
                i++;
                right = Math.max(right, intervals[i][1]);
            }
            res.add(new int[]{left, right});
        }

        int n = res.size();
        int[][] result = new int[n][2];
        for(int i = 0;i < n;i++){
            int[] a = res.get(i);
            result[i][0] = a[0];
            result[i][1] = a[1];
        }
        return result;

    }
}

LeetCode 986. 区间列表的交集

在这里插入图片描述

解题思路

对于两个区间,我们用[a1,a2]和[b1,b2]表示在A和B中的两个区间,那么什么情况下这两个区间有交集呢:

# 不等号取反,or 也要变成 and
if b2 >= a1 and a2 >= b1:
    [a1,a2][b1,b2] 存在交集

两个区间存在交集的情况有哪些呢

在这里插入图片描述
如果交集区间是[c1,c2],那么c1=max(a1,b1),c2=min(a2,b2)!这一点就是寻找交集的核心.

代码实现

class Solution {
    public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
        List<int[]> res = new ArrayList<>();
        int i = 0, j = 0;
        while(i < firstList.length && j < secondList.length){
            int a1 = firstList[i][0], a2 = firstList[i][1];
            int b1 = secondList[j][0], b2 = secondList[j][1];
            if(a2 >= b1 && b2 >= a1){
                res.add(new int[]{Math.max(a1, b1), Math.min(a2, b2)});
            }
            //单独走if分支判断,因为有交集时,也符合b2 >= a2
            if(b2 >= a2){
                i++;
            }else{
                j++;
            }
        }

        int n = res.size();
        int[][] r = new int[n][2];
        for(int k = 0; k < n;k++){
            int[] arr = res.get(k);
            r[k][0] = arr[0];
            r[k][1] = arr[1];
        }
        return r;

    }
}

数组考察

LeetCode 448. 找到所有数组中消失的数字

在这里插入图片描述

解题思路

把相应的nums中的数对应的-1下标位置,乘以-1 表明这个下标也就是这个数出现过了,判断不是负数的情况就是缺失元素。

代码实现

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        int n = nums.length;
        LinkedList<Integer> list = new LinkedList<>();
        for(int i = 0; i < n; i++){
        //把相应的nums中的数对应的-1下标位置,乘以-1 表明这个下标也就是这个数出现过了
            if(nums[Math.abs(nums[i]) - 1] > 0)
                nums[Math.abs(nums[i]) - 1] *= -1; 
        }
        for(int i = 0; i < n; i++){
            if(nums[i] > 0)
                list.add(i+1);
        }
        return list;
    }
}

LeetCode 240. 搜索二维矩阵 II

在这里插入图片描述

代码实现

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int i = matrix.length - 1, j = 0;
        while(i >= 0 && j < matrix[0].length)
        {
            if(matrix[i][j] > target) i--;
            else if(matrix[i][j] < target) j++;
            else return true;
        }
        return false;
    }
}

LeetCode 49. 字母异位词分组

在这里插入图片描述

代码实现

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        for (String str : strs) {
            char[] array = str.toCharArray();
            Arrays.sort(array);
            String key = new String(array);
            List<String> list = map.getOrDefault(key, new ArrayList<String>());
            list.add(str);
            map.put(key, list);
        }
        return new ArrayList<List<String>>(map.values());
    }
}

LeetCode 33. 搜索旋转排序数组

在这里插入图片描述

代码实现

class Solution {
    public int search(int[] nums, int target) {
        int minIndex = findMinIndex(nums);
        int res = search(nums, target, 0, minIndex);
        if (res == -1) {
            res = search(nums, target, minIndex, nums.length);
        }
        return res;
    }
    // 找最小值的下标
    private int findMinIndex(int[] nums) {
        int index = 1;
        while (index < nums.length-1) {
            // 比左右两边元素都小的就是nums的最小值
            if (nums[index] < nums[index-1] && nums[index] < nums[index+1]) {
                return index;
            }
            index++;
        }
        // 第一个或最后一个元素可能是最小元素
        return nums[0]<nums[nums.length-1] ? 0 : nums.length-1;
    }
    private int search(int[] nums, int target, int start, int end) {
        while (start < end) {
            int mid = start + (end-start)/2;
            if (nums[mid] > target) {
                end = mid;
            }
            else if (nums[mid] < target) {
                start = mid+1;
            }
            else {
                return mid;
            }
        }
        return -1;
    }
}

LeetCode 31. 下一个排列

在这里插入图片描述

解题思路

在这里插入图片描述

代码实现

class Solution {
    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        if (i >= 0) {
            int j = nums.length - 1;
            while (j >= 0 && nums[i] >= nums[j]) {
                j--;
            }
            swap(nums, i, j);
        }
        reverse(nums, i + 1);
    }

    public void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public void reverse(int[] nums, int start) {
        int left = start, right = nums.length - 1;
        while (left < right) {
            swap(nums, left, right);
            left++;
            right--;
        }
    }
}

LeetCode 85. 最大矩形

在这里插入图片描述

代码实现

class Solution {
    public int maximalRectangle(char[][] matrix) {
        int m = matrix.length;
        if (m == 0) {
            return 0;
        }
        int n = matrix[0].length;
        int maxArea = 0;
        int[] heights = new int[n];         // 柱高
        for (int row = 0; row < m; row++) {
            // 一行一行遍历,保存柱高
            // 一层一层遍历,保存柱高,相当于计算柱状图中的最大矩形
            for (int col = 0; col < n; col++) {
                if (matrix[row][col] == '1') {
                    heights[col] += 1;
                } else {
                    heights[col] = 0;
                }                          
            }
            maxArea = Math.max(maxArea, largestArea(heights));
        }        
        return maxArea;
    }
    // LeetCode 84
    public int largestArea(int[] heights) {
        int n = heights.length;
        if (n == 0) {
            return 0;
        }

        int[] left = new int[n];
        int[] right = new int[n];
        Arrays.fill(right, n);

        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
                right[stack.peek()] = i;
                stack.pop();
            }
            left[i] = stack.isEmpty() ? -1 : stack.peek();
            stack.push(i);
        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            res = Math.max(res, (right[i] - left[i] - 1) * heights[i]);
        }
        return res;
    }
}

LeetCode 84. 柱状图中最大的矩形

在这里插入图片描述

代码实现

while循环外处理值逻辑,遍历顺序和方向相反,即求左侧第一个最小值,正序遍历,反之同理。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] left = new int[n];
        Stack<Integer> st = new Stack<>();
        for (int i = 0; i < n; i++) {
            int x = heights[i];
            while (!st.isEmpty() && x <= heights[st.peek()]) {
                st.pop();
            }
            left[i] = st.isEmpty() ? -1 : st.peek();
            st.push(i);
        }

        int[] right = new int[n];
        st.clear();
        for (int i = n - 1; i >= 0; i--) {
            int x = heights[i];
            while (!st.isEmpty() && x <= heights[st.peek()]) {
                st.pop();
            }
            right[i] = st.isEmpty() ? n : st.peek();
            st.push(i);
        }

        int ans = 0;
        for (int i = 0; i < n; i++) {
            ans = Math.max(ans, heights[i] * (right[i] - left[i] - 1));
        }
        return ans;
    }
}

LeetCode 287. 寻找重复数

在这里插入图片描述

代码实现

class Solution {

    public int findDuplicate(int[] nums) {
        int len = nums.length; // n + 1 = len, n = len - 1

        // 在 [1..n] 查找 nums 中重复的元素
        int left = 1;
        int right = len - 1;
        while (left < right) {
            int mid = (left + right) / 2;

            // nums 中小于等于 mid 的元素的个数
            int count = 0;
            for (int num : nums) {
                if (num <= mid) {
                    count++;
                }
            }

            if (count > mid) {
                // 下一轮搜索的区间 [left..mid]
                right = mid;
            } else {
                // 下一轮搜索的区间 [mid + 1..right]
                left = mid + 1;
            }
        }
        return left;
    }
}

总结

本题来源于Leetcode中 归属于数组、链表类型题目。
同许多在算法道路上不断前行的人一样,不断练习,修炼自己!
如有博客中存在的疑问或者建议,可以在下方留言一起交流,感谢各位!

觉得本博客有用的客官,可以给个点赞+收藏哦! 嘿嘿

喜欢本系列博客的可以关注下,以后除了会继续更新面试手撕代码文章外,还会出其他系列的文章!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值