leetcode 2/14-2/20做题笔记

拼接最大数(HARD)

在这里插入图片描述

  • 划分解空间:第一个数组取x个数,第二个数组取y个数,其中 x + y = k x+y = k x+y=k。显然取的数在满足顺序条件的情况下是最大数,利用单调栈求解
  • 取数后,转化为如何将两个序列拼成最大数。双指针(归并)即可。
  • 使用双指针贪心归并。考察两个序列的第一个数。若不等则取较大的,指针后移。若相等,考察后面第一个不等的数,较大的那个序列指针加1。
class Solution {
    //从num中删除k个数得到的最大数
    public int[] deal(int[] num,int k)
    {
        List<Integer> s = new ArrayList<>();
        int pop = 0;  //pop的个数
        for(int i= 0;i<num.length;i++)
        {
            if(s.isEmpty())
                s.add(num[i]);
            else
            {
                while(!s.isEmpty() && num[i] > s.get(s.size() - 1) && pop < k)
                {
                    s.remove(s.size() - 1);
                    pop++;
                }
                s.add(num[i]);
            }
        }
        int[] ret = new int[num.length - k];
        for(int i = 0;i<ret.length;i++)
            ret[i] = s.get(i);
        return ret;
    }

    public boolean greater(int[] a,int[] b)
    {
        for(int i = 0;i<a.length;i++)
        {
            if(a[i] > b[i])
                return true;
            if(a[i] < b[i])
                return false;
        }
        return false;
    }


    public int[] maxNumber(int[] nums1, int[] nums2, int k) {
        int[] now = new int[k];
        int[] max = new int[k];
        boolean first  =true;
        for(int x = Math.max(0,k - nums2.length);x<=Math.min(k ,nums1.length);x++)
        {
            int[] a = deal(nums1,nums1.length - x);
            int[] b = deal(nums2,nums2.length - (k - x));
            int i = 0,j = 0,count = 0;
            while(i<a.length && j < b.length)
            {
                if(a[i] > b[j]) {
                    now[count++] = a[i];
                    i++;
                }
                else if(a[i] < b[j])
                {
                    now[count++] = b[j];
                    j++;
                }
                else
                {
                    int q = 0;
                    while(i+q<a.length && j+q < b.length && a[i+q] == b[j+q])
                    {
                        q++;
                    }
                    if(i+q >= a.length && j+q>=b.length)
                    {
                        now[count++] = a[i];
                        i++;
                    }
                    else if(j+q>=b.length)
                    {
                        now[count++] = a[i];
                        i++;
                    }
                    else if(i + q >= a.length)
                    {
                        now[count++] = b[j];
                        j++;
                    }
                    else if(a[i+q] > b[j+q]) {
                        now[count++] = a[i];
                        i++;
                    }
                    else if(b[j + q] > a[i + q])
                    {
                        now[count++] = b[j];
                        j++;
                    }
                }
            }
            while(i < a.length)
            {
                now[count++] = a[i];
                i++;
            }
            while(j < b.length)
            {

                    now[count++] = b[j];
                    j++;
                

            }
            if(greater(now,max))
                System.arraycopy(now,0,max,0,now.length);
        }
        return max;
    }
    public static void main(String[] args)
    {
        new Solution().maxNumber(new int[]{7,6,1,9,3,2,3,1,1},new int[]{4,0,9,9,0,5,5,4,7},9);
    }
}

区间和的个数(HARD)

在这里插入图片描述

分治+归并排序

  • 回顾:逆序对问题的分治求法。数组一分为二 + 求一分为二之间逆序对数目
  • 递归:一分为二,假设左侧满足条件的个数已经求出,右侧的满足条件个数已经求出,且左右两侧均有序。
  • 考察前缀和数组。设左侧部分为数组 n 1 n_1 n1,右侧部分为数组 n 2 n_2 n2,均为升序。即求 n 2 [ j ] − n 1 [ i ] ∈ ( l o w e r , u p p e r ) n_2[j] - n_1[i] \in (lower,upper) n2[j]n1[i](lower,upper)的(i,j)个数。对 n 2 n_2 n2利用滑动窗口(或称为双指针),只会向右侧移动而不会回溯。对 n 1 n_1 n1中的每个值顺序求解。
  • 算法分析:至多遍历一次 n 1 , n 2 n_1,n_2 n1,n2,复杂度 O ( n ) O(n) O(n),再加上merge的复杂度 O ( n ) O(n) O(n)。所以复杂度等于merge复杂度 O ( n ) O(n) O(n)。最终复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
class Solution {
    int ans = 0;
    public long[] mergesort(int low,int high,long[] nums,int lower,int upper)
    {
        if(low == high)
        {
            if(nums[low] >= lower && nums[low]<=upper)
                ans++;
            return new long[]{nums[low]};
        }
        int mid = (low+high) / 2;
        long[] a = mergesort(low,mid,nums,lower,upper);
        long[] b = mergesort(mid + 1,high,nums,lower,upper);
        //merge
        long[] c = new long[high - low + 1];
        int i=0,l=0,r=0;
        while(i<a.length)
        {
            if(r < 0)
                r++;
            while(l<b.length && (long)b[l] - (long)a[i] < (long)lower)
                l++;
            while(r<b.length && (long)b[r] - (long)a[i] <= (long)upper)
                r++;
            r--;
            if(l<=r)
                ans += (r - l + 1);
            i++;
        }
        l =  r = i = 0;
        while(l < a.length && r < b.length)
        {
            if(a[l] <= b[r])
            {
                c[i++] = a[l];
                l++;
            }
            else
            {
                c[i++] = b[r];
                r++;
            }
        }
        while(l < a.length)
        {
            c[i++] = a[l];
            l++;
        }
        while(r < b.length)
        {
            c[i++] = b[r];
            r++;
        }
        return c;
    }


    public int countRangeSum(int[] nums, int lower, int upper) {
        long[] pre = new long[nums.length];
        pre[0] = nums[0];
        for(int i  =1;i<nums.length;i++)
            pre[i] = pre[i - 1] + nums[i];
        mergesort(0,pre.length - 1,pre,lower,upper);
        return ans;
    }

    public static void main(String[] args)
    {
        new Solution().countRangeSum(new int[]{2147483647,-2147483648,-1,0},-1,0);
    }
}

总结

  • 在归并排序和快速排序过程中,在递归的阶段除了做该做的事情外,还可以做其他事情而使得复杂度不提升。例如在本题中就做了统计(i,j)对,在逆序对中统计了逆序对。

按要求补齐数组(HARD)

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

  • 没有想到的点:转化为[1,n]中空缺区间的填补

最优子结构(显然)

贪心选择

  • 为了包括1,如果num中不包括1一定要补充1
  • MOTIVATION:每次选择使区间扩充尽可能大,又填补了空缺。
  • 然后,一定存在某个最优解,第一步填写原始num中所有可能和在[1,n]空缺的最小值(设为x)。
  • 若不然,任取某个最优解,一定会填写 < x <x <x的某个值进以补全x。将此操作提到第一步再重复原始最优解的所有操作构造了另一个最优解。

算法分析

  • 即使num只有1,按上述算法,每次区间规模扩大2倍。因此至多添加logn步即可最优解。

Implementation(巧妙)

  • 设遍历到 n u m [ k ] num[k] num[k]时,未将num[k]纳入有区间[1,r],将num[k]纳入后区间变为 [ 1 , r ] ∪ [ n u m [ k ] , n u m [ k ] + r ] [1,r] \cup [num[k],num[k] + r] [1,r][num[k],num[k]+r]
    • 由于num是递增的,对原始num的所有和构成的区间,区间之间的空白部分第一次生成(r与num[k]间的空白)就不可能由后续的数字填充。 只能当即填充。
  • 维护r的定义:当前从1开始所能连续到达的最远数字。
    • [ 1 , r ] ∪ [ n u m [ k ] , n u m [ k ] + r ] [1,r] \cup [num[k],num[k] + r] [1,r][num[k],num[k]+r]中间有空隙,则按上述贪心思路填充,直到将空隙消除。消除后,对新的 r , r^, r,,更新 r ← r , + n u m s [ k ] r \leftarrow r^, + nums[k] rr,+nums[k]
    public int minPatches(int[] nums, int n) {
        int ans = nums[0] == 1?0:1;
        long r = 1;
        int i = nums[0] == 1?1:0;
        while(r < (long)n && i < nums.length)
        {
            if(r < (long)nums[i] - 1)
            {
                ans++;
                r = 2*r + 1;
            }
            else
            {
                r = r + (long)nums[i];
                i++;
            }
        }
        while(r < (long)n) {
            r = 2 * r + 1;
            ans++;
        }
        return ans;
    }

路径交叉(HARD)

在这里插入图片描述

  • 分类讨论即可(需要仔细)
class Solution {
    public boolean isSelfCrossing(int[] distance) {
        if(distance.length < 4)
            return false;
        if(distance[2] > distance[0] || distance[3] < distance[1])
        {
            if(distance.length == 4)
                return false;
            int upper = distance[1] == distance[3] ? 0 : distance[0];
            int left = -distance[1];
            int lower = distance[0] - distance[2];
            int right = 0;
            int x = -distance[1] + distance[3],y = distance[0] - distance[2];  //现在位子
            int j = 4;
            if(distance[3] > distance[1])
            {
                int prex = 0;
                int prey = 0;
                int tempx;
                int tempy;
                while(true)
                {
                    if(j>=distance.length)
                        return false;
                    tempx = x;
                    tempy = y;
                    y += distance[j];
                    if(y < prey)
                        left = prex - distance[j-3];
                    else if(y<=prey + distance[j-4])
                        left = prex;
                    else left = Integer.MIN_VALUE;
                    if(j+1 >= distance.length)
                        return false;
                    x -= distance[j + 1];
                    if(x <= left)
                        return true;
                    if(j+2 >= distance.length)
                        return false;
                    if(x > prex)
                        lower = prey+distance[j-4] - distance[j-2];
                    else if(x >= prex - distance[j-3])
                        lower = prey + distance[j-4];
                    else lower  = Integer.MIN_VALUE;
                    y -= distance[j+2];
                    if(y <= lower)
                        return true;
                    if(j+3 >= distance.length)
                        return false;
                    right = tempx;
                    if(x < prex - distance[j-3] && (y<=prey+distance[j-4] && y>=prey+distance[j-4]-distance[j-2]))
                        right = prex - distance[j-3];
                    else if(y < tempy)
                        right = Integer.MAX_VALUE;
                    x+=distance[j+3];
                    if(x>=right)
                        return true;
                    if(distance[j+1] >= distance[j+3])
                    {
                        upper = (y<tempy && x>=prex - distance[j-3])?tempy:tempy + distance[j];
                        left = tempx - distance[j+1];
                        lower =  tempy + distance[j] - distance[j+2];
                        right = x;
                        j+=4;
                        break;
                    }
                    prex = tempx;
                    prey = tempy;
                    j+=4;
                }
            }
            else
                right = x;
            for(;j<distance.length;j+=4)
            {
                y = y + distance[j];
                if(y >= upper)
                    return true;
                upper = y;
                if(j+1 >= distance.length)
                    return false;
                x -= distance[j+1];
                if(x <= left)
                    return true;
                left = x;
                if(j+2 >= distance.length)
                    return false;
                y -= distance[j+2];
                if(y <= lower)
                    return true;
                lower = y;
                if(j+3 >= distance.length)
                    return false;
                x += distance[j+3];
                if(x >= right)
                    return true;
                right = x;
            }
            return false;
        }
        else return true;    //第一种图形相交
    }

  
}

回文对(HARD)

在这里插入图片描述

  • 讨论每一种拼接的情况。相同长度,需要互为逆序;不同长度,长度较小的需要是长度较长的前缀或后缀,长度较长的剩余部分需要是回文串
  • 获取每个字符串的逆序,保存到key为该逆序,value为Index的哈希表。当长度不同时,对每个原始字符串的前缀和后缀在hash中查找index,另外判断剩余部分是否是回文串。

超时代码

class Solution {
    public List<List<Integer>> palindromePairs(String[] words) {
        Map<String,Integer> q = new HashMap<>();
        int emptyindex = -1;
        for(int i = 0;i<words.length;i++)
        {
            if(words[i].equals(""))
                emptyindex = i;
            StringBuilder a = new StringBuilder(words[i]);
            q.put(a.reverse().toString(),i);
        }
        List<List<Integer>> ans = new ArrayList<>();
        //拼接长度相同的
        for(int i = 0;i<words.length;i++)
        {
            if(q.containsKey(words[i]) && q.get(words[i]) != i) {
                List<Integer> temp = new ArrayList<>();
                temp.add(i); temp.add(q.get(words[i]));
                ans.add(temp);
            }
        }
        //作为前缀和后缀,拼接长度比自己小的
        for(int p = 0;p<words.length;p++)
        {
            String a = words[p];
            boolean[][] dp = new boolean[a.length()][a.length()];
            if(words[p].equals(""))
                continue;
            for(int i = a.length() - 1;i>=0;i--)
                for(int j = a.length() - 1;j>=0;j--)
                {
                    if(i >= j)
                        dp[i][j] = true;
                    else
                        dp[i][j] = (i + 1 >= j - 1 || (dp[i + 1][j - 1] )) && a.charAt(j) == a.charAt(i);
                }
            if(dp[0][a.length() - 1] && emptyindex!=-1)
            {
                List<Integer> temp = new ArrayList<>();
                temp.add(p); temp.add(emptyindex);
                ans.add(temp);
                temp = new ArrayList<>();
                temp.add(emptyindex);temp.add(p);
                ans.add(temp);
            }
            for(int i = 0;i<a.length() - 1;i++)
            {
                //作为前缀
                if(dp[i+1][a.length() - 1] && q.containsKey(a.substring(0,i+1)))
                {
                    List<Integer> temp = new ArrayList<>();
                    temp.add(p); temp.add(q.get(a.substring(0,i+1)));
                    ans.add(temp);
                }
                //作为后缀
                if(dp[0][a.length() - 2 - i] && q.containsKey(a.substring(a.length() - 1 - i)))
                {
                    List<Integer> temp = new ArrayList<>();
                    temp.add(q.get(a.substring(a.length() - 1 - i)));
                    temp.add(p);
                    ans.add(temp);
                }
            }
        }
        return ans;
    }

    public static void main(String[] args)
    {
        new Solution().palindromePairs(new String[]{"abcd","dcba","lls","s","sssll"
});
    }
}

在这里插入图片描述

改进

  • 原因:卡常。不需要将dp[i][j]全部计算完成。
class Solution {
    public List<List<Integer>> palindromePairs(String[] words) {
        Map<String,Integer> q = new HashMap<>();
        int emptyindex = -1;
        for(int i = 0;i<words.length;i++)
        {
            if(words[i].equals(""))
                emptyindex = i;
            StringBuilder a = new StringBuilder(words[i]);
            q.put(a.reverse().toString(),i);
        }
        List<List<Integer>> ans = new ArrayList<>();
        //拼接长度相同的
        for(int i = 0;i<words.length;i++)
        {
            if(q.containsKey(words[i]) && q.get(words[i]) != i) {
                List<Integer> temp = new ArrayList<>();
                temp.add(i); temp.add(q.get(words[i]));
                ans.add(temp);
            }
        }
        //作为前缀和后缀,拼接长度比自己小的
        for(int p = 0;p<words.length;p++)
        {
            String a = words[p];
            if(words[p].equals(""))
                continue;
            if(ispar(a,0,a.length() - 1)&& emptyindex!=-1)
            {
                List<Integer> temp = new ArrayList<>();
                temp.add(p); temp.add(emptyindex);
                ans.add(temp);
                temp = new ArrayList<>();
                temp.add(emptyindex);temp.add(p);
                ans.add(temp);
            }
            for(int i = 0;i<a.length() - 1;i++)
            {
                //作为前缀
                if(ispar(a,i+1,a.length() - 1) && q.containsKey(a.substring(0,i+1)))
                {
                    List<Integer> temp = new ArrayList<>();
                    temp.add(p); temp.add(q.get(a.substring(0,i+1)));
                    ans.add(temp);
                }
                //作为后缀
                if(ispar(a,0,a.length() - 2 - i)&& q.containsKey(a.substring(a.length() - 1 - i)))
                {
                    List<Integer> temp = new ArrayList<>();
                    temp.add(q.get(a.substring(a.length() - 1 - i)));
                    temp.add(p);
                    ans.add(temp);
                }
            }
        }
        return ans;
    }

    public boolean ispar(String a,int low,int high)
    {
        while(low <= high)
        {
            if(a.charAt(low) == a.charAt(high))
            {
                low++;
                high--;
            }
            else
                return false;
        }
        return true;
    }
    public static void main(String[] args)
    {
        new Solution().palindromePairs(new String[]{"abcd","dcba","lls","s","sssll"
});
    }
}

将数据流变为多个不相交区间(HARD)

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

  • 分类讨论并维护不相交的区间即可

朴素的二分查找和区间维护

  • 算法分析:合并区间时删除后一个区间可能造成 O ( n ) O(n) O(n)的复杂度
class SummaryRanges {

    List<int[]> ita = new ArrayList<>();
    public SummaryRanges() {
    }

    public void addNum(int val) {
        if(ita.size() == 0) {
            ita.add(new int[]{val, val});
            return;
        }
        int low = 0;
        int high = ita.size();
        while(low < high)
        {
            int mid = (low + high) / 2;
            if(!(ita.get(mid)[0] >= val))
                low = mid + 1;
            else high = mid;
        }
        //既有前一个又有后一个
        if(low < ita.size() && low - 1 >= 0)
        {
            if(ita.get(low-1)[1] >= val || ita.get(low)[0] == val)   //原先存在
                return;
            if(val == ita.get(low-1)[1] + 1 && val == ita.get(low)[0] - 1)  //能够合并两个区间
            {
                ita.get(low - 1)[1] = ita.get(low)[1];
                ita.remove(low);
                return;
            }
            if(val == ita.get(low-1)[1] + 1)   //仅与前方合并
                ita.get(low -  1)[1]++;
            else if(val == ita.get(low)[0] - 1)  //仅与后方合并
                ita.get(low)[0]--;
            else
                ita.add(low,new int[]{val,val});   //不能合并
        }
        else if(low < ita.size())   //仅后面有
        {
            if(ita.get(low)[0] == val )
            if(val == ita.get(low)[0] - 1)  //仅与后方合并
                ita.get(low)[0]--;
            else ita.add(low,new int[]{val,val});
        }
        else  //仅前面有
        {
            if(ita.get(low-1)[1] >= val )
                return;
            if(val == ita.get(low-1)[1] + 1)   //仅与前方合并
                ita.get(low -  1)[1]++;
            else ita.add(low,new int[]{val,val});
        }
    }

    public int[][] getIntervals() {
        int[][] ans = new int[ita.size()][2];
        for(int i = 0;i<ita.size();i++)
            ans[i] = ita.get(i);
        return ans;
    }
}

加入哨兵的代码优化

  • 前后方分别加入[MIN,MIN],[MAX,MAX]区间避免分类讨论
class SummaryRanges {

    List<int[]> ita = new ArrayList<>();
    public SummaryRanges() {
        ita.add(new int[]{Integer.MIN_VALUE,Integer.MIN_VALUE});
        ita.add(new int[]{Integer.MAX_VALUE,Integer.MAX_VALUE});
    }

    public void addNum(int val) {
        int low = 1;
        int high = ita.size() - 1;
        while(low < high)
        {
            int mid = (low + high) / 2;
            if(!(ita.get(mid)[0] >= val))
                low = mid + 1;
            else high = mid;
        }
        if(ita.get(low-1)[1] >= val || ita.get(low)[0] == val)   //原先存在
            return;
        if(val == ita.get(low-1)[1] + 1 && val == ita.get(low)[0] - 1)  //能够合并两个区间
        {
            ita.get(low - 1)[1] = ita.get(low)[1];
            ita.remove(low);
            return;
        }
        if(val == ita.get(low-1)[1] + 1)   //仅与前方合并
            ita.get(low -  1)[1]++;
        else if(val == ita.get(low)[0] - 1)  //仅与后方合并
            ita.get(low)[0]--;
        else
            ita.add(low,new int[]{val,val});   //不能合并

    }

    public int[][] getIntervals() {
        int[][] ans = new int[ita.size() - 2][2];
        for(int i = 1;i<ita.size()-1;i++)
            ans[i-1] = ita.get(i);
        return ans;
    }
}

使用TreeSet有序映射维护区间 + 哨兵优化

  • 红黑树都数据结构可以区间值为键值。查找最后一个小于和第一个大于等于的区间左端点,删除,插入等都在 O ( l o g n ) O(logn) O(logn)时间完成。
  • 无论何种情况顺序都不会变化,因此改变数组元素并不影响原始红黑树的结构
class SummaryRanges {

    TreeSet<int[]> ita = new TreeSet<>(new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            return Integer.compare(o1[0],o2[0]);
        }
    });
    public SummaryRanges() {
        ita.add(new int[]{Integer.MIN_VALUE,Integer.MIN_VALUE});
        ita.add(new int[]{Integer.MAX_VALUE,Integer.MAX_VALUE});
    }

    public void addNum(int val) {
        int[] cur = new int[]{val,val};
        int[] prev = ita.floor(cur);
        int[] next = ita.ceiling(cur);
        if(prev[1] >= val || next[0] == val)   //原先存在
            return;
        if(val == prev[1] + 1 && val == next[0] - 1)  //能够合并两个区间
        {
            prev[1] = next[1];
            ita.remove(next);
            return;
        }
        if(val == prev[1] + 1)   //仅与前方合并
            prev[1]++;
        else if(val == next[0] - 1)  //仅与后方合并
            next[0]--;
        else
            ita.add(new int[]{val,val});   //不能合并

    }

    public int[][] getIntervals() {
        Iterator<int[]> iter = ita.iterator();
        int n = ita.size();
        int[][] ans = new int[n-2][2];
        iter.next();   //忽略第一个[MIN,MIN]
        for(int i = 0;i<n-2;i++)
        {
            ans[i] = iter.next();
        }
        return ans;
    }
}

俄罗斯套娃信封问题(HARD,很好的一道题)

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

  • 信封间显然偏序关系。将信封看作结点,即为求有向无环图中最长路径
  • 但是建图 O ( n 2 ) O(n^2) O(n2)

Motivation(考察最优解的形式)

  • 注意解的特殊性:任何上升序列的宽w都是不同的
  • 根据宽对所有的信封分组,宽相同的为一组
  • 问题转换为从每个分组中抽取至多一封信(当前分组也可以不抽取),使得上升序列最大。
  • 上升首先要保证宽上升。故先考虑分组顺序为:宽较小的分组在前,较大的在后。这样分组抽取的顺序也就保证了。
  • 问题转换为:按顺序,每个分组至多选择一封信,且选择后的信封高要上升。如何能选择最多的信封?
  • 如能对每个分组中的高从大到小排列,则上升序列不会出现在分组的内部,只会出现在分组之间,此时求最长上升子序列即可(二分求法)。
    public int maxEnvelopes(int[][] envelopes) {
        Arrays.sort(envelopes, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                int a = Integer.compare(o1[0],o2[0]);
                if(a != 0)
                    return a;
                return -Integer.compare(o1[1],o2[1]);
            }
        });  //第一关键字:w,第二关键字:h排序
        List<Integer> a = new ArrayList<>();
        a.add(envelopes[0][1]);
        for(int i = 1;i<envelopes.length;i++)
        {
            int low = 0;
            int high = a.size();
            int b = a.get(a.size() - 1);
            if(b < envelopes[i][1])
            {
                a.add(envelopes[i][1]);
            }
            else if(b > envelopes[i][1])
            {
                while(low < high)
                {
                    int mid = (low + high) >> 1;
                    if(a.get(mid) < envelopes[i][1])
                        low = mid + 1;
                    else high = mid;
                }
                a.set(low,envelopes[i][1]);
            }
        }
        return a.size();
    }

矩形区域不超过K的最大数值和

在这里插入图片描述

二维前缀和(暴力),复杂度 O ( m 2 n 2 ) O(m^2n^2) O(m2n2)

  • d p [ i ] [ j ] dp[i][j] dp[i][j],i,j格点及其左上角矩阵元素之和
class Solution {
    public int maxSumSubmatrix(int[][] matrix, int k) {
        int m = matrix.length;
        int n = matrix[0].length;
        int[][] dp = new int[m][n];
        int ans = Integer.MIN_VALUE;
        for(int i = 0;i<m;i++)
            for(int j = 0;j<n;j++)
            {
                int a = j-1 >= 0 ? dp[i][j-1]: 0;       //左边的一个
                int b = i-1>=0? dp[i-1][j] : 0;  //上边的一个
                int c = (i-1)>=0 && (j-1)>=0?dp[i-1][j-1]:0; //左上角的一个
                dp[i][j] = a + b -c  +matrix[i][j];   //所有包括左上角的矩形区域
                if(dp[i][j]<=k)
                    ans = Math.max(ans,dp[i][j]);
            }
        for(int i = 0;i<m;i++)
            for(int j = 0;j<n;j++)
            {
                for(int p = 0;p<=i;p++)
                    for(int q = 0;q<=j;q++)
                    {
                        int c = (p-1)>=0 && (q-1)>=0 ? dp[p-1][q-1]:0;
                        int a = (p-1)>=0 ? dp[p-1][j] : 0;
                        int b = (q-1)>=0? dp[i][q-1]:0;
                        int d = (dp[i][j] - a - b + c);
                        if(d<=k)
                            ans = Math.max(ans,d);
                    }
            }
        return ans;
    }
}

数组最大区间和(有序集合log的魅力)

  • 一维数组,如何求最大区间和?求前缀和,利用平衡树维护前缀和的有序性,在树上二分求解,时间复杂度 O ( l o g n ) O(logn) O(logn)
  • 有序集合:
    • Ceiling方法:求解大于等于指定数的最小数
    • floor方法:求解小于等于指定数的最大数
class Solution {
    public int maxSumSubmatrix(int[][] matrix, int k) {
        int m = matrix.length;
        int n = matrix[0].length;
        int[][] dp = new int[matrix.length][matrix[0].length];  //前缀和
        int ans = Integer.MIN_VALUE;
        for(int i = 0;i<matrix.length;i++)
        {
            for(int j = 0;j<dp[0].length;j++)
            {
                int a;
                if(i == 0)
                    a = 0;
                else a = dp[i-1][j];
                dp[i][j] = a + matrix[i][j];
            }
        }
        for(int i = 0;i<matrix.length;i++)
            for(int j = i;j<matrix.length;j++)   //[i,j]区间的一列之和
            {
                TreeSet<Integer> s = new TreeSet<>();
                s.add(0);
                int pre = 0;
                for(int q= 0;q<matrix[0].length;q++)
                {
                    int a;
                    if(i - 1 < 0)
                        a = 0;
                    else a = dp[i-1][q];
                    int cur = dp[j][q] - a;       //当前第q列元素
                    pre += cur;                //前缀和
                    Integer p = s.ceiling(pre - k);
                    if(p != null)
                        ans = Math.max(ans,pre - p);
                    s.add(pre);
                }
            }
        return ans;
    }

    public static void main(String[] args)
    {
        new Solution().maxSumSubmatrix(new int[][]{{1,0,1},{0,-2,3}},2);
    }
}

O(1)时间插入,删除和获取随机元素-允许重复

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

  • 没有想到的点:固化的想存储每个元素个数,利用0-1间随机数的线性映射。可以利用一个list存储所有元素,根据random生成到size的整数即可
  • 如果顺序无关紧要,删除list中一个元素可以与最后一个元素交换达到 O ( 1 ) O(1) O(1)复杂度

不使用迭代器

  • 当元素不重复时,可使用map:数据到数据list下标的映射,以及数据list实现getrandom。
  • 对同一元素在b数组中的下标不重复,使用上述方法getrandom
  • b数组存储所有元素
  • m_1:val到b中下标列表的映射
  • m_2: val到一个map的映射,这个map为b中下标到m_1下标列表下标的映射
class RandomizedCollection {
    List<Integer> b = new ArrayList<>();
    Map<Integer,List<Integer>>  m_1 = new HashMap<>();
    Map<Integer,Map<Integer,Integer>> m_2 = new HashMap<>();
    Random rand = new Random();


    public RandomizedCollection() {
    }

    public boolean insert(int val) {
        boolean ans = !m_1.containsKey(val);
        b.add(val);
        List<Integer> temp = m_1.getOrDefault(val,new ArrayList<>());
        temp.add(b.size() - 1);
        m_1.put(val,temp);
        //b中下标到m_1中list下标的映射
        Map<Integer,Integer> m = m_2.getOrDefault(val,new HashMap<>());
        m.put(b.size() - 1,temp.size() - 1);
        m_2.put(val,m);
        return ans;
    }

    public boolean remove(int val) {
        if(m_1.containsKey(val))
        {
            List<Integer> temp = m_1.get(val);
            int index = temp.remove(temp.size() - 1);   //删除最后一个
            Map<Integer,Integer> tmp =  m_2.get(val);
            tmp.remove(index);   //删除b中下标
            //在b中删除该值
            if(index == b.size() - 1)
                b.remove(b.size() - 1);
            else    //需要和b的最后一个元素交换
            {
                int q = b.get(b.size() - 1);
                b.set(index,q);
                Map<Integer,Integer> p = m_2.get(q);
                List<Integer> r = m_1.get(q);
                int index_r = p.get(b.size() - 1);   //r中的index
                p.remove(b.size() - 1);
                r.set(index_r,index);
                p.put(index,index_r);
                b.remove(b.size() - 1);   //和当前元素交换的值
            }
            if(temp.size() == 0)
            {
                m_1.remove(val);
                m_2.remove(val);
            }
            return true;
        }
        return false;
    }

    public int getRandom() {
        return b.get(rand.nextInt(b.size()));
    }

使用迭代器

  • 上述方法复杂的原因在于不能随机获取set中某个元素导致(即获取一个下标)
  • 使用迭代器.next方法获取元素
class RandomizedCollection {
    Map<Integer, Set<Integer>> idx;
    List<Integer> nums;

    /** Initialize your data structure here. */
    public RandomizedCollection() {
        idx = new HashMap<Integer, Set<Integer>>();
        nums = new ArrayList<Integer>();
    }
    
    /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
    public boolean insert(int val) {
        nums.add(val);
        Set<Integer> set = idx.getOrDefault(val, new HashSet<Integer>());
        set.add(nums.size() - 1);
        idx.put(val, set);
        return set.size() == 1;
    }
    
    /** Removes a value from the collection. Returns true if the collection contained the specified element. */
    public boolean remove(int val) {
        if (!idx.containsKey(val)) {
            return false;
        }
        Iterator<Integer> it = idx.get(val).iterator();  
        int i = it.next();
        int lastNum = nums.get(nums.size() - 1);
        nums.set(i, lastNum);
        idx.get(val).remove(i);
        idx.get(lastNum).remove(nums.size() - 1);
        if (i < nums.size() - 1) {
            idx.get(lastNum).add(i);
        }
        if (idx.get(val).size() == 0) {
            idx.remove(val);
        }
        nums.remove(nums.size() - 1);
        return true;
    }
    
    /** Get a random element from the collection. */
    public int getRandom() {
        return nums.get((int) (Math.random() * nums.size()));
    }
}



完美矩形(HARD)

找规律(没有想到判断重叠的方法)

  • 多画一些图找出充要条件
  • 找出矩形区域的四个顶点,则首先需要满足这四个顶点出现且仅出现一次,所有矩形面积之和等于该区域面积再加上矩形间两两不重合即可。
  • 有限区域不重叠的充要条件的必要条件:矩形区域四个顶点仅出现一次且其余顶点出现2或4次。
  • 考察任意一种矩形重合的情形(注意区域的有限性),产生矛盾。
  • 于是上述条件为充要条件。
    在这里插入图片描述
class Solution {

    class point
    {
        int x;
        int y;

        public point(int x,int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            point point = (point) o;
            return x == point.x &&
                    y == point.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
    }


    public boolean isRectangleCover(int[][] rectangles) {
        //conflict的概率可能较大
        Map<point,Integer> m = new HashMap<>();
        long s = 0;
        int lx = Integer.MAX_VALUE;  //区域一定最左下角的x值(若是完美矩形的话),以此类推(没有想到的点),是所有矩形x的最小值
        int ly = Integer.MAX_VALUE;
        int ux = Integer.MIN_VALUE;
        int uy = Integer.MIN_VALUE;
        point point1;
        point point2;
        point point3;
        point point4;

        for(int i = 0;i<rectangles.length;i++)
        {
            int x1 = rectangles[i][0];
            int y1 = rectangles[i][1];
            int x2 = rectangles[i][2];
            int y2 = rectangles[i][3];

            //累计面积
            s += (long)(x2 - x1) * (y2 - y1);

            //更新边角坐标
            lx = Math.min(lx,x1);
            ly = Math.min(ly,y1);
            ux = Math.max(ux,x2);
            uy = Math.max(uy,y2);

            //四个顶点的计数
             point1 = new point(x1,y1);
             point2 = new point(x1,y2);
             point3 = new point(x2,y1);
             point4 = new point(x2,y2);

            m.put(point1,m.getOrDefault(point1,0)+1);
            m.put(point2,m.getOrDefault(point2,0)+1);
            m.put(point3,m.getOrDefault(point3,0)+1);
            m.put(point4,m.getOrDefault(point4,0)+1);
        }
        point1 = new point(lx,ly);
        point2 = new point(lx,uy);
        point3 = new point(ux,ly);
        point4 = new point(ux,uy);
        if(s!= (long)(uy - ly) * (ux - lx)|| m.getOrDefault(point1,0)!= 1 || m.getOrDefault(point2,0)!= 1 || m.getOrDefault(point3,0)!= 1 || m.getOrDefault(point4,0)!= 1)
            return false;
        m.remove(point1); m.remove(point2); m.remove(point3); m.remove(point4);
        for(point a : m.keySet())
        {
            if(m.get(a) != 2 && m.get(a) != 4)
                return false;
        }
        return true;
    }

    public static void main(String[] args)
    {
        new Solution().isRectangleCover(new int[][]{{-100000,-100000,100000,100000}});

    }
}

扫描线(另一种充分必要条件)

  • 边缘竖线单独出现且连成整体(红色)
  • 其他竖线若直接相连需要成对出现(绿色)
  • 如图所示
    在这里插入图片描述

周赛T4-统计可以被K整除的下标数(HARD)

在这里插入图片描述

  • 对于固定点nums[j]若想乘一个数使得结果能被k整除。分解质因数的结果表明nums[i]必须有因子k/gcd(nums[j],k)
  • 没有想到:如何维护nums的前缀中每个因子的个数。
    • 不对每个数分解质因数再组合(造成重复),而是考察每个可能的因子所能达到的数(注意体会)
  • 注意static的应用:只运行一次,降低时间复杂度
  • 算法分析:预处理每个数的因子所用时间最多
    m + m 2 + ⋯ + = l o g m m + \frac{m}{2} + \cdots + = logm m+2m++=logm
class Solution {
    public int gcd(int m,int n)
    {
        if(m < n)
            return gcd(n,m);
        int r = 0;
        while(n>0)
        {
            r = m % n;
            m = n;
            n = r;
        }
        return m;
    }

    static List<Integer>[] a = new List[100001];
    static{
        for(int  i= 1;i<a.length;i++)
            a[i] = new ArrayList();


        for(int i = 1;i<=100000;i++)  //枚举所有因子
            for(int j = i;j<=100000;j+=i)  //该因子可以达到的数
                a[j].add(i);
    }



    public long countPairs(int[] nums, int k) {
        //统计每个数的因子
        //转换为每个因子能够达到的数,降低复杂度

        //左边数中具有的所有因子
        Map<Integer,Integer> b = new HashMap<>();
        for(int x : a[nums[0]])
            b.put(x,b.getOrDefault(x,0)+1);
        long ans =0;
        for(int i = 1;i<nums.length;i++)
        {
            int x =k/ gcd(nums[i],k);
            ans += (long)b.getOrDefault(x,0);
            for(int o : a[nums[i]])
                b.put(o,b.getOrDefault(o,0) + 1);
        }
        return ans;
    }



}

双周赛T4-统计数组中好三元组数目

在这里插入图片描述

MergeSort的思路

  • 没有想到的点:问题的形式化:设nums1中i<j<k的下标的三元组的数,经过双射映射到nums2中下标为 f [ i ] , f [ j ] , f [ k ] f[i],f[j],f[k] f[i],f[j],f[k]
  • 只需要f[i]<f[j]<f[k]即可,即求f数组中递增三元组个数。归并排序即可
class Solution {
    //asend是否升序?
    public int[] mergesort(int low,int high,int[] nums,Map<Integer,Integer> b,boolean asend)
    {
        if(low == high)
            return new int[]{nums[low]};
        int mid = (low + high) >> 1;
        int[] l = mergesort(low,mid,nums,b,asend);
        int[] r = mergesort(mid + 1,high ,nums,b,asend);
        int[] ret = new int[l.length + r.length];
        int i = 0,j = 0,k = 0;
        while(i < l.length && j < r.length)
        {
            if(l[i] < r[j])
            {
                if(asend)
                {
                    b.put(l[i],b.getOrDefault(l[i],0)+(r.length - j));
                    ret[k++] = l[i++];
                }
                else
                {
                    b.put(r[j],b.getOrDefault(r[j],0) + (l.length - i));
                    ret[k++] = r[j++];
                }
            }
            else
            {
                if(asend)
                    ret[k++] = r[j++];
                else ret[k++] = l[i++];
            }
        }
        while(i < l.length)
        {
            ret[k++] = l[i++];;
        }
        while(j < r.length)
            ret[k++] = r[j++];
        return ret;
    }


    public long goodTriplets(int[] nums1, int[] nums2) {
        //构造f数组
        int[] f = new int[nums1.length];
        //建立nums[2]的逆映射
        Map<Integer, Integer> a = new HashMap<>();
        for (int i = 0; i < nums2.length; i++)
        {
            a.put(nums2[i],i);
        }
        for(int i = 0;i<nums1.length;i++)
        {
            f[i] = a.get(nums1[i]);
        }
        int[] g = new int[f.length];
        System.arraycopy(f,0,g,0,f.length);
        long ans = 0;
        a.clear();  //记录每个元素左侧小于自身元素的个数
        Map<Integer,Integer> b = new HashMap<>();  //记录每个元素右侧大于自身元素的个数
        mergesort(0,f.length - 1,f,a,true);
        mergesort(0,f.length - 1,g,b,false);
        for(int c : a.keySet())
            if(b.containsKey(c))
                ans += (long) a.get(c) * b.get(c);
        return ans;
    }

    public static void main(String[] args)
    {
        new Solution().goodTriplets(new int[]{2,0,1,3},new int[]{0,1,2,3});
    }
}

数据结构-树状数组

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值