LC刷题笔记1-GT

数组双指针——二分查找:

二分不仅在数组这种有明显边界的情况下使用,在无明显边界的如,无穷小和无穷大的一个极端情,也可以使用。
这种情况下,nums[mid]就由自己确定的f(mid)来定,且要考虑同为target时,要满足题目条件,还需要取满足target的更大/小的mid。

        int start = 0,end = nums.length - 1;
        int mid;
        while(start <= end){
            mid = (start + end) / 2;
            if(nums[mid] > target){
                end = mid - 1;
            }else if(nums[mid] < target){
                start = mid + 1;
            }else{
                return mid;
            }
        }

875. 爱吃⾹蕉的珂珂
⼆分搜索的套路⽐较固定,如果遇到⼀个算法问题,能够确定 x, f(x), target分别是什么,并写出单调函数f 的代码。 这题珂珂吃⾹蕉的速度就是⾃变量 x,吃完所有⾹蕉所需的时间就是单调函数 f(x),target 就是吃⾹蕉的时间限制 H。
上面的就是自变量是mid,f(x)即为nums[mid],target即为限制,即目标值。
上下算法不同主要是因为下面这个吃香蕉问题,即使在H正好吃完也可能有多个mid速度,那么根据题目意思,需要取最小的,慢慢吃。

        int left = 1;
        int right = 1000000000 + 1;
        int mid;
        while(left < right){
            mid = (left + right) / 2;
            if(getTime(piles,mid) <= h){
                right = mid; //如果能在这个时间吃完,那么要保证最终能吃得完。
                //满足条件的情况下尽可能取小的值,所以是right = mid
            }else{
                left = mid + 1;
            }
        }
        return left;
    }

    // 返回以速度v吃完香蕉需要的时间
    public int getTime(int[] piles,int v){
        int hours = 0;
        for(int i = 0;i < piles.length;i++){
            hours += piles[i] / v;
            if(piles[i] % v > 0){
                hours++;
            }
        }
        return hours;

876. 在 D 天内送达包裹的能⼒
无边界的二分查找,要看好边界条件,同时考虑要满足题目条件,还需要取满足target的更大/小的mid。

        //二分查找,自变量mid就是weights,target是days.但要求最低运载能力,即同days时,要选择更小的weights,nums[mid]这里就是f(x)
        int left = 1,right = 25000001, mid;
        while(left < right){
            mid = (left + right) / 2;
            if(getdays(weights,mid) <= days){
                right = mid;
            }else{
                left = mid + 1;// > days说明完不成,自然不能保留,<=days说明完的成,保留,right= mid
            }
        }
        return left; //或者right反正最后left = right,因为while中设置的left < right.
    }

    public int getdays(int[] weights,int w){
        int days = 0, sum = 0,i = 0;
        while(i < weights.length){
            if(weights[i] > w) return 50001;
            sum += weights[i];
            if(sum > w){
                days++;
                sum = weights[i];
            }
            i++;
        }
        
        return days + 1;

数组双指针——滑动窗口:

什么是滑动窗口?

其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
何移动?
我们只要把队列的左边的元素移出就行了,直到满足题目要求!
一直维持这样的队列,找出队列出现最长的长度时候,求出解!
即遍历增大窗口,特定条件缩小窗口。

int left = 0, right = 0;

while (right < s.size()) {
    // 增大窗口
    window.add(s[right]);
    right++;
    
    while (window needs shrink) {
        // 缩小窗口
        window.remove(s[left]);
        left++;
    }
}

3. 无重复字符的最长子串
本题,利用 Map<Character,Integer> hashmap 存储字符和遍历至此时,该字符最靠右的索引
hashmap.containsKey(s.charAt(i))?
left = Math.max(left,hashmap.get(s.charAt(i)) + 1); // 不直接left=hashmap.get(s.charAt(i)) + 1是因为left左边的字符和索引也在hashmap里,那些索引在left左边.此行代码实现窗口移动。
这里的hashmap不是队列,所以会存在包含的元素索引在left左边
这里的left和i共同构成窗口,left实现移动,移动与否取决于hashmap.containsKey(s.charAt(i))?
left = Math.max(left,hashmap.get(s.charAt(i)) + 1)

        if(s.length() <= 1) return s.length();
        Map<Character,Integer> hashmap = new HashMap<>(); //用于存储字符和遍历至此时,该字符最靠右的索引
        int maxlen = 0;
        int left = 0;
        for(int i = 0;i < s.length();i++){
            if(hashmap.containsKey(s.charAt(i))){
                left = Math.max(left,hashmap.get(s.charAt(i)) + 1); // 不直接left=hashmap.get(s.charAt(i)) + 1是因为left左边的字符和索引也在hashmap里,那些索引在left左边.此行代码实现窗口移动。
            }
            hashmap.put(s.charAt(i),i); //存储字符和索引,包含时更新索引。
            maxlen = Math.max(maxlen,i - left + 1);
        }
        return maxlen;

438. 找到字符串中所有字母异位词
自编:思路就是找到p存在的字符,就进入窗口从0开始增大,不满足异位时缩小窗口至0;
可行,但是超过时间限制。

        List<Integer> list = new ArrayList();
        List<Character> listp = new ArrayList();
        int plen = p.length();
        for(int i = 0;i < plen;i++){
            listp.add(p.charAt(i));
        }
       
        //滑动窗口,遍历增大窗口,条件下缩小窗口.
        int left = 0;
        int right =0;
        while(right <= s.length() - plen){
            if(listp.contains(s.charAt(right))){
                List<Character> templist = new ArrayList(listp);
                left = right;//窗口大小从0开始
                //从0开始增大窗口
                while(left < right + plen){
                    if(!templist.remove(Character.valueOf(s.charAt(left)))){
                        break;
                    }
                    left++;
                }
                if(templist.isEmpty()){
                    list.add(left - plen);
                }
            }
            right++;
        }
        return list;

官方思路:
固定窗口大小进行滑动,比较滑动窗口中各种字符个数与p中是否一致;
字符个数统计,利用数组[26]。表示26个字母各自个数
s.charAT(i) - ‘a’
(a-z字符转数字整形)即可得到个数

        // 固定窗口大小进行滑动,比较滑动窗口中各种字符个数与p中是否一致;
        // 字符个数统计,利用数组[26]。表示26个字母各自个数
        // s.charAT(i) - 'a'(a-z字符转数字整形)即可得到个数
        int slen = s.length(),plen = p.length();
        if(slen < plen) return new ArrayList();

        List<Integer> list = new ArrayList();
        //初始窗口初始化:
        int[] pcount = new int[26],scount = new int[26];
        for(int i = 0;i < plen;i++){
            pcount[p.charAt(i) - 'a'] += 1;
            scount[s.charAt(i) - 'a'] += 1;
        }
        if(Arrays.equals(pcount,scount)){
            list.add(0);
        }

        for(int i = 0;i < slen - plen;i++){
            scount[s.charAt(i) - 'a'] -= 1;
            scount[s.charAt(i + plen) - 'a'] += 1;
            if(Arrays.equals(pcount,scount)){
            list.add(i + 1);
        }
        }
        return list;

567.字符串排列
思路同上438

链表双指针:

2.俩数相加:双指针分别指向l1,l2

        // 模拟:代码优化,主要是在一个while内完成所有进位模拟计算,有一个node为null时,加值设为0即可。
        // 相应的要在循环内部对可能出现的l1,l2为null的情况进行处理
        int count = 0,num1,num2;//表示进位
        ListNode temp1 = new ListNode(0);
        ListNode result = temp1;
        while(l1 != null || l2 != null){
            num1 = (l1 == null ? 0 : l1.val);
            num2 = (l2 == null ? 0 : l2.val);
            temp1.next = new ListNode((num1 + num2 + count) % 10);
            count = (num1 + num2 + count) / 10;
            temp1 = temp1.next;
            l1 = (l1 == null ? l1 : l1.next);
            l2 = (l2 == null ? l2 : l2.next);
        }

        if(count == 1){
            temp1.next = new ListNode(1);
        }

        return result.next;
  1. 删除链表的倒数第 N 个结点
        //倒数第n个节点,就是正数第len-n+1个。可以正向遍历至该处进行删除,
        //也可以双指针相隔n个节点距离,后一个指针为null时,前一个指针删除即可。
        int len = 0;
        ListNode temp = new ListNode();
        temp = head;
        while(temp != null){
            len++;
            temp = temp.next;
        }
        if(len == 1 || len == n) return head.next;
        temp = head;
        for(int i = 1;i < len - n;i++){
            temp = temp.next;
        }
        temp.next = temp.next.next;
        return head;
        //相隔n个节点的双指针.为了方便可在head前加一个0节点,用于left指针起步
        ListNode start = new ListNode(0,head);
        ListNode left = start;
        ListNode right = head;
        for(int i = 0;i < n;i++){
            right = right.next;
        }
        while(right != null){
            left = left.next;
            right = right.next;
        }

        left.next = left.next.next;

        return start.next;

21.合并俩个有序链表
双指针即可/亦可利用递归,递归函数里只对俩个链表的首节点对比,较小者置于前面,将剩余链表送入递归之中

        // //双指针+遍历循环
        // if(list1 == null) return list2;
        // if(list2 == null) return list1;

        // ListNode ResultNode = new ListNode(0);
        // ListNode p = ResultNode;

        // while(list1 != null && list2 != null){
        //     if(list1.val <= list2.val){
        //         p.next = list1;
        //         list1 = list1.next;
        //     }else{
        //         p.next = list2;
        //         list2 = list2.next;
        //     }
        //     p = p.next;
        // }

        // if(list1 == null)  p.next = list2;
        // if(list2 == null)  p.next = list1;

        // return ResultNode.next;

        //递归
        if(l1 == null) return l2;
        if(l2 == null) return l1;

        if(l1.val < l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }

141.环形链表
哈希表:有环则会在哈希表内重复节点
快慢双指针,ListNode fastNode = head.next.next, slowNode = head.next;有环则必定相遇

        // //1.哈希表法
        // //空间复杂度不满足进阶
        // if(head == null || head.next == null) return false;

        // Set<ListNode> nodeSet = new HashSet();
        // ListNode p = head;
        // while(p != null) {
        //     if(!nodeSet.add(p)){
        //         return true;
        //     }
        //     p = p.next;
        // }
        // return false;


        //2.快慢指针,
        //若有环则必定相遇
        if(head == null || head.next == null) return false;
        ListNode fastNode = head.next.next, slowNode = head.next;

        while(fastNode != slowNode) {
            if(fastNode == null) return false;
            else if(fastNode.next == null) return false;
            fastNode = fastNode.next.next;
            slowNode = slowNode.next;
        }
        return true; 

142.环形链表Ⅱ
同上。相遇后,另一个快指针从head重新出发,且速度均为一,再次相遇即为环的入口。

        if(head == null || head.next == null) return null;
        ListNode fast = head.next.next, slow = head.next;//既然指针没移动时,会导致head相等,那么在进入循环前先使得来指针先各移动一次

        while(fast != slow) {
            if(fast == null) {return null;}
            else if(fast.next == null){return null;}
            //不能写成fastNode.next == null || fastNode.next.next == null。这样可能fast.next已经为null就不存在fastNode.next.next
            fast = fast.next.next;
            slow = slow.next;
        }

        fast = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;

160.相交链表:
1.哈希表,将节点存入哈希表中,另一个链表的节点遍历在哈希表中查找即可。
2.只要在相同速度为1的双指针各自走到null时,令其从另一个链表的head处开始继续.next即可。
俩个都经过null,再从另一个链表开始遍历至交汇处时,共同走了俩链表交点前的长度和+交点后的长度,则有交点必会相遇。
3.双指针,长的链表先遍历俩者长度差值的部分,以便俩指针从距离交点相同的距离开始共同遍历

        // //2.双指针遍历,至Null后跳转另一个链表继续遍历。若俩者长度相同,则第一次相遇即为交点,若长度不同,且不相交,会在遍历彼此链表时到null处相同,即返回null
        // ListNode pa = headA, pb = headB;
        // while(pa != pb){
        //     pa = pa == null ? headB:pa.next;
        //     pb = pb == null ? headA:pb.next;
        // }
        // return pa;
        //3.双指针,长的链表先遍历俩者长度差值的部分,以便俩指针从距离交点相同的距离开始共同遍历
        ListNode pa = headA, pb = headB;
        //获取长度
        int la = 0, lb = 0;
        while(pa != null){
            pa = pa.next;
            la++;
        }
        while(pb != null){
            pb = pb.next;
            lb++;
        }
        //长的链表遍历至距离交点相同距离的地方
        pa = headA;
        pb = headB;
        int flag = la - lb;
        if(flag >= 0){
            for(int i = 0;i < flag;i++){
                pa = pa.next;
            }
        }else{
            for(int i = 0;i < -flag;i++){
                pb = pb.next;
            }
        }
        //俩指针共同遍历
        while(pa != pb){
            pa = pa.next;
            pb = pb.next;
        }
        return pa; //null说明无交点,俩者都遍历结束

876.链表的中间节点
1.获取长度,按19.倒数第n节点思路来做
2.快慢指针,中间节点则选择速度为1、2的快慢双指针即可。
fast != null && fast.next != null

        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
  1. 删除排序链表中的重复元素
    单指针遍历即可
        //1.单指针+遍历
        if (head == null) return null;
        ListNode p = head;
        while(p.next != null){
            if(p.val == p.next.val){
                p.next = p.next.next;
            }else{
                p = p.next;
            }
        }
        return head;
  1. 反转链表
    ***ListNode pre = null, curr = head, temp = head;***双指针+临时指针进行反转操作
        if(head == null || head.next == null) return head;
        
        ListNode pre = null, curr = head, temp = head;
        while(curr != null){
            temp = temp.next;
            curr.next = pre;
            pre = curr;
            curr = temp;
        }
        return pre;

92.反转链表Ⅱ
记录left前的一个节点pre,从left开始反转后面节点至right,记录原顺序时right.next的节点after
至此反转完成,pre.next = right;left.next = after
重点:ListNode start = new ListNode(0,head);,加个零起始节点便于处理包括链表只有一个节点的情况。(因为算法中有前中后三个节点,当链表只有一个节点时,算法的三个节点会有一个不存在)
为避免最后一次反转,midafter不存在,最后一次反转安排在循环外
一次遍历完成如下:

        //记录left前的一个节点pre,从left开始反转后面节点至right,记录原顺序时right.next的节点after。
        //至此反转完成,pre.next = right;left.next = after
        // if(head.next == null) return head;
        ListNode start = new ListNode(0,head);
        ListNode pre = start;
        //先找pre
        for(int i = 0;i < left - 1;i++){
            pre = pre.next;
        }
        ListNode leftnode = pre.next;
        ListNode midpre = null,mid = leftnode,midafter = leftnode.next;
        for(int i = 0;i < right - left;i++){
            mid.next = midpre; //反转当前节点

            midpre = mid;
            mid = midafter;
            midafter = mid.next;
        }
        mid.next = midpre; // 考虑最后一次反转,mid成为Null,midafter会不存在,所以最后一次反转放在循环外
        ListNode rightnode = mid,after = midafter;
        pre.next = rightnode;
        leftnode.next = after;

        return start.next;

方法2.也可以先遍历一遍链表,反转left和right生成新链表,
再次遍历链表至left前和right后,完成拼接
25. K 个⼀组翻转链表
根据上题思路,本题只不过是需要先遍历链表确定链表长度,确定要多少次完成k组反转,然后遍历链表,对k内进行上题的left到left+k的反转

在这里插入代码片
  1. 回⽂链表
    法1.将值复制到数组中,进行双指针正序逆序的移动对比
    法2.先确定中点(快慢双指针,以head为起点即可不考虑奇偶情况),反转中点以后的链表,然后双指针遍历**前续链表,和反转的后续链表,**直至前续链表指针为null
        //链表中节点数目在范围[1, 105] 内
        //找中点
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }

        //从slow开始反转后续链表
        slow = reverseNode(slow);
        ListNode slowsave = slow;

        //反转后续链表slowpre
        fast = head;
        while(slow != null){
            if(fast.val != slow.val){
                slowsave = reverseNode(slowsave); // 链表后半段反转回去
                return false;
            }
            fast = fast.next;
            slow = slow.next;
        }
        slowsave = reverseNode(slowsave);
         return true;

    }

    public ListNode reverseNode(ListNode node){
        ListNode slowpre = null, slownext = node;
        while(node != null){
            slownext = slownext.next;
            node.next = slowpre;
            slowpre = node;
            node = slownext;
        }
        return slowpre;

前缀和:

标准的前缀和问题,核⼼思路是⽤⼀个新的数组 preSum 记录 nums[0…i-1] 的累加和
即.构造前缀数组presums

  1. 区域和检索 - 数组不可变
    this.presums[i] = this.presums[i - 1] + nums[i];
    本题注意点:left为0时,该边界条件下无prenums[0-1],且此时结果就为presums[right]
class NumArray {
    public int[] presums;
    
    public NumArray(int[] nums) {
        this.presums = nums;
        for(int i = 1;i < nums.length;i++){
            this.presums[i] = this.presums[i - 1] + nums[i];
        }
    }
    
    public int sumRange(int left, int right) {
    //这里要注意,当left为0时,不存在prenums[0-1],
        if(left == 0){
            return presums[right];
        }else{
            return presums[right] - presums[left - 1];
        }
    }
}

304.⼆维区域和检索 - 矩阵不可变
本题重点:为避免分类,即有col1 - 1/row1 - 1 = -1的情况,可以给二维矩阵扩充0行0列,这样就可以全部为第四种情况,即可不分类
学习顿悟:遇到需要分类讨论的如链表,数组,二维数组的索引index - 1时,若index = 0,则index - 1 就会超出索引范围,此时即可扩充零节点,零元素,零行零列

class NumMatrix {
    public int[][] matrixsums;
    public NumMatrix(int[][] matrix) {
        //为避免分类,即有col1 - 1/row1 - 1 = -1的情况,可以给二维矩阵扩充0行0列,这样就可以全部为第四种情况,即可不分类
        int row = matrix.length,col = matrix[0].length;
        matrixsums = new int[row + 1][col + 1];
        for(int i = 0;i < row + 1;i++){
            matrixsums[i][0] = 0;
        }
        for(int i = 0;i < col + 1;i++){
            matrixsums[0][i] = 0;
        }

        for(int i = 1;i < row + 1;i++){
            for(int j = 1;j < col + 1;j++){
                matrixsums[i][j] = matrixsums[i - 1][j] + matrixsums[i][j - 1] - matrixsums[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        //分类处理,为避免分类,即有col1 - 1/row1 - 1 = -1的情况,可以给二维矩阵扩充0行0列,这样就可以全部为第四种情况,即可不分类
        return matrixsums[row2 + 1][col2 + 1] - matrixsums[row1][col2 + 1] - matrixsums[row2 + 1][col1] + matrixsums[row1][col1];

        // if(row1 == 0 && col1 == 0){
        //     return matrixsums[row2][col2];
        // }else if(row1 == 0){
        //     return matrixsums[row2][col2] - matrixsums[row2][col1 - 1];
        // }else if(col1 == 0){
        //     return matrixsums[row2][col2] - matrixsums[row1 - 1][col2];
        // }else{
        //     return matrixsums[row2][col2] - matrixsums[row1 - 1][col2] - matrixsums[row2][col1 - 1] + matrixsums[row1 - 1][col1 - 1];
        // }

    }
}
  1. 和为 K 的子数组
    前缀和+哈希表优化(利用前缀和将本题转为:数组中找俩数和为目标值target,即可用哈希表来优化求解
    每遍历一个数字,即查看表中有无对应匹配的另一个数字,并获取个数,同时本数字也要存入表中,次数也要叠加
        //1.暴力枚举
        //2.前缀和+哈希表优化(类比题目数组找俩数和为target
        int result = 0;
        //扩充0元素以避免边界条件的分类讨论
        int[] sums = new int[nums.length + 1];
        sums[0] = 0;
        for(int i = 0;i < nums.length;i++){
            sums[i + 1] = sums[i] + nums[i];
        }
        Map<Integer,Integer> hashmap = new HashMap();
        for(int i = 0;i < sums.length;i++){
            
            if(hashmap.containsKey(sums[i] - k)){
                result += hashmap.get(sums[i] - k);
            }
            if(hashmap.containsKey(sums[i])){
                hashmap.put(sums[i],hashmap.get(sums[i]) + 1);
            }else{
                hashmap.put(sums[i],1);
            }
        }
        return result;

差分数组

差分数组技巧适⽤于频繁对数组区间进⾏增减的场景。
进⾏区间增减,如果你想对区间 nums[i…j] 的元素全部加 3,那么只需要让 diff[i] += 3,然后再 让 diff[j+1] -= 3 即可:
差分数组模板:
其中:diff[i] += k;因为diff是差分数组,所以该行代码对差分换原的原数组从i开始的后面所有元素增加了k;但是我们要在区间i-j之间进行操作,那么就需要*对j以后的元素将增加的操作减去,即diff[j+1] -= 3。*但是这里有个边界条件,即区间右侧是数组最右端时,就不存在j+1的索引,因此可用if语句if(j != n){diff[j+1] -= 3};或者,利用前面提到的去除边界条件的讨论,可以增加零元素,只不过这次是增加在差分数组后面(因为边界问题的产生是在差分数组的最右端)
注意:差分数组的构造与换原,首元素都是不变的。

        //1.构造差分数组
        int[] diff = new int[n];
        diff[0] = nums[0];
        for(int i = 1;i < n;i++){
            diff[i] = nums[i] - nums[i - 1];
        }
        //区间增减
         diff[i] += k;
         diff[j+1] -= k;//这里注意在区间i-j之间进行增减,
        //3.换原差分数组
        int[] result = new int[n];
        result[0] = diff[0];
        for(int i = 1;i < n;i++){
            result[i] = result[i - 1] + diff[i];
        }

1109.航班预订统计
diff差分数组右侧边界问题用if语句解决:

        //差分数组技巧适⽤于频繁对数组区间进⾏增减的场景。
        int[] nums = new int[n];
        for(int i = 0;i < n;i++){
            nums[i] = 0;
        }
        //1.构造差分数组
        int[] diff = new int[n];
        diff[0] = nums[0];
        for(int i = 1;i < n;i++){
            diff[i] = nums[i] - nums[i - 1];
        }

        //2.进⾏区间增减,i-j区间是针对数组的i-1,j-1之间,且j = n时,即为数组最右边界时,无需diff[j - 1 + 1] -=3
        for(int i = 0;i < bookings.length;i++){
            diff[bookings[i][0] - 1] += bookings[i][2];
            if(bookings[i][1] != n){
                diff[bookings[i][1]] -= bookings[i][2];
            }
        }

        //3.换原差分数组
        int[] result = new int[n];
        result[0] = diff[0];
        for(int i = 1;i < n;i++){
            result[i] = result[i - 1] + diff[i];
        }
        return result;

针对diff差分数组扩充零元素以避免边界问题讨论:

        //差分数组技巧适⽤于频繁对数组区间进⾏增减的场景。
        int[] nums = new int[n];
        for(int i = 0;i < n;i++){
            nums[i] = 0;
        }
        //1.构造差分数组
        int[] diff = new int[n + 1];
        diff[0] = nums[0];
        diff[n] = 0;
        for(int i = 1;i < n;i++){
            diff[i] = nums[i] - nums[i - 1];
        }

        //2.进⾏区间增减,i-j区间是针对数组的i-1,j-1之间,且j = n时,即为数组最右边界时,无需diff[j - 1 + 1] -=3
        for(int i = 0;i < bookings.length;i++){
            diff[bookings[i][0] - 1] += bookings[i][2];
            diff[bookings[i][1]] -= bookings[i][2];
            
        }

        //3.换原差分数组
        int[] result = new int[n];
        result[0] = diff[0];
        for(int i = 1;i < n;i++){
            result[i] = result[i - 1] + diff[i];
        }
        return result;

370.区间加法

        // //暴力遍历
        // int[] result = new int[length];
        // for(int i = 0;i < length;i++){
        //     result[i] = 0;
        // }
        // for(int i = 0;i < updates.length;i++){
        //     for(int j = updates[i][0];j <= updates[i][1];j++){
        //         result[j] += updates[i][2];
        //     }
        // }
        // return result;

差分法

        int[] nums = new int[length];
        for(int i = 0;i < length;i++){
            nums[i] = 0;
        }
        //用扩充差分数组右侧零元素来解决边界冲突问题
        //构造差分数组
        int[] diff = new int[length + 1];
        diff[0] = nums[0];
        diff[length] = 0;
        for(int i = 1;i < length;i++){
            diff[i] = nums[i];
        }

        //区间增减
        for(int i = 0;i < updates.length;i++){
            diff[updates[i][0]] += updates[i][2];
            diff[updates[i][1] + 1] -= updates[i][2];
        }

        //差分数组换原
        nums[0] = diff[0];
        for(int i = 1;i < length;i++){
            nums[i] = nums[i - 1] + diff[i];
        }

        return nums;

1094.拼⻋
//本题将题目意思转换之后就是区间增减问题,但是细节注意是本题上下车站点i-j,则在区间上应该是区间[i,j - 1]内的增减
//所以编写程序需要注意,同时仍需注意diff有边界问题,扩充零元素]
默认初始化就是全零

        int[] nums = new int[1000];
        // nums[0] = 0;
        // for(int i = 0;i < 1000;i++){
        //     nums[i] = 0;
        // }
        // 默认初始化就是全零
        //本题将题目意思转换之后就是区间增减问题,但是细节注意是本题上下车站点i-j,则在区间上应该是区间[i,j - 1]内的增减
        //所以编写程序需要注意,同时仍需注意diff有边界问题,扩充零元素
        int[] diff = new int[1001];
        diff[0] = nums[0];
        // diff[1000] = 0;
        for(int i = 1;i < 1000;i++){
            diff[i]  = nums[i] - nums[i - 1];
        }

        for(int i = 0;i < trips.length;i++){
            diff[trips[i][1]] += trips[i][0];
            diff[trips[i][2]] -= trips[i][0]; //这里就是本题的区别
        }

        //换原
        nums[0] = diff[0];
        if(nums[0] > capacity){
            return false;
        }
        for(int i = 1;i < 1000;i++){
            nums[i] = diff[i] + nums[i - 1];
            if(nums[i] > capacity){
                return false;
            }
        }

        return true;

补充:以上代码中的全零数组创建不用初始化,因为默认初始化就是全零

队列/栈算法

对于括号匹配的题目,常用的做法是使用栈进行匹配,栈具有后进先出的特点,因此可以保证右括号和最近的左括号进行匹配。
20.有效的括号

        //1.栈的压入与推出
        // if((s.length() & 1) == 1) return false;//字符串长度为奇数则直接返回false
        // Stack<String> stack = new Stack();
        // char[] cstring = s.toCharArray();
        // String sc = "";
        // for(int i = 0;i < cstring.length;i++){
        //     sc = String.valueOf(cstring[i]);
        //     if("(".equals(sc) || "[".equals(sc) || "{".equals(sc)){
        //         stack.push(sc);
        //     }else if(stack.isEmpty()){
        //         return false;
        //     }else if(")".equals(sc)){
        //         if(!stack.pop().equals("(")) return false;
        //     }else if("]".equals(sc)){
        //         if(!stack.pop().equals("[")) return false;
        //     }else{
        //         if(!stack.pop().equals("{")) return false;
        //     }
        // }
        // if(!stack.isEmpty()) return false;
        // return true;

        //2.利用哈希表来快速获取对应括号
        Map<Character, Character> map = new HashMap<Character, Character>() {{
            put(')', '(');
            put(']', '[');
            put('}', '{');
        }};
        Stack<Character> stack = new Stack();
        Character c;
        for(int i = 0;i < s.length();i++){
            c = s.charAt(i);
            if(map.containsKey(c)){
                if(stack.isEmpty() || stack.pop() != map.get(c)) return false;
            }else{
                stack.push(c);
            }
        }
        if(!stack.isEmpty()) return false;
        return true;

921.使括号有效的最少添加
以左括号为基准,通过维护对右括号的需求数 need,来计算最⼩的插⼊次数。
遍历字符串,通过⼀个 need 变量记录对右括号的需求数,根据 need 的变化来判断是否需要插⼊。当 need == -1 时,意味着我们遇到⼀个多余的右括号,显然需要插⼊⼀个左括号。

        //通过⼀个 need 变量记录对右括号的需求数,根据 need 的变化来判断是否需要插⼊。当 need == -1 时,意味着我们遇到⼀个多余的右括号,显然需要插⼊⼀个左括号。
        int need = 0,result = 0;
        Character c;
        for(int i = 0;i < s.length();i++){
            c = s.charAt(i);
            if(c == '('){
                need++;
            }else{
                need--;
            }

            if(need == -1){
                need++;
                result++;
            }
        }

        return result + need;

上面方法更快速,
用栈也能解决,但性能不足够好。

        //是以左括号为基准,通过维护对右括号的需求数 need,来计算最⼩的插⼊次数。
        Stack<Character> stack = new Stack();
        for(int i = 0;i < s.length();i++){
            if('(' == (s.charAt(i))){
                stack.push('(');
            }else{
                if(!stack.isEmpty() && '(' == (stack.peek())){
                    stack.pop();
                }else{
                    stack.push(')');
                }
            }
        }
        int result = 0;
        while(!stack.isEmpty()){
            stack.pop();
            result++;
        }
        return result;

1541.平衡括号字符串的最少插入次数
遍历字符串,通过⼀个 need 变量记录对右括号的需求数,根据 need 的变化来判断是否需要插⼊。 类似 921. 使括号有效的最少添加,当 need == -1 时,意味着我们遇到⼀个多余的右括号,显然需要插⼊ ⼀个左括号。
对于本题,另外,当遇到左括号时,若对右括号的需求量为奇数,需要插⼊ 1 个右括号,因为⼀个左括号需要两个右括 号嘛,右括号的需求必须是偶数,这⼀点也是本题的难点。

        //
        int need = 0,result = 0;
        Character c;
        for(int i = 0;i < s.length();i++){
            c = s.charAt(i);
            if(c == '('){
                need += 2;
                if(need % 2 == 1){
                    //如果对右括号的需求为奇数,而我们需要左右1:2,所以需要添加一个左括号
                    result++;
                    need--;
                }
            }else{
                need--;
                if(need == -1){
                    //对右括号需求为-1,则添加一个左括号,那么对右括号需求就为1了
                    result++;
                    need = 1;
                }
            }
        }
        return result + need;

225.用队列实现栈
入栈操作时,首先将元素入队到 q2,然后将q1的全部元素依次出队并入队到q2,此时q2的前端的元素即为新入栈的元素,再将q1
和q2互换,则q1的元素即为栈内的元素,q1的前端和后端分别对应栈顶和栈底。
public void push(int x) {
q1.offer(x);
while(!q2.isEmpty()){
q1.offer(q2.poll());
}
LinkedList temp = new LinkedList();
temp = q1;
q1 = q2;
q2 = temp;

}

    public LinkedList<Integer> q1;//q1作为临时队列使用
    public LinkedList<Integer> q2;//q2作为实现栈

    public MyStack() {
        q1 = new LinkedList();
        q2 = new LinkedList();
    }
    
    public void push(int x) {
        q1.offer(x);
        while(!q2.isEmpty()){
            q1.offer(q2.poll());
        }
        LinkedList<Integer> temp = new LinkedList();
        temp = q1;
        q1 = q2;
        q2 = temp;
    }
    
    public int pop() {
        return q2.poll();
    }
    
    public int top() {
        return q2.peek();
    }
    
    public boolean empty() {
        return q2.isEmpty();
    }

232.用栈来实现队列
设置一个栈为stackin,另一个栈为stackout。用俩个栈的push,pop,peek,isEmpty来实现队列的四个功能
public int pop() {
if(outStack.isEmpty()){
inToout();
}
return outStack.pop();

}
public void inToout(){
while(!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}

class MyQueue {

    private static Stack<Integer> inStack;
    private static Stack<Integer> outStack;

    public MyQueue() {
        inStack  = new Stack<Integer> ();
        outStack  = new Stack<Integer> ();
    }
    
    public void push(int x) {
        inStack.push(x);
    }
    
    public int pop() {
        if(outStack.isEmpty()){
            inToout();
        }
        return outStack.pop();
    }
    
    public int peek() {
        if(outStack.isEmpty()){
            inToout();
        }
        return outStack.peek();
    }
    
    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

    public void inToout(){
        while(!inStack.isEmpty()){
            outStack.push(inStack.pop());
        }
    }

二叉堆

堆的实现是数组,从数组索引1处开始;
构造堆:从length/2(最后一个非叶节点)倒叙至1执行下沉操作。
add:加至堆尾,执行上浮操作
deletemax:删除1处max,将N处元素置于1处执行下沉操作;
堆排序:1处max与N处对调,执行1处下沉操作,循环对调和下沉直至N’为2对调即可。
用堆实现最大/小优先队列,

可用API:
PriorityQueue pq = new PriorityQueue(); // 该API是最小优先队列
pq.add(num)
pq.size()
pq.poll()
pq.peek()

215.数组中的第K个最大元素
使用工具类:

        PriorityQueue<Integer> pq = new PriorityQueue(); // 构造最小优先队列
        for(int num : nums){
            pq.add(num);
        }
        while(pq.size() > k){
            pq.poll();
        }
        return pq.peek();

手动构造堆和优先队列:

        //手动构造堆
        int len = nums.length;
        int[] arr = new int[len + 1];
        for(int i = 0;i < len;i++){
            arr[i + 1] = nums[i];
        }
        for(int i = len / 2;i > 0;i--){
            down(arr,i,arr.length - 1);
        }

        //最大优先队列删除顶端max,
        int maxk = 0;
        for(int i = 0;i < k;i++){
            maxk = arr[1];
            exchange(arr,1,len - i);//1与N'交换
            down(arr,1,len - i - 1);//下沉至N' - 1;
        }
        return maxk;
    }
    //下沉
    public void down(int[] arr,int k,int indexlen){
        // int indexlen = arr.length - 1;
        int max;
        while(2 * k <= indexlen){
            if(2 * k < indexlen){
                max = (arr[2 * k] > arr[2 * k + 1] ? 2 * k : 2 * k + 1);//获取俩者较大值的索引
                if(arr[k] < arr[max]){
                    exchange(arr,k,max);
                    k = max;
                }else{
                    return;
                }
            }else{
                if(arr[k] < arr[2 * k]){
                    exchange(arr,k,2 * k);
                    k = 2 * k;
                }else{
                    return;
                }
            }
        }
        return;
    }
    //元素交换
    public void exchange(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
        return;
    }

703.数据流中的第 K 大元素
使用工具类:

    public PriorityQueue<Integer> pq;
    public int k;
    public KthLargest(int k, int[] nums) {
        this.k = k;
        pq = new PriorityQueue(); // 该API是最小优先队列
        for(int num:nums){
            pq.add(num);
        }
    }
    
    public int add(int val) {
        pq.add(val);
        while(pq.size() > this.k){
            pq.poll();
        }
        return pq.peek();
    }

手动构造堆和优先队列:

        //手动构造堆
        this.len = nums.length;
        this.k = k;
        for(int i = 0;i < len;i++){
            arr[i + 1] = nums[i];
        }
        for(int i = len / 2;i > 0;i--){
            down(arr,i,len);
        }

    }
    
    public int add(int val) {
        arr[++len] = val;
        up();
        for(int i = 1;i <= len;i++){
            arrtemp[i] = arr[i];
        }
        //最大优先队列删除顶端max,
        int maxk = 0;
        for(int i = 0;i < k;i++){
            maxk = arrtemp[1];
            exchange(arrtemp,1,len - i);//1与N'交换
            down(arrtemp,1,len - i - 1);//下沉至N' - 1;
        }
        return maxk;
    }

    //上浮
    public void up(){
        int m = len;
        while(m > 1){
            if(arr[m] >= arr[m / 2]){
                exchange(arr,m,m / 2);
                m = m / 2;
            }else{
                return;
            }
        }
    }

    //下沉
    public void down(int[] arrd,int m,int indexlen){
        // int indexlen = arr.length - 1;
        int max;
        while(2 * m <= indexlen){
            if(2 * m < indexlen){
                max = (arrd[2 * m] >= arrd[2 * m + 1] ? 2 * m : 2 * m + 1);//获取俩者较大值的索引
                if(arrd[m] < arrd[max]){
                    exchange(arrd,m,max);
                    m = max;
                }else{
                    return;
                }
            }else{
                if(arrd[m] < arrd[2 * m]){
                    exchange(arrd,m,2 * m);
                    m = 2 * m;
                }else{
                    return;
                }
            }
        }
        return;
    }

    //元素交换
    public void exchange(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
        return;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值