leetcode-2022/1/24-2022/1/30做题记录

地下城游戏(HARD)

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

  • 正向动态规划:一开始想的是使用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示到达(i,j)点,从(0,0)所可能的最小初始点数,但并不具有最优子结构!还考虑同时保存直到(i,j)点该种走法的剩余点数,也受到最优子结构的限制。
  • 当正向不起作用,考虑反向DP。考虑从(i,j)开始到最右下角的子矩阵,可以求出从(i,j)到最右下角的最小初始点数。
  • 从(i,j)只可能向右或向下,所以如果右边和下边的点到最右下方的最小初始点数求出来,就有:
    D P [ i ] [ j ] = m a x ( 1 , m i n ( D P [ i ] [ j − 1 ] , D P [ i − 1 ] [ j ] ) − n u m [ i ] [ j ] ) DP[i][j] = max(1,min(DP[i][j-1],DP[i-1][j])-num[i][j] ) DP[i][j]=max(1,min(DP[i][j1],DP[i1][j])num[i][j])
class Solution {
    public int calculateMinimumHP(int[][] dungeon) {
        int[] dp  =  new int[dungeon[0].length];
        dp[dp.length  - 1] = Math.max(1,1-dungeon[dungeon.length - 1][dp.length - 1]);
        for(int j = dp.length - 2;j>=0;j--)
            dp[j] = Math.max(dp[j+1] - dungeon[dungeon.length - 1][j],1);
        int[] predp = new int[dp.length];
        System.arraycopy(dp,0,predp,0,dp.length);
        for(int i = dungeon.length - 2;i>=0;i--)
        {
            for(int j = dp.length - 1;j>=0;j--)
            {
                if(j == dp.length  -  1)
                    dp[j] = Math.max(dp[j] - dungeon[i][j],1);
                else
                {
                    dp[j] = Math.max(1,Math.min(dp[j+1],predp[j]) - dungeon[i][j]);
                }
            }
            System.arraycopy(dp,0,predp,0,dp.length);
        }
        return dp[0];
    }
}

翻转字符串中的单词(MEDIUM)

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

  • 空间 O ( 1 ) O(1) O(1)做法(想不到):去翻转整个字符串,再翻转每个单词(在去除所有的多余空格之后),虽然JAVA本身无法对字符串进行原地修改
class Solution {

    public void reverse(char[] a,int i , int j)
    {
        while(i<=j)
        {
            char temp = a[i];
            a[i] = a[j];
            a[j] = temp;
            i++;
            j--;
        }
    }

    public String reverseWords(String s) {
        s = s.strip();  //移除开头和末尾的空格
        s = s.replaceAll(" +"," ");  //单个空格
        char[] a = s.toCharArray();
        int i = 0;
        int j = s.length() - 1;
        //反转整个字符串
        reverse(a,i,j);
        //反转每个单词
        i = 0;
        j = i;
        while(i<a.length && j < a.length)
        {
            while(j<s.length() && a[j]!=' ')
            {
                j++;
            }
            reverse(a,i,j-1);
            i = j + 1;
            j = i;
        }
        StringBuilder ans  = new StringBuilder();
        for(i = 0;i<a.length;i++)
        {
            ans.append(a[i]);
        }
        return ans.toString();
    }
}

最大间距(HARD)(基数排序)

重新写下原来学过的数据结构中基数排序的代码

  • 设置10个队列,分别装载当前位为0-9的数字。从个位开始,遍历Num数组:
    • 将当前数字加入表示当前数字当前位的队列
  • 遍历完成一位后,从0表示的队列开始出队,出队元素从nums的第一个元素开始放,直到所有队列为空。
  • 下面的实现比较巧妙:
    • cnt用于记录buf当中哪一下标用于放置当前位的最后一个数字
    • 注意重新入队时的顺序
class Solution {
    public int maximumGap(int[] nums) {
        if(nums.length < 2)
            return 0;
        long e  = 1;
        int max_val = Arrays.stream(nums).max().getAsInt();
        while(e <= max_val)
        {
            int[] cnt = new int[10];  //统计每个digit
            int[] buf = new int[nums.length];

            for(int i = 0;i<nums.length;i++)
            {
                cnt[(nums[i] / (int)e)%10]++;
            }
            for(int i = 1;i<10;i++)
            {
                cnt[i] = cnt[i] + cnt[i-1];  //应该放置的位置的末尾
            }
            for(int i = nums.length-1;i>=0;i--)
            {
                buf[cnt[(nums[i] / (int)e)%10] - 1] = nums[i];
                cnt[(nums[i] / (int)e)%10]--;
            }
            System.arraycopy(buf,0,nums,0,buf.length);
            e*=10;
        }
        int ans = Integer.MIN_VALUE;
        for(int i = 0;i<nums.length - 1;i++)
        {
            ans = Math.max(ans,nums[i+1] - nums[i]);
        }
        return ans;
    }
}

周赛T4-字符串分组

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

超时的思路

  • 字符串编码异或为0,或者长度相同且编码异或后仅有2个1,或者编码异或为2的次幂(即仅有一个1).先计算出这些信息,再使用并查集分类。
/**
 * 用于实现并查集中每个结点
 */
class UNION_FIND_NODE<E>
{
    public E data;
    public UNION_FIND_NODE<E> p;
    public int rank;
    public UNION_FIND_NODE(E data)
    {
        this.data = data;
        this.p = this;
        this.rank = 0;
    }
}

class UNION_FIND<E>
{
    public void MAKE_SET(UNION_FIND_NODE<E> x)
    {
        x.p = x;
        x.rank = 0;
    }

    public void UNION(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
    {
        LINK(FIND_SET(x),FIND_SET(y));
    }

    public UNION_FIND_NODE<E> FIND_SET(UNION_FIND_NODE<E> x)
    {
        if(x!=x.p)
            x.p = FIND_SET(x.p);
        return x.p;
    }


    private void LINK(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
    {
        if(x.rank > y.rank)
        {
            y.p = x;
        }
        else
        {
            x.p = y;
            if(x.rank == y.rank)
                y.rank++;
        }
    }
}

class Solution {
    public int[] groupStrings(String[] words) {
        UNION_FIND_NODE<Integer>[] a = new UNION_FIND_NODE[words.length];
        UNION_FIND<Integer> e = new UNION_FIND<>();
        for(int i = 0;i<words.length;i++)
        {
            a[i] = new UNION_FIND_NODE<Integer>(i);
            e.MAKE_SET(a[i]);
        }
        int[] c = new int[26];
        int count = 0;
        for(int i = 1;i<=(1<<25);i = i<<1)
            c[count++]  =  i;
        Set<Integer> d = new HashSet<>();
        for(int  i = 0;i<c.length - 1;i++)
            for(int  j = i+1;j<c.length;j++)
                d.add(c[i] + c[j]);
        Set<Integer> h = new HashSet<>();
        for(int i : c)
            h.add(i);
        int[] f = new int[words.length];  //记录编码
        for(int i = 0;i<words.length;i++)
        {
            for(int j = 0;j<words[i].length();j++)
            {
                f[i] += (1 << (words[i].charAt(j) - 'a'));
            }
        }
        for(int i = 0;i<words.length - 1;i++)
            for(int j = i+1;j<words.length;j++)
            {
                if(((f[i] ^ f[j])== 0) || (words[i].length() == words[j].length() && d.contains((f[i] ^ f[j]))) || h.contains(f[i] ^ f[j]))
                    e.UNION(a[i],a[j]);
            }
        Map<Integer,Integer> k = new HashMap<>();
        for(int i = 0;i<words.length;i++)
        {
            UNION_FIND_NODE<Integer> o  = e.FIND_SET(a[i]);
            k.put(o.data,k.getOrDefault(o.data,0) + 1);
        }
        int[] ans = new int[2];
        ans[0] = k.keySet().size();
        for(int i : k.keySet())
        {
            ans[1] = Math.max(ans[1],k.get(i));
        }
        return ans;
    }

}
  • 超时原因分析:建立并查集时 O ( n 2 ) O(n^2) O(n2)遍历了字符串数组,有许多不必要的重复遍历!
  • 改进:对每个字符串是否需要遍历其后的所有字符串?不需要!仅需考察所有可能的变化结果! O ( 2 6 2 n ) O(26^2 n) O(262n)
  • 注意重复字符串,先将其合并即可。
/**
 * 用于实现并查集中每个结点
 */
class UNION_FIND_NODE<E>
{
    public E data;
    public UNION_FIND_NODE<E> p;
    public int rank;
    public UNION_FIND_NODE(E data)
    {
        this.data = data;
        this.p = this;
        this.rank = 0;
    }
}

class UNION_FIND<E>
{
    public void MAKE_SET(UNION_FIND_NODE<E> x)
    {
        x.p = x;
        x.rank = 0;
    }

    public void UNION(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
    {
        LINK(FIND_SET(x),FIND_SET(y));
    }

    public UNION_FIND_NODE<E> FIND_SET(UNION_FIND_NODE<E> x)
    {
        if(x!=x.p)
            x.p = FIND_SET(x.p);
        return x.p;
    }


    private void LINK(UNION_FIND_NODE<E> x,UNION_FIND_NODE<E> y)
    {
        if(x.rank > y.rank)
        {
            y.p = x;
        }
        else
        {
            x.p = y;
            if(x.rank == y.rank)
                y.rank++;
        }
    }
}

class Solution {
    public int[] groupStrings(String[] words) {
        UNION_FIND_NODE<Integer>[] a = new UNION_FIND_NODE[words.length];
        UNION_FIND<Integer> e = new UNION_FIND<>();
        Map<Integer,List<Integer>> h = new HashMap<>();
        int[] f = new int[words.length];
        for(int i = 0;i<words.length;i++)
        {
            for(int j = 0;j<words[i].length();j++)
            {
                f[i] += (1 << (words[i].charAt(j) - 'a'));
            }
        }
        for(int i = 0;i<words.length;i++)
        {
            List<Integer> tmp = h.getOrDefault(f[i],new ArrayList<Integer>());
            tmp.add(i);
            h.put(f[i],tmp);
            a[i] = new UNION_FIND_NODE<Integer>(i);
            e.MAKE_SET(a[i]);
        }
        for(Integer l  : h.keySet())
        {
            if(h.get(l).size() > 1)
            {
                for(int  i = 1;i<h.get(l).size();i++)
                    e.UNION(a[h.get(l).get(i)],a[h.get(l).get(i-1)]);   //合并重复的字符串
            }
        }
        for(int i = 0;i<words.length;i++)
        {
            if(h.containsKey(f[i]))
            {
                h.remove(f[i]);
                for(int j = 0;j<=25;j++)
                {
                    //添加
                    if(((f[i] >> j) & 1) == 0 && h.containsKey(((1 << j) | f[i])))
                        e.UNION(a[i],a[h.get(((1 << j) | f[i])).get(0)]);
                    //删除
                    if(((f[i] >> j) & 1) == 1 && h.containsKey((~(1 << j) & f[i])))
                        e.UNION(a[i],a[h.get((~(1 << j) & f[i])).get(0)]);
                    //替换
                    if(((f[i] >> j)&1) == 1)
                    {
                        for(int k = 0;k<=25;k++)
                        {
                            if(k!=j && (((f[i] >> k) & 1) == 0))
                            {
                                int tmp = (~(1 << j) & f[i]) | (1 << k);
                                if(h.containsKey(tmp))
                                    e.UNION(a[i],a[h.get(tmp).get(0)]);
                            }
                        }
                    }
                }
            }
        }
        Map<Integer,Integer> k = new HashMap<>();
        for(int i = 0;i<words.length;i++)
        {
            UNION_FIND_NODE<Integer> o  = e.FIND_SET(a[i]);
            k.put(o.data,k.getOrDefault(o.data,0) + 1);
        }
        int[] ans = new int[2];
        ans[0] = k.keySet().size();
        for(int i : k.keySet())
        {
            ans[1] = Math.max(ans[1],k.get(i));
        }
        return ans;
    }
}

T3(滚动哈希+反向滑动窗口)

虽然暴力可以过,但是反向滑动窗口 O ( n ) O(n) O(n)。充分利用两数乘积取余的结论。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值