LeetCode Weekly Contest 36解题思路

LeetCode Weekly Contest 36解题思路

详细代码可以fork下Github上leetcode项目,不定期更新。

赛题

本次周赛主要分为以下4道题:

Leetcode 617. Merge Two Binary Trees (4分)

Probelm:

Given two binary trees and imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not.

You need to merge them into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of new tree.

Example :
alt text

Note:

Note: The merging process must start from the root nodes of both trees.

天然的递归子问题,有些小细节,比如判断t1和t2是否为空,经验不足,浪费了很多时间,哎。

代码如下:

public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null && t2 == null) return null;
        TreeNode root = null;
        if (t1 != null && t2 != null) {
            root = new TreeNode(t1.val + t2.val);
            root.left = mergeTrees(t1.left, t2.left);
            root.right = mergeTrees(t1.right, t2.right);
        } else if (t1 == null) {
            root = t2;
        } else if (t2 == null) {
            root = t1;
        }
        return root;
    }

还可以这样:

public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null && t2 == null) return null;
        TreeNode ret = new TreeNode((t1 != null ? t1.val : 0) + (t2 != null ? t2.val : 0));
        ret.left = mergeTrees(t1 != null ? t1.left : null, t2 != null ? t2.left : null);
        ret.right = mergeTrees(t1 != null ? t1.right : null, t2 != null ? t2.right : null);
        return ret;
    }

所以空不空看的是t1和t2,而和新构造的ret没有任何关系。

省点空间的做法:

public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
    if (t1 == null) return t2;
    if (t2 == null) return t1;
    t1.val += t2.val;
    t1.left = mergeTrees(t1.left, t2.left);
    t1.right = mergeTrees(t1.right, t2.right);
    return t1;
}

Leetcode 604. Design Compressed String Iterator (5分)

Problem:

Design and implement a data structure for a compressed string iterator. It should support the following operations: next and hasNext.

The given compressed string will be in the form of each letter followed by a positive integer representing the number of this letter existing in the original uncompressed string.

next() - if the original string still has uncompressed characters, return the next letter; Otherwise return a white space.
hasNext() - Judge whether there is any letter needs to be uncompressed.

Note:

Please remember to RESET your class variables declared in StringIterator, as static/class variables are persisted across multiple test cases. Please see here for more details.

Example:

StringIterator iterator = new StringIterator("L1e2t1C1o1d1e1");

iterator.next(); // return 'L'
iterator.next(); // return 'e'
iterator.next(); // return 'e'
iterator.next(); // return 't'
iterator.next(); // return 'C'
iterator.next(); // return 'o'
iterator.next(); // return 'd'
iterator.hasNext(); // return true
iterator.next(); // return 'e'
iterator.hasNext(); // return false
iterator.next(); // return ' '

为了支持动态扩展,我用了list和map来分别记录字符的顺序和字符的次数,折腾了蛮久,代码不够美观。

思路很简单,每当next一次,map中对应的字符次数减一,直到减到0,删除map和list中的此字符。

如下:

public class StringIterator {

    List<Integer> words = new ArrayList<>();
    Map<Integer, Integer> map = new HashMap<>();
    char[] cs;
    public StringIterator(String compressedString) {
        int n = compressedString.length();
        cs = compressedString.toCharArray();
        for (int i = 0; i < n; ++i) {
            if (Character.isAlphabetic(cs[i])) {
                words.add(i);
            } else {
                int num = 0;
                int lst = i - 1;
                while (i < n && Character.isDigit(cs[i])) {
                    num = num * 10 + cs[i++] - '0';
                }
                map.put(lst, num);
                i--;
            }
        }
    }

    public char next() {
        if (!hasNext())
            return ' ';
        int idx = words.get(0);
        char c = cs[words.get(0)];
        int cnt = map.get(idx);
        if (cnt == 1) {
            words.remove(0);
            map.remove(idx);
        } else {
            map.put(idx, cnt - 1);
        }
        return c;
    }

    public boolean hasNext() {
        return !map.isEmpty();
    }

    public static void main(String[] args) {
        StringIterator iterator = new StringIterator("L1e2t1C1o1d1e1");
        iterator.next(); // return 'L'
        iterator.next(); // return 'e'
        iterator.next(); // return 'e'
        iterator.next(); // return 't'
        iterator.next(); // return 'C'
        iterator.next(); // return 'o'
        iterator.next(); // return 'd'
        iterator.hasNext(); // return true
        iterator.next(); // return 'e'
        iterator.hasNext(); // return false
        iterator.next(); // return ' '
    }
}

这道题完全可以用数组代替,因为字符串的长度一定,所以最多最多也就存放s.length + 1大小的字符(不可能),我们就开这么多数组,存放字符和计数。另外一点,开大了之后,对于实际字符集的大小是无法根据数组大小确定的,所以需要额外的len变量来记录实际大小。

代码如下:

public class StringIterator {

    char[] letter;
    int[] cnt;
    int len;
    public StringIterator(String compressedString) {
        int n = compressedString.length();
        char[] s = compressedString.toCharArray();
        letter = new char[n+1];
        cnt = new int[n+1];
        for (int i = 0, k = 0; i < n; ++i){
            if (s[i] >= 'a' && s[i] <= 'z' || s[i] >= 'A' && s[i] <= 'Z'){
                letter[k] = s[i];
                len++;
            }
            else{
                int num = 0;
                while (i < n && s[i] >= '0' && s[i] <= '9'){
                    num = num * 10 + s[i++] - '0';
                }
                cnt[k] = num;
                k++;
                i--;
            }
        }
    }

    int fir = 0;
    public char next() {
        if (!hasNext()) return ' ';
        char c = letter[fir];
        if (cnt[fir] == 1) fir++;
        else cnt[fir]--;
        return c;
    }

    public boolean hasNext() {
        return len != fir;
    }

    public static void main(String[] args) {
        StringIterator iterator = new StringIterator("L1e2t1C1o1d1e1");
        iterator.next(); // return 'L'
        iterator.next(); // return 'e'
        iterator.next(); // return 'e'
        iterator.next(); // return 't'
        iterator.next(); // return 'C'
        iterator.next(); // return 'o'
        iterator.next(); // return 'd'
        iterator.hasNext(); // return true
        iterator.next(); // return 'e'
        iterator.hasNext(); // return false
        iterator.next(); // return ' '
    }
}

Leetcode 611. Valid Triangle Number (7分)

Problem:

Given an array consists of non-negative integers, your task is to count the number of triplets chosen from the array that can make triangles if we take them as side lengths of a triangle.

Example 1:

Input: [2,2,3,4]
Output: 3
Explanation:
Valid combinations are:
2,3,4 (using the first 2)
2,3,4 (using the second 2)
2,2,3

Note:

  • The length of the given array won’t exceed 1000.
  • The integers in the given array are in the range of [0, 1000].

挺简单的一道题,用到的性质:两边之和大于第三边,此题考的还不是这个,而是在遍历过程中能够加快算法效率的一些技巧。

任意两条边的遍历需要 O(n2) ,第三条边应该如何快速搜索到?因为 O(n2) 是该算法的下限,所以我们可以对数组进行排序,并不影响算法的下限,而在有序数组中,我们就得到了查找第三条边的很好性质,在剩余的边中,只要找到最后一个元素符合三角性质,那么前面的所有元素均符合,而二分查找能够查找 nums[i] < key的最大i。

方法一,二分查找,代码如下:

public int triangleNumber(int[] nums) {
        if (nums.length <= 2) return 0;
        Arrays.sort(nums);
        int cnt = 0;
        int n = nums.length;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n - 1; ++j) {
                int sum = nums[i] + nums[j];
                int lst = j + 1;
                int idx = binarySearch(nums, lst, n - 1, sum);
                if (idx != -1) {
                    cnt += idx - lst + 1;
                }
            }
        }
        return cnt;
    }

    public int binarySearch(int[] nums, int s, int e, int key) {
        int lf = s, rt = e;
        while (lf < rt){
            int mid = lf + (rt - lf + 1) / 2;
            if (nums[mid] >= key) rt = mid - 1;
            else lf = mid;
        }
        if (nums[lf] < key) return lf;
        return -1;
    }

方法二:(利用遍历本身的性质来降低时间复杂度)

nums = 2 2 3 4  (有序)

在找两条边时,我们的搜索顺序是这样的
i = 0
2 2   第三边:3
2 3   第三边:4  
2 4   第三边:无
i = 1
2 3   第三边:4
2 4   第三边:无
i = 2
3 4   第三边:无

在 i = 0 和 i = 1的遍历中,出现了第三边重复的情况,why?

遍历结构告诉我们:随着遍历的一步步进行,nums[i] + nums[j]是不断递增的,所以在i = 0找到的边,可以直接加到 i = 1的边上。

if nums[i] + nums[j] > nums[k]

有效第三边集合为:
j + 1 ... k

if nums[i] + nums[j+1] > nums[k']
有效第三边集合为:
j + 2 ... k ... k'

这个性质告诉我们,保持之前的指针继续搜索就好了,同时把之前的结果加上,就能保证答案的正确了。

代码如下:

public int triangleNumber(int[] nums) {
        if (nums.length <= 2) return 0;
        Arrays.sort(nums);
        int n = nums.length;
        int cnt = 0;
        for (int i = 0; i < n; ++i){
            int pos = i + 2;
            for (int j = i + 1; j < n - 1 && nums[i] != 0; ++j){
                int sum = nums[i] + nums[j];
                while (pos < n && sum > nums[pos]) pos++;
                cnt += pos - j - 1;
            }
        }
        return cnt;
    }

pos并非在两个内循环内不断更新,而是延迟到了一个循环外,这样对于相同的i,pos对不同j是信息共享的,自然时间复杂度就下去了。

Leetcode 616. Add Bold Tag in String (10分)

Problem:

Given a string s and a list of strings dict, you need to add a closed pair of bold tag and to wrap the substrings in s that exist in dict. If two such substrings overlap, you need to wrap them together by only one pair of closed bold tag. Also, if two substrings wrapped by bold tags are consecutive, you need to combine them.

Example 1:

Input:
s = “abcxyz123”
dict = [“abc”,”123”]
Output:
abcxyz123

Example 2:

Input:
s = “aaabbcc”
dict = [“aaa”,”aab”,”bc”]
Output:
aaabbcc”

Note:

  • The given dict won’t contain duplicates, and its length won’t exceed 100.
  • All the strings in input have length in range [1, 1000].

思路很简单,找到所有字典在字符串出现的位置,用区间表示,如果遇到重叠的区间,进行合并,最后用StringBuilder append一下完事,比较综合。

代码如下:

class Interval{
        int i;
        int j;
        public Interval(int i, int j){
            this.i = i;
            this.j = j;
        }

        @Override
        public String toString() {
            return "["+i+" ,"+j+"]";
        }
    }
    public String addBoldTag(String s, String[] dict) {
        List<Interval> inters = new ArrayList<>();
        for (int i = 0; i < dict.length; ++i){
            List<Interval> it = findWord(s, dict[i]);
            if (!it.isEmpty()){
                inters.addAll(it);
            }
        }
        if (inters.isEmpty()) return s;
        Collections.sort(inters, (a,b) -> (a.i != b.i ? a.i - b.i : a.j - b.j));
        List<Interval> mergedInterval = merge(inters);
        StringBuilder sb = new StringBuilder();
        int lst = 0;
        for (Interval it : mergedInterval){
            int i = it.i;
            int j = it.j;
            sb.append(s.substring(lst,i));
            sb.append("<b>");
            sb.append(s.substring(i,j+1));
            sb.append("</b>");
            lst = j + 1;
        }
        sb.append(s.substring(lst,s.length()));
        return sb.toString();
    }

    private List<Interval> merge(List<Interval> intervals){
        List<Interval> ans = new ArrayList<>();
        Interval merge = intervals.get(0);
        int start = merge.i;
        int end = merge.j;
        for (Interval it : intervals){
            if (it.i > end + 1){
                ans.add(new Interval(start, end));
                start = it.i;
                end = it.j;
            }else{
                end = Math.max(end, it.j);
            }
        }
        ans.add(new Interval(start, end));
        return ans;
    }

    private List<Interval> findWord(String s, String f){
        List<Interval> ans = new ArrayList<>();
        char[] cs = s.toCharArray();
        char[] fs = f.toCharArray();
        int j = -1;
        for (int k = 0; k < cs.length; ++k){
            int l = 0;
            while (l < fs.length && k < cs.length && cs[k] == fs[l]){
                k++;
                l++;
            }
            j = k - 1;
            if (l == fs.length) {
                ans.add(new Interval(j - fs.length + 1,j));
            }
            k -= l;
        }
        return ans;
    }

当然你也可以用java库中的indexOf做字符串查找,这里练练手,就自己写了个暴力查找算法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值