leetcode1月31日-2月6日做题笔记

重复的DNA序列(MEDIUM)

  • 如果仅利用哈希表等信息判重,会造成 O ( n L ) O(nL) O(nL)的复杂度

  • 没有充分利用仅有ATCG四种字符这种性质!
    leetcode1月31日-2月6日做题笔记_第1张图片

  • 考虑状态压缩!利用位运算!分别编码ATCG为00,01,10,11!则每个字符串与一个20位的整数一一对应!

class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        Map<Integer,Boolean> a = new HashMap<>();
        Map<Character,Integer> b = new HashMap<>();
        b.put('A',0);b.put('T',1);b.put('C',2);b.put('G',3);
        List<String> ans = new ArrayList<>();
        int n = s.length();
        if(n <= 10)
            return ans;
        int mask = (1 << 20)  - 1;
        //取前10个字符
        int value = 0;
        for(int i = 0;i<10;i++)
        {
            value = value  |  (b.get(s.charAt(i)) << (18 -  2*i));
        }
        a.put(value,false);
        for(int i = 1;i<=n - 10;i++)
        {
            value = (( value << 2 ) | b.get(s.charAt(i + 9))) & mask;
            if(!a.containsKey(value))
            {
                a.put(value,false);
            }
            else if(!a.get(value)) {
                a.put(value, true);
                ans.add(s.substring(i,i+10));
            }
        }
        return ans;
    }
}

数字范围按位与(MEDIUM)

在这里插入图片描述

  • 开始的 l o g n 2 log n^2 logn2的代码,即观察left为1的位置左边第一个为0的位置再与left原先的位置相与是否小于等于right:
class Solution {
    public int rangeBitwiseAnd(int left, int right) {
//        long a;
        int ans  =  0;
        for(int i = 0;i <= 30;i++)
        {
            if(((left >> i) & 1 )== 1)
            {
                int j;
               for(j = i+1;j<=30 && ((left >> j) & 1 )== 1;j++);
               if(j<=30)
               {
                   if(((1 << j) | (left & (Integer.MAX_VALUE - ((1 << j) - 1)) ))>right)
                       ans  |= (1 << i);
               }
               else ans  |= (1 << i);
            }
        }
        return ans;
    }
}
  • 优化:left和right的最长公共前缀是最终结果!从左到右考察left和right第一位不相同的位置,left为0,right为1,对left从该位往后全是0,该位为1再加上最长公共前缀构成的数在[left,right]范围内,而该位本身与right相与结果为0
  • 求最长公共前缀:不断右移left,right直到两者相等
class Solution {
    public int rangeBitwiseAnd(int left, int right) {
        int  i = 0;
        while(left < right)
        {
            left = left >> 1;
            right = right >> 1;
            i++;
        }
        return left << i;
    }
}
  • Brian Kernighan 算法(不需要i计数)
  • 主要原理:num和Num-1相与可去掉num最右侧的1
class Solution {
    public int rangeBitwiseAnd(int left, int right) {
        int  i = 0;
        while(left < right)
        {
            right &= (right  - 1);
        }
        return right;
    }
}

排列序列(HARD)

leetcode1月31日-2月6日做题笔记_第2张图片

  • next permutation的思路:
    public String getPermutation(int n, int k) {
        int[] a = new int[n + 1];
        for(int i = 1;i<a.length;i++)
        {
            a[i ] = i  ;
        }
        int count = 1;
        while(count < k)
        {
            int j;
            for(j = a.length - 1;j>=2 && a[j - 1] > a[j];j--);
            int q;
            for(q = a.length - 1;a[q] <= a[j - 1];q--);
            int temp = a[q];
            a[q]  = a[j - 1];
            a[j-1] = temp;
            int low = j;
            int high = a.length - 1;
            while(low <= high)
            {
                int tmp = a[high];
                a[high] = a[low];
                a[low] = tmp;
                low++;
                high--;
            }
            count ++;
        }
        //count == k
        StringBuilder s = new StringBuilder();
        for(int i = 1;i<=a.length - 1;i++)
            s.append((char)(a[i] + '0'));
        return s.toString();
    }
  • 可能造成 O ( n ! ∗ n ) O(n! *n) O(n!n)的运算复杂度!
  • 缩小问题规模+数学:
    • 固定第一个数字,有(n-1)!个排列。根据k的大小确定第一个数字,如此递归下去。时间复杂度 O ( n 2 ) O(n^2) O(n2)
 public String getPermutation(int n, int k) {
       List<Integer> a = new ArrayList<>();
       for(int i = 1;i<=n;i++)
       {
            a.add(i);
       }
       StringBuilder s = new StringBuilder();
       int acc = 0;
       for(int i = 1;i<=n;i++)  //n步选择
       {
           int n_fa = 1;
           for(int j  = 1;j<=(n - i);j++) {
               n_fa *= j;
           }
           for(int j = 1;j<=n - i + 1;j++)
               if(acc + n_fa * j >= k)
               {
                   s.append(a.get(j - 1));
                   a.remove(j - 1);
                   acc += n_fa * (j - 1);
                   break;
               }
       }
       return s.toString();
    }

扰乱字符串-HARD

leetcode1月31日-2月6日做题笔记_第3张图片
leetcode1月31日-2月6日做题笔记_第4张图片

  • 记忆化搜索
  • 注意长度相等
  • dp[i][j][k],字符串s1从第i个字符开始,s2从第j个字符开始,长度k个字符的字符串能否通过互相扰动得到。

没有优化之前(纯记忆化搜索,10ms)

class Solution {
    public int dfs(int i,int j,int k,int[][][] dp,String s1,String s2)
    {
        if(dp[i][j][k]!=0)
            return dp[i][j][k];
        if(k == 1)
        {
            dp[i][j][k] = s1.charAt(i) == s2.charAt(j) ? 1 : -1;
            return dp[i][j][k];
        }
        if(i + k - 1 >= s1.length() || j + k - 1 >= s2.length())
        {
            dp[i][j][k] = -1;
            return -1;
        }
        int  a = -1;
        dp[i][j][k] = -1;
        for(int len = 1 ; len <= k - 1;len++)
        {
            if(dfs(i,j,len,dp,s1,s2) == 1 && dfs(i + len,j + len,k  - len,dp,s1,s2) == 1)
            {
                dp[i][j][k] = 1;
                return 1;
            }
            if(dfs(i,j+k - len ,len,dp,s1,s2) == 1&& dfs(i +len,j,k - len,dp,s1,s2) == 1)
            {
                dp[i][j][k] = 1;
                return 1;
            }
        }
        return -1;
    }


    public boolean isScramble(String s1, String s2) {
        int[][][] dp  =  new int[30][30][31];    //dp[i][j][k]表明从s_1开始,从s_2开始的字符串,长度为k的字符串是否能经过扰乱还原
        // for(int i  = 0;i<30;i++)
        //     for(int j = 0;j<30;j++)
        //         {
        //             if(s1.charAt(i) == s2.charAt(j))
        //                 dp[i][j][k] = 1;
        //             else dp[i][j][k] = -1;
        //         }
        int a = dfs(0,0,s1.length(),dp,s1,s2);
        return a == 1;
    }

    public static void main(String[] args)
    {
        new Solution().isScramble("ab","aa");
    }
}

周赛T4-移除所有载有违禁货物车厢所需的最少时间(HARD-前后缀DP)leetcode1月31日-2月6日做题笔记_第5张图片

leetcode1月31日-2月6日做题笔记_第6张图片

前后缀分隔DP的MOTIVATION

  • 直接前缀或后缀DP得到的困难。如果令 p r e [ i ] pre[i] pre[i]从最前直到处理到第i+1个车厢所需要的最小时间。对第i+1个车厢可能从右侧直接移除,于是 p r e pre pre不具有最优子结构。同理 s u f [ i ] suf[i] suf[i]也不具有。
  • 但是所有可能的移除方法,总可将移除操作在s中分为两部分。前半部分移除仅运用1,3策略,后半部分仅运用2,3策略。(对任意解考虑最长的从左存在1策略的部分,则剩余的部分仅运用2,3策略)
  • 因此最终只需要搜索分割点即可

Solution(空间复杂度 O ( n ) O(n) O(n),遍历三次)

  • p r e [ i ] pre[i] pre[i]仅使用1,3策略处理从最前到s[i]需要的最小时间,若 s [ i ] = 0 s[i] = 0 s[i]=0,则 p r e [ i ] = p r e [ i − 1 ] pre[i] = pre[i-1] pre[i]=pre[i1];若 s [ i ] = 1 s[i]=1 s[i]=1则对第i+1个车厢可能直接删除,可能从s[i]最前到右删除,即 p r e [ i ] = m i n { p r e [ i − 1 ] + 2 , i + 1 } pre[i] = min\{pre[i-1] + 2,i+1\} pre[i]=min{pre[i1]+2,i+1}
    • 由于仅采用1,3策略,不从后往前删除, p r e [ i − 1 ] pre[i-1] pre[i1]对应的方法完全不依赖于 s [ i ] s[i] s[i]
  • 同理令 s u f [ i ] suf[i] suf[i]仅使用2,3策略处理从最后到 s [ i ] s[i] s[i]需要的最小时间,当 s [ i ] = 0 , s u f [ i ] = s u f [ i + 1 ] s[i] = 0,suf[i] = suf[i+1] s[i]=0,suf[i]=suf[i+1],而 s [ i ] = 1 s[i] = 1 s[i]=1有:
    s u f [ i ] = min ⁡ { s u f [ i + 1 ] + 2 , s . l e n g t h − i } suf[i] = \min\{ suf[i+1] + 2,s.length - i\} suf[i]=min{suf[i+1]+2,s.lengthi}
  • 原问题: max ⁡ i { p r e [ i ] + s u f [ i + 1 ] } \max_i\{pre[i] + suf[i+1]\} maxi{pre[i]+suf[i+1]}
class Solution {
    public int minimumTime(String s) {
        int pre = s.charAt(0) == '0'?0 : 1;
        int[] suf = new int[s.length()];
        int len = s.length();
        suf[len - 1] = s.charAt(len - 1) == 0?0 : 1;
        for(int i = len - 2;i>=0;i--)
        {
            if(s.charAt(i) == '0')
                suf[i] = suf[i + 1];
            else suf[i] = Math.min(suf[i+1]+2,len - i);
        }
        int ans = suf[0];
        for(int i = 0;i<len - 1;i++)
        {
            ans = Math.min(suf[i + 1]+pre,ans);
            pre = s.charAt(i + 1) == '0'?pre:Math.min(pre + 2,i+2);
        }
        return Math.min(pre,ans);
    }
}

Solution(空间复杂度 O ( 1 ) O(1) O(1),必要条件减小搜索空间)

  • 上述解法并未充分利用最优解的必要条件。
  • 对最优解而言,是否存在一种等价的划分方式,划分点右侧全用策略2,左侧全用策略1,3呢?
  • 对任意一个可行解,只需考虑最后一个从右边删除的不合法车厢,从该车厢(包括)向右的所有车厢都是通过策略2删除的。以该车厢作为划分点即可。
class Solution {
    public int minimumTime(String s) {
        int pre = s.charAt(0) == '0'?0 : 1;
        int len = s.length();
        int ans = len;
        for(int i = 0;i<len - 1;i++)
        {
            ans = Math.min(pre + len - i - 1,ans);
            pre = s.charAt(i + 1) == '0'?pre:Math.min(pre + 2,i+2);
        }
        return Math.min(pre,ans);
    }
}
  • 主要分析解满足什么样的形式,在这样的形式下搜索最优解。例如这道题的最优解一定满足:从左边一部分使用策略1删除,中间一部分使用策略3删除,后边使用策略2删除。中间部分和左边部分可以合并成pre变量。在这样的搜索空间下,确定分割点得到最优解。

双周赛T4-设置最小时间的代价(HARD)

leetcode1月31日-2月6日做题笔记_第7张图片

  • 和上一道DP类似,枚举分割点(实际上是划分解空间)+优先队列,和k递增那道题类似:考察删除后数组的性质(分析最优解所满足的必要条件),前n个元素一定是某个分割点左侧n个最大元素,后n个元素相反。
  • 求最大n个元素(最小n个元素和),使用优先队列
class Solution {
    public long minimumDifference(int[] nums) {
        PriorityQueue<Integer> q_min = new PriorityQueue<>();
        int n = nums.length / 3;
        long[] right = new long[nums.length];
        for(int i = nums.length - 1;i>=2 * n ;i--) {
            right[n] += (long)nums[i];
            q_min.add(nums[i]);
        }
        for(int i = 2 * n - 1;i>=n;i--)
        {
            if(nums[i] > q_min.peek())
            {
                int a = q_min.poll();
                q_min.add(nums[i]);
                right[3*n - i] = right[3*n - i - 1] - (long)a + (long)nums[i];
            }
            else right[3*n - i] = right[3*n - i - 1];
        }
        long left = 0;
        PriorityQueue<Integer> q_max = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return -Integer.compare(o1,o2);
            }
        });
        for(int i = 0;i<n;i++)
        {
            q_max.add(nums[i]);
            left += (long)nums[i];
        }
        long ans = Long.MAX_VALUE;
        ans = Math.min(ans,left - right[2*n]);
        for(int i = n ;i<=2*n - 1;i++)
        {
            if(nums[i] < q_max.peek())
            {
                int a = q_max.poll();
                q_max.add(nums[i]);
                left = left + (long)nums[i] - (long)a;
            }
            ans = Math.min(ans,left - right[3*n - i - 1]);
        }
        return ans;
    }

}

你可能感兴趣的:(leetcode)