leetcode 76. Minimum Window Substring

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

For example,
S = “ADOBECODEBANC”
T = “ABC”
Minimum window is “BANC”.

Note:
If there is no such window in S that covers all characters in T, return the empty string “”.

If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

错误思路
T = “ABC”
S = “ADOBECODEBANC”
1.记录t的map
2.起始时:判断s的map是否包含了 t的map 然后不断缩短s 直到刚好包含t的map(其实并没有能找到最短子串的“缩短”策略)
这个map可以用hashmap 但是已知关键字范围的情况下 使用hashmap占用额外的内存 没有必要 直接用长度128的一维int数组 比用hashmap <character,integer >更好


缩短s的时候
1.本来是这样做的:左边一直走 走到不能走了 然后右边走
显然挂掉了
如:
abaccccb
ab
显然上面的策略是把i定位到第二个a 然后j定位到那个b


2.左边走一步 右边走一步
gewcae
cae
左一步右一步地删除了两边的g e 剩下中间的ewca返回 但是正确结果显然是最后的3位cae 所以这种缩短策略也是错的


这说明。。。如果一开始统计的是s的map 并不知道怎么缩短才能让子段最短。。。
所以一开始就是错的。。。


再重新看这个例子:
cabwefgewcwaefgcf
cae
既然找不到哪个子串是最短的 那么只能把每个符合条件(包含target字符集合)的子串 都试试 看哪个最短了
记录子串的begin end 分别以每个字符作为begin


1.c开始 那么为了包含 需要c a b w e
2.然后尝试从a开始 由于抛弃了前面的c 那么需要找到一个c才能覆盖target 所以直接到abwefgewc
3.然后尝试从b开始 前面抛弃的是个a bwefgewcwa
4.然后尝试从w开始 前面抛弃的是个无用的b wefgewcwa
5.然后尝试从e开始 前面抛弃的是个无用的w efgewcwa
6.然后尝试从f开始 前面抛弃的是个e fgewcwa
7.然后尝试从g开始 前面抛弃的是个f gewcwa
8.然后尝试从e开始 前面抛弃的是个g ewcwa
9.然后尝试从w开始 前面抛弃的是个e wcwae
10.然后尝试从c开始 前面抛弃的是个w cwae
11.然后尝试从w开始 前面抛弃的是个c waefgc
….
其实最小的cwae在第10步已经出现了


如果每次查找对应end的时候都逐个排查 不利用前一个begin的信息 那么就没有体现算法 就是很差的解答 实际上这里是可以利用的:
当前一个begin-end对已知 现在begin右移一步 分为两种情况:
1.移出去的节点无关紧要 即使移出去还是包含t 那么说明end不需要移动
2.移出去的节点很重要 移出去了之后就不包含t了 那么为了包含t 需要移动end 找到移出去的那个元素 使得子串可以再次包含t


这才是正确的思路。。。。。!!!!!!
这道题目在前面的缩短策略上花费了很多时间 后来看了别人的解答 应该是没有办法的不然大家都用了 这种题目不要想偏了。。不要在错误的思路上想太久。。。


找到第一个子串 定义最小窗口值(最小窗口的begin end width)
循环移动子串的begin:
1.如果移出的begin字符导致不再包含t的字符集 那么移动end(直至边界)
2.如果不影响(依然包含) 那么不更新end 考虑更新最小窗口值


网上的一个思路:
找到第一个子串 定义最小窗口值(最小窗口的begin end width)
循环移动子串的end:
1.加入当前end后判断当前的首节点是否变成多余 如果是就删掉 一直删掉无冗余的位置 更新最小窗口值
2.如果加入end后当前首节点并不多余 那么无操作


这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

可以看到 不是每次新found一个字符就减count 要看这个新found的有没有用 没在target中出现的字符 或者该字符已经满了 就是没用的found 是不更新count的
现在找到了第一个覆盖t的子串 :cabwe
接下来考虑怎么挪动这个子串 尽快找到下一个子串
一种方法是上面说的:每次删掉首节点 该删除动作可能导致状态由覆盖变成不覆盖 所以要将尾节点移动到合适的位置 保证依然覆盖
第二种方法是每次新加入一个尾节点后缀 节点的加入不可能导致不覆盖只可能会冗余 所以加入之后判首节点 去掉冗余就可以
这个方法非常好非常建议用http://www.cnblogs.com/ganganloveu/p/4153041.html
相比而言第一种有特殊情况:删掉一个头结点 尾节点走到尾也没找到可以补充的 但是这毕竟是我自己想的方法 跪着也要写完

public String minWindow(String s, String t) {
        //统计target的    count  map
        int count = 0;
        int[] found = new int[128];
        Arrays.fill(found, 0);
        int[] needFind = new int[128];
        Arrays.fill(needFind, 0);
        for(char c:t.toCharArray()) { needFind[(int)c]++; count++;}
        int minStart = 0,minEnd = 0,minWidth = 0;
        int start = 0,end = 0;
        while(end < s.length()){
            if(count == 0)
                break;
            found[(int)s.charAt(end)]++;
            if(needFind[(int)s.charAt(end)] >= found[(int)s.charAt(end)])
                count --;
            end++;
        }
        if(count > 0) return "";
        minEnd = end;
        minWidth = end;
        while(start < s.length()){
            //System.out.println(start+" " +found[s.charAt(start)]+" " + needFind[s.charAt(start)]);
            if(found[s.charAt(start)] == needFind[s.charAt(start)]){
                while(end < s.length() && s.charAt(end) != s.charAt(start)){
                    found[(int)s.charAt(end)]++;
                    end++;
                }
                if(end == s.length())
                    break;
                else{
                    found[(int)s.charAt(end)]++;
                    end++;
                }
            }
            found[s.charAt(start)] --;
            start++;
            if(end - start < minWidth){
                minStart = start;
                minEnd = end;
                minWidth = end - start;
            }
        }
        System.out.println(minStart + " " + minEnd);
        return s.substring(minStart,minEnd);
    }

写了整整一天的代码 深刻体会到程序复杂度是很重要的一部分
再整理 一遍我的思路:
这里写图片描述
现在的cabwe满足 但是我准备踢掉c了 我发现c很重要 所以end你去找个c来填充 如果找不到 说明当前开始位置已经不能满足条件了 那我们就返回吧 如果找得到 你就留在那个位置
这里写图片描述
我现在要踢掉a了 我发现a也是临界的重要元素 踢掉他要有替补a才行
end你去找个a 你已经在尾部不能去找a了?那没戏了 返回吧

每次踢掉首节点的时候
end如果需要找替补
1.1 找到了替补 很好
1.2没找到替补 说明当前首节点开始的子串已经不符合 直接返回
踢元素 看看当前长度有没有小于最小长度

再写一下网上的那个思路 就是移动尾节点的那个版本:

public String minWindow(String s, String t) {
        int[] found = new int[128];
        Arrays.fill(found, 0);
        int count = 0;
        int[] needFind = new int[128];
        Arrays.fill(needFind, 0);
        for(char c:t.toCharArray()) { needFind[(int)c]++; count++;}
        int minStart = 0,minEnd = 0,minWidth = Integer.MAX_VALUE;
        int start = 0,end = 0;
        while(end < s.length()){
            found[(int)s.charAt(end)]++;
            if(found[(int)s.charAt(end)] <= needFind[(int)s.charAt(end)])
                count --;
            end ++;
            if(count == 0){
                while( found[(int)s.charAt(start)] > needFind[(int)s.charAt(start)]) {
                    found[(int)s.charAt(start)]--;
                    start++;
                }
                if(end - start < minWidth){
                    minStart = start;
                    minEnd = end;
                    minWidth = end - start;
                }
            }
        }
        return s.substring(minStart,minEnd);
    }

方法好 代码就会非常简洁 比我想的移头结点的方法好多了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值