Loading

Kopoo的刷题日记

2022.4.6-前缀和数组

学习的是前缀数组

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

class NumArray {
    private int[]res;
    public NumArray(int[] nums) {
        res=new int[nums.length+1];
        for(int i=1;i<res.length;i++){
            //res[i]代表i个数组元素的和
            res[i]=nums[i-1]+res[i-1];
        }
    }
    
    public int sumRange(int left, int right) {
        return res[right+1]-res[left];
    }
}

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * int param_1 = obj.sumRange(left,right);
 */

前缀数组空间换时间,我觉得最主要的点是res数组代表了什么

res[i]记录nums[0...i-1]的累加和,因此最终取得[left,right]的数组和应该为res[right+1]-res[left]

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

class NumMatrix {
    private int[][] res;
    public NumMatrix(int[][] matrix) {
        int m=matrix.length;
        int n=matrix[0].length;
        if(m==0||n==0)return;
        res=new int[m+1][n+1];
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                res[i][j]=res[i-1][j]+res[i][j-1]-res[i-1][j-1]+matrix[i-1][j-1];
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        int result=res[row2+1][col2+1]-res[row2+1][col1]-res[row1][col2+1]+res[row1][col1];
        return result;
    }
}

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix obj = new NumMatrix(matrix);
 * int param_1 = obj.sumRegion(row1,col1,row2,col2);
 */

这题目其实不难,矩阵的面积是(A-B)(C-D) = AC - AD - BC + BD

但是要注意题目的测试用例,是从0,0 开始因此sumRegin方法取值最好不要用-1这样的,会越界异常

还有就是数组的定义

定义:res[i] [j]记录 matrix 中⼦矩阵 [0, 0, i-1, j-1] 的元素和

560. 和为 K 的子数组

class Solution {
    public int subarraySum(int[] nums, int k) {
        HashMap<Integer,Integer> map=new HashMap<>();
        int result=0;
        int sum=0;
        map.put(0,1);
        for(int num:nums){
            sum+=num;
            int temp=sum-k;
            if(map.containsKey(temp)){
                result+=map.get(temp);
            }
            map.put(sum,map.getOrDefault(sum,0)+1);
        }
        return result;
    }
}

这道题目感觉最难的是hashmap如何使用,实现放的key是累加和,value是累加和出现的次数

map 记录的是前缀和到该前缀和出现的次数的映射。

总结

总的来说该算法还是很巧妙的,就是需要用空间换时间的方式记录需要的数的和,然后需要计算时以O(1)的复杂度相减直接计算

2022.4.7-差分数组

复习一下前缀和数组

/**
 * @Author: Kopoo
 * @Date: 2022/4/7 10:24
 */
public class PrefixSum {
    int[] preSum;

    public PrefixSum(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 pre_Sum(int i, int j) {
        return preSum[j + 1] - preSum[i];
    }
}


类似前缀和技巧构造的 prefix 数组,我们先对nums数组构造⼀个 diff 差分数组,diff[i] 就是 nums[i] 和 nums[i-1] 之差

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

diff反推arr

设计成工具类

/**
 * @Author: Kopoo
 * @Date: 2022/4/7 11:03
 */
// 差分数组工具类
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;
    }
}

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

原理很简单,回想 diff 数组反推 nums 数组的过程,diff[i] += 3 意味着给 nums[i..] 所有的元素都加了 3,然后 diff[j+1] -= 3 又意味着对于 nums[j+1..] 所有元素再减 3,那综合起来,是不是就是对 nums[i..j] 中的所有元素都加 3 了

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

370.区间加法

题目描述

假设你有一个长度为 n 的数组,初始情况下所有的数字均为 0,你将会被给出 k 个更新的操作。

其中,每个操作会被表示为一个三元组:[startIndex, endIndex, inc],你需要将子数组 A[startIndex ... endIndex](包括 startIndex 和 endIndex)增加 inc

请你返回 k 次操作后的数组。

输入: length = 5, updates = [[1,3,2],[2,4,3],[0,2,-2]
输出: [-2,0,3,5,3]
初始状态:
[0,0,0,0,0]

进行了操作 [1,3,2] 后的状态:
[0,2,2,2,0]

进行了操作 [2,4,3] 后的状态:
[0,2,5,5,3]

进行了操作 [0,2,-2] 后的状态:
[-2,0,3,5,3]
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int length = sc.nextInt();
        int[] arr = new int[length];
        while (sc.hasNext()) {
            int startIndex = sc.nextInt();
            int endIndex = sc.nextInt();
            int inc = sc.nextInt();
            help(startIndex, endIndex, inc, arr);
            System.out.println(Arrays.toString(arr));
        }

        System.out.println(Arrays.stream(arr).toArray());

    }

    private static void help(int startIndex, int endIndex, int inc, int[] arr) {
        //diff[i]代表arr[i]-arr[i-1]
        int[] diff = new int[arr.length];
        diff[0] = arr[0];
        for (int i = 1; i < arr.length; i++) {
            diff[i] = arr[i] - arr[i - 1];
        }
        diff[startIndex] += inc;
        if (endIndex + 1 < arr.length) {
            diff[endIndex + 1] -= inc;
        }
        recover_arr(diff, arr);
    }

    private static void recover_arr(int[] diff, int[] res) {
        res[0] = diff[0];
        for (int i = 1; i < diff.length; i++) {
            res[i] = res[i - 1] + diff[i];
        }
    }

}

image-20220407120925713

没啥问题,经典的差分数组

1109. 航班预订统计

class Solution {
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] res=new int[n+1];
        int[] diff=new int[n+1];
        int[] result=new int[n];
        diff[1]=res[1];
        //diff[i]=res[i]-res[i-1]
        for(int i=0;i<bookings.length;i++){
            help(res,bookings[i][0],bookings[i][1],bookings[i][2],diff);
        }
        result_arr(diff,res);
        for(int i=0;i<n;i++){
            result[i]=res[i+1];
        }
        return result;
    }
    public void help(int[]res,int start,int end,int index,int[] diff){
        diff[start]+=index;
        if(end+1<res.length){
            diff[end+1]-=index;
        }
    }
    public void result_arr(int[] diff,int[] res){
        res[1]=diff[1];
        for(int i=2;i<diff.length;i++){
            res[i]=res[i-1]+diff[i];
        }
    }
}

容易,要记住差分数组的实现代码

1094. 拼车

1 2 3 4 5 6 7

2 2 2 2 2

​ 3 3 3

class Solution {
    public boolean carPooling(int[][] trips, int capacity) {
        int[] res=new int[1001];
        int[] diff=new int[1001];
        for(int[] trip:trips){
            int numPassenger=trip[0];
            int from=trip[1];
            int to=trip[2]-1;
            help(numPassenger,from,to,diff);
        }
        result(res,diff);
        for(int num:res){
            if(num>capacity){
                return false;
            }
        }
        return true;
    }

    public void help(int numPassenger,int from,int to,int[] diff){
        diff[from]+=numPassenger;
        if(to+1<diff.length){
            diff[to+1]-=numPassenger;
        }
    }
    public void result(int[] res,int[] diff){
        res[0]=diff[0];
        for(int i=1;i<res.length;i++){
            res[i]=diff[i]+res[i-1];
        }
    }
}

写错了一点,题目有点没说清楚,就是 比如下面这个测试用例

[[2,1,5],[3,5,7]]
3

应该是true,也就是一群人下和一群人上车可以同步进行的

所以范围要改为[trip[1], trip[2] - 1]

总结

差分数组不难,需要掌握好的是范围取值,

i到j

是diff[i]+index,然后在diff[j+1]-index

2022.4.8-双指针技巧秒杀七道链表题目

21. 合并两个有序链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy=new ListNode(-1);
        ListNode p1=list1;
        ListNode p2=list2;
        ListNode p=dummy;
        while(p1!=null&&p2!=null){
            if(p1.val<p2.val){
                p.next=p1;
                p1=p1.next;
            }
            else{
                p.next=p2;
                p2=p2.next;
            }
            p=p.next;
        }
        if(p1!=null){
            p.next=p1;
        }
        if(p2!=null){
            p.next=p2;
        }
        return dummy.next;
    }
}

dummy虚拟头节点还是很有用的,不然代码会复杂很多

23. 合并K个升序链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length==0)return null;
        ListNode dummy=new ListNode(-1);
        ListNode p=dummy;
        PriorityQueue<ListNode>pq=new PriorityQueue<>((a,b)->(a.val-b.val));
        for(ListNode list: lists){
            if(list!=null){
                pq.add(list);
            }
        }
        while(!pq.isEmpty()){
            ListNode node=pq.poll();
            p.next=node;
            if(node.next!=null){
                pq.add(node.next);
            }
            p=p.next;
        }
        return dummy.next;
    }
}

PriorityQueue<ListNode>pq=new PriorityQueue<>((a,b)->(a.val-b.val));

上面这个表达式很核心,要学会用lambda表达式来进行使用=

剑指 Offer 22. 链表中倒数第k个节点

快慢指针就可以

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast=head, slow=head;
        for(int i=0;i<k;i++){
            fast=fast.next;
        }
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
}

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

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy=new ListNode(-1);
        dummy.next=head;
        ListNode fast=dummy;
        ListNode slow=dummy;
        for(int i=0;i<n;i++){
            fast=fast.next;
        }
        while(fast.next!=null){
            slow=slow.next;
            fast=fast.next;
        }
        slow.next=slow.next.next;
        return dummy.next;
    }
}

需要注意的是出现只有一个数的链表情况,只要将快慢指针标在虚拟节点上就可以解决

这个虚拟节点还挺好用的

876. 链表的中间结点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }
}

141. 环形链表

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(fast==slow)return true;
        }
        return false;       
    }
}

看看就好,都是比较经典的方法类似

4月16日-142. 环形链表 II

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow)break;
        }
        if(fast==null||fast.next==null)return null;
        slow=head;
        while(fast!=slow){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
}

把图记下来

image-20220416200018250

如果从相遇点继续前进 k - m 步,也恰好到达环起点。因为结合上图的 fast 指针,从相遇点开始⾛k步可以转回到相遇点,那⾛ k - m 步肯定就⾛到环起点了

4月17日-160. 相交链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
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;
    }
}

这个刷不出来就很奇怪,真的要好好努力了,加油啊!你一定可以的

2022.5.1-双指针技巧秒杀七道数组题目

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

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

秒了,思路很清晰,双指针思想,实际就是快慢指针

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

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;
        if(head==null)return null;
        while(fast!=null){ 
            if(fast.val!=slow.val){
                slow.next=fast;
                slow=slow.next;
            }
            fast=fast.next;
        }
        slow.next=null;
        return head;

    }
}

同上题一样双指针,用在链表上

5月2日-27. 移除元素

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

秒了,还是双指针

5月3日-283. 移动零

class Solution {
    public void moveZeroes(int[] nums) {
        if(nums==null||nums.length==0)return;
        int fast=0,slow=0;
        while(fast<nums.length){
            if(nums[fast]==0){
                fast++;
            }
            else{
                nums[slow++]=nums[fast++];
            }
        }
        while(slow<nums.length){
            nums[slow++]=0;
        }
    }

秒了,还是快慢指针

5月4日-167. 两数之和 II

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left=0,right=numbers.length-1;
        while(left<right){
            int sum=numbers[left]+numbers[right];
            if(sum==target){
                return new int[]{left+1,right+1};
            }
            else if(sum<target){
                left++;
            }
            else if(sum>target){
                right--;
            }
        }
        return new int[]{-1,-1};
    }
}

思路要清晰,实际就是双指针类似于二分查找

5月5日-344. 反转字符串

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

刷简单题目还是很容易的了,就是双指针来反转数组

5. 最长回文子串

class Solution {
    public String longestPalindrome(String s) {
        String temp="";
        for(int i=0;i<s.length();i++){
            String s1=help(s,i,i);
            String s2=help(s,i,i+1);
            temp=temp.length()>s1.length()?temp:s1;
            temp=temp.length()>s2.length()?temp:s2;
        }
        return temp;
    }
    public String help(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);
    }
}

回文串的判断注意是中心扩散法,让左右指针从中心向两端扩展。

2022.6.5-我写了首诗,把滑动窗口算法算法变成了默写题

76. 最小覆盖子串

class Solution {
    public String minWindow(String s, String t) {
        HashMap<Character,Integer> need=new HashMap<>();
        HashMap<Character,Integer> window=new HashMap<>();
        for(Character c:t.toCharArray()){
            need.put(c,need.getOrDefault(c,0)+1);
        }
        int left=0;
        int right=0;
        int valid=0;
        int start=0;
        int len=Integer.MAX_VALUE;
        while(right<s.length()){
            char c=s.charAt(right);
            right++;
            if(need.containsKey(c)){
                window.put(c,window.getOrDefault(c,0)+1);
                if(window.get(c).equals(need.get(c))){
                    valid++;
                }
            }
            while(valid==need.size()){
                if(right-left<len){
                    start=left;
                    len=right-left;
                }
                char d=s.charAt(left);
                left++;
                if(need.containsKey(d)){
                    if(window.get(d).equals(need.get(d))){
                        valid--;
                    }
                    window.put(d,window.getOrDefault(d,0)-1);
                }
            }
        }
        return len==Integer.MAX_VALUE?"":s.substring(start,start+len);
    }
}

背诵,思路要清晰,这个题目逻辑性很强非常典型的滑动窗口题

6月16号-567. 字符串的排列

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

和上面一题的思路一样,不同点在于何时进行窗口左移,缩小的时机应该为窗口大于或者等于要求字符串大小

第二个不同点是直接返回true,如果长度相同并且valid也相同

posted @ 2022-05-01 22:08  kopoo  阅读(77)  评论(0编辑  收藏  举报