LeetCode Top100之76,78,79,84题

  • 写于2019年6月2日-6月3日
76. 最小覆盖子串
① 题目描述
  • 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
  • 示例:

输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”

  • 说明:
    如果 S 中不存这样的子串,则返回空字符串 “”。
    如果 S 中存在这样的子串,我们保证它是唯一的答案。
② 暴力法(Time Limit Exceeded)
  • 从下标0开始,验证长度至少为T.len的S的子串:遍历T中的字符看其在子串中是否存在,若存在需要删除子串中的该字符,避免出现情况1;在最后更新result时,需要将result.len与原始的子串长度作比较,而不是直接sub.length(),避免出现情况2.

情况1:
Input: “bbaa"和"aba”
Output: “bba”
Expected: “baa”

情况2:
Input: “abc"和"ab”
Output: “abc”
Expected: “ab”

  • 代码如下,可惜Time Limit Exceeded
public String minWindow(String s, String t) {
    int tlen = t.length();
    int slen = s.length();
    String minString = "";
    for (int i = 0; i < slen; i++) {
        for (int j = i + tlen - 1; j < slen; j++) {
            String sub = s.substring(i, j + 1);
            boolean flag = true;
            for (int k = 0; k < tlen; k++) {
                int index = sub.indexOf(t.charAt(k));
                if (index == -1) {
                    flag = false;
                    break;
                } else {
                    sub = sub.substring(0, index) + sub.substring(index + 1);
                }
            }
            if (flag) {
                if (minString.equals("")) {
                    minString = s.substring(i, j + 1);
                } else if (minString.length() > j - i + 1) {// 不能与当前的sub.len作比较,应该与初始时的sub.len作比较
                    minString = s.substring(i, j + 1);
                }
            }
        }
    }
    return minString;
}
③ 滑动窗口(双指针)
  • 算法思想如下:
    ① 初始,left指针和right指针都指向S的第一个元素。
    ② 将 right 指针右移,扩张窗口,直到得到一个可行窗口,该窗口包含T的全部字目。
    ③ 得到可行的窗口后,将left指针逐个右移压缩窗口(去除窗口前端多余字母),若得到的窗口依然可行,则更新最小窗口大小。
    ④ 若窗口不再可行,则跳转至 ②。
  • 使用Hashmap记录T中字母的个数,然后使用count进行计数,如果出现T中的有效字符count + 1;不断移动right指针,直到count与T.len相等。
  • left指针右移压缩窗口,当前字母个数加1,如果出现当前字母的个数大于0,表示该字母是有效的,要让count减1。
  • 代码如下:
 public String minWindow(String s, String t) {
    HashMap<Character, Integer> map = new HashMap<>();
    for (int i = 0; i < t.length(); i++) {// 构造t的Hashmap
        char ch = t.charAt(i);
        map.put(ch, map.getOrDefault(ch, 0) + 1);
    }
    int left = 0;
    int right = 0;
    int count = 0;
    String result = "";
    while (right < s.length()) {// 如果right=s.len,说明到最后一个字母时,没有找到最小覆盖子串,不用再找了
        char ch1 = s.charAt(right);
        // 当前的字母次数减一,一定得是字母在Hashmap中,即是t中的字母
        if (map.containsKey(ch1)) {
            map.put(ch1, map.get(ch1) - 1);
            //代表当前符合了一个字母
            if (map.get(ch1) >= 0) {
                count++;
            }
        }
        while (count == t.length()) {// 只要count仍然为t.len,就可以尝试不断右移left压缩窗口
            if (result.equals("")) {
                result = s.substring(left, right + 1);
            } else if (result.length() > (right - left + 1)) {
                result = s.substring(left, right + 1);
            }
            char ch2 = s.charAt(left);
            // 因为要把当前字母移除,所以相应次数要加 1
            if (map.containsKey(ch2)) {
                map.put(ch2, map.get(ch2) + 1);
                if (map.get(ch2) > 0) { //此时的 map[key] 大于 0 了,表示缺少当前字母了,count--
                    count--;
                }
            }
            left++;
        }
        right++;
    }
    return result;
}
78. 子集
① 题目描述
  • 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
  • 说明:解集不能包含重复的子集。
  • 示例:

输入: nums = [1,2,3]
输出: [
       [3],
       [1],
       [2],
       [1,2,3],
       [1,3],
       [2,3],
       [1,2],
       []
]

② 回溯法
  • 从构造一个集合的指定个数的数的组合获取灵感,固定start,选取相应数目的元素,构成一个组合。
    在这里插入图片描述
  • 代码如下:
public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    result.add(new ArrayList<>());
    if (nums.length == 0) {
        return result;
    }
    for (int i = 1; i <= nums.length; i++) {
        List<Integer> item = new ArrayList<>();
        backtrace(nums, i, item, result, 0);
    }
    return result;
}

public void backtrace(int[] nums, int len, List<Integer> item, List<List<Integer>> result, int start) {
    if (item.size() == len) {
        result.add(new ArrayList<>(item));
        return;
    }
    for (int i = start; i < nums.length; i++) {
        item.add(nums[i]);
        backtrace(nums, len, item, result, i + 1);
        item.remove(item.size() - 1);
    }
}
③ 位操作
  • 通过观察发现,N个数字生成的组合可以与N bit从0~ 2 N − 1 2^N - 1 2N1一致。
1 2 3
0 0 0 -> [     ]
0 0 1 -> [    1]
0 1 0 -> [  2  ]   
0 1 1 -> [  2 1]  
1 0 0 -> [3    ]
1 0 1 -> [3   1] 
1 1 0 -> [3 2  ]
1 1 1 -> [3 2 1]
  • 代码如下:
public List<List<Integer>> subsets(int[] nums) {
    int times = (int) Math.pow(2, nums.length);
    List<List<Integer>> result = new ArrayList<>();
    for (int i = 0; i < times; i++) {
        int temp = i;
        List<Integer> list = new ArrayList<>();
        int count = 0;// 记录要添加的是哪一位
        while (temp != 0) {
            if ((temp & 1) == 1) {// 判断待添加位是否为1
                list.add(nums[count]);
            }
            temp = temp >> 1; // 右移一位,继续判断下一个待添加位
            count++;// 指向下一个要添加的位置
        }
        result.add(list);
    }
    return result;
}
79. 单词搜索
① 题目描述
  • 给定一个二维网格和一个单词,找出该单词是否存在于网格中。
  • 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
  • 示例:

board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
给定 word = “ABCCED”, 返回 true.
给定 word = “SEE”, 返回 true.
给定 word = “ABCB”, 返回 false.

② 回溯+DFS
  • DFS需要使用visited记录元素是否已经访问过,如果元素被访问过,或者元素不在word中,就返回false。
  • 代码如下:
public boolean exist(char[][] board, String word) {
    boolean[][] visited = new boolean[board.length][board[0].length];
    for (int i = 0; i < board.length; i++) {
        for (int j = 0; j < board[0].length; j++) {
            if (word.charAt(0) == board[i][j] && backtrace(i, j, 0, word, board, visited)) {
                return true;
            }
        }
    }
    return false;
}

public boolean backtrace(int i, int j, int cur, String word, char[][] bord, boolean[][] visited) {
    if (cur == word.length()) {
        return true;
    }
    if (i < 0 || j < 0 || i >= bord.length || j >= bord[0].length
            || visited[i][j] || word.charAt(cur) != bord[i][j]) {
        return false;
    }
    visited[i][j] = true;
    if (backtrace(i - 1, j, cur + 1, word, bord, visited) || backtrace(i + 1, j, cur + 1, word, bord, visited)
            || backtrace(i, j - 1, cur + 1, word, bord, visited) || backtrace(i, j + 1, cur + 1, word, bord, visited)) {
        return true;
    }
    visited[i][j] = false;
    return false;
}
84. 柱状图中最大的矩形
① 题目描述
  • 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
  • 求在该柱状图中,能够勾勒出来的矩形的最大面积。
    在这里插入图片描述
  • 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
    在这里插入图片描述
  • 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
  • 示例:

输入: [2,1,5,6,2,3]
输出: 10

② 暴力法
  • 从下标i开始,获得i到j 的最小高度作为矩形的高,将(j - i + 1)作为矩形的长,计算出当前矩形的面积,并更新max_area;
  • 代码如下,虽然运行时间很长,但是竟然没有超时!
public int largestRectangleArea(int[] heights) {
    int area = 0;
    for (int i = 0; i < heights.length; i++) {
        int min = heights[i];
        for (int j = i; j < heights.length; j++) {
            if (min > heights[j]) {
                min = heights[j];
            }
            int temp_area = (j - i + 1) * min;
            if (area < (j - i + 1) * min) {
                area = temp_area;
            }
        }
    }
    return area;
}
③ 使用Stack
  • 首先我们定义我们的stack,用来储存的是对应矩形的下标。我们希望,这个stack里储存的下标所对应的高度是递增的。如果出现不递增的情况,则代表出现了断层,这时候我们就可以在这个断层处更新我们的最大面积。
  • 特殊情况: 有人可能会问,如果这些矩形的高度是一直递增的呢?那不就不存在下降的断层嘛?没错,所以我们在heights的末尾处加了一个0,就是为了让面积能在最后结束结算。因此循环时,i的上限为heights.len
  • 代码如下:
public int largestRectangleArea(int[] heights) {
    int area = 0;
    Stack<Integer> stack = new Stack<>();
    for (int i = 0; i <= heights.length; i++) {
        int h = (i == heights.length) ? 0 : heights[i];
        if (stack.isEmpty() || h >= heights[stack.peek()]) {
            stack.push(i);
        } else {
            int temp = stack.pop();
            area = Math.max(area, heights[temp] * (stack.isEmpty() ? i : i - 1 - stack.peek()));
            i--;
        }
    }
    return area;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值